/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <iomanip> #include <iostream> #include <cmath> #include <sstream> #include "Generator.h" #include "Specification.h" #include "Utilities.h" using namespace std; // Converts float2 to FLOAT_32 and 2, etc. static void convertToRsType(const string& name, string* dataType, char* vectorSize) { string s = name; int last = s.size() - 1; char lastChar = s[last]; if (lastChar >= '1' && lastChar <= '4') { s.erase(last); *vectorSize = lastChar; } else { *vectorSize = '1'; } dataType->clear(); for (int i = 0; i < NUM_TYPES; i++) { if (s == TYPES[i].cType) { *dataType = TYPES[i].rsDataType; break; } } } // Returns true if any permutation of the function have tests to b static bool needTestFiles(const Function& function, unsigned int versionOfTestFiles) { for (auto spec : function.getSpecifications()) { if (spec->hasTests(versionOfTestFiles)) { return true; } } return false; } /* One instance of this class is generated for each permutation of a function for which * we are generating test code. This instance will generate both the script and the Java * section of the test files for this permutation. The class is mostly used to keep track * of the various names shared between script and Java files. * WARNING: Because the constructor keeps a reference to the FunctionPermutation, PermutationWriter * should not exceed the lifetime of FunctionPermutation. */ class PermutationWriter { private: FunctionPermutation& mPermutation; string mRsKernelName; string mJavaArgumentsClassName; string mJavaArgumentsNClassName; string mJavaVerifierComputeMethodName; string mJavaVerifierVerifyMethodName; string mJavaCheckMethodName; string mJavaVerifyMethodName; // Pointer to the files we are generating. Handy to avoid always passing them in the calls. GeneratedFile* mRs; GeneratedFile* mJava; /* Shortcuts to the return parameter and the first input parameter of the function * specification. */ const ParameterDefinition* mReturnParam; // Can be nullptr. NOT OWNED. const ParameterDefinition* mFirstInputParam; // Can be nullptr. NOT OWNED. /* All the parameters plus the return param, if present. Collecting them together * simplifies code generation. NOT OWNED. */ vector<const ParameterDefinition*> mAllInputsAndOutputs; /* We use a class to pass the arguments between the generated code and the CoreVerifier. This * method generates this class. The set keeps track if we've generated this class already * for this test file, as more than one permutation may use the same argument class. */ void writeJavaArgumentClass(bool scalar, set<string>* javaGeneratedArgumentClasses) const; // Generate the Check* method that invokes the script and calls the verifier. void writeJavaCheckMethod(bool generateCallToVerifier) const; // Generate code to define and randomly initialize the input allocation. void writeJavaInputAllocationDefinition(const ParameterDefinition& param) const; /* Generate code that instantiate an allocation of floats or integers and fills it with * random data. This random data must be compatible with the specified type. This is * used for the convert_* tests, as converting values that don't fit yield undefined results. */ void writeJavaRandomCompatibleFloatAllocation(const string& dataType, const string& seed, char vectorSize, const NumericalType& compatibleType, const NumericalType& generatedType) const; void writeJavaRandomCompatibleIntegerAllocation(const string& dataType, const string& seed, char vectorSize, const NumericalType& compatibleType, const NumericalType& generatedType) const; // Generate code that defines an output allocation. void writeJavaOutputAllocationDefinition(const ParameterDefinition& param) const; /* Generate the code that verifies the results for RenderScript functions where each entry * of a vector is evaluated independently. If verifierValidates is true, CoreMathVerifier * does the actual validation instead of more commonly returning the range of acceptable values. */ void writeJavaVerifyScalarMethod(bool verifierValidates) const; /* Generate the code that verify the results for a RenderScript function where a vector * is a point in n-dimensional space. */ void writeJavaVerifyVectorMethod() const; // Generate the line that creates the Target. void writeJavaCreateTarget() const; // Generate the method header of the verify function. void writeJavaVerifyMethodHeader() const; // Generate codes that copies the content of an allocation to an array. void writeJavaArrayInitialization(const ParameterDefinition& p) const; // Generate code that tests one value returned from the script. void writeJavaTestAndSetValid(const ParameterDefinition& p, const string& argsIndex, const string& actualIndex) const; void writeJavaTestOneValue(const ParameterDefinition& p, const string& argsIndex, const string& actualIndex) const; // For test:vector cases, generate code that compares returned vector vs. expected value. void writeJavaVectorComparison(const ParameterDefinition& p) const; // Muliple functions that generates code to build the error message if an error is found. void writeJavaAppendOutputToMessage(const ParameterDefinition& p, const string& argsIndex, const string& actualIndex, bool verifierValidates) const; void writeJavaAppendInputToMessage(const ParameterDefinition& p, const string& actual) const; void writeJavaAppendNewLineToMessage() const; void writeJavaAppendVectorInputToMessage(const ParameterDefinition& p) const; void writeJavaAppendVectorOutputToMessage(const ParameterDefinition& p) const; // Generate the set of instructions to call the script. void writeJavaCallToRs(bool relaxed, bool generateCallToVerifier) const; // Write an allocation definition if not already emitted in the .rs file. void writeRsAllocationDefinition(const ParameterDefinition& param, set<string>* rsAllocationsGenerated) const; public: /* NOTE: We keep pointers to the permutation and the files. This object should not * outlive the arguments. */ PermutationWriter(FunctionPermutation& permutation, GeneratedFile* rsFile, GeneratedFile* javaFile); string getJavaCheckMethodName() const { return mJavaCheckMethodName; } // Write the script test function for this permutation. void writeRsSection(set<string>* rsAllocationsGenerated) const; // Write the section of the Java code that calls the script and validates the results void writeJavaSection(set<string>* javaGeneratedArgumentClasses) const; }; PermutationWriter::PermutationWriter(FunctionPermutation& permutation, GeneratedFile* rsFile, GeneratedFile* javaFile) : mPermutation(permutation), mRs(rsFile), mJava(javaFile), mReturnParam(nullptr), mFirstInputParam(nullptr) { mRsKernelName = "test" + capitalize(permutation.getName()); mJavaArgumentsClassName = "Arguments"; mJavaArgumentsNClassName = "Arguments"; const string trunk = capitalize(permutation.getNameTrunk()); mJavaCheckMethodName = "check" + trunk; mJavaVerifyMethodName = "verifyResults" + trunk; for (auto p : permutation.getParams()) { mAllInputsAndOutputs.push_back(p); if (mFirstInputParam == nullptr && !p->isOutParameter) { mFirstInputParam = p; } } mReturnParam = permutation.getReturn(); if (mReturnParam) { mAllInputsAndOutputs.push_back(mReturnParam); } for (auto p : mAllInputsAndOutputs) { const string capitalizedRsType = capitalize(p->rsType); const string capitalizedBaseType = capitalize(p->rsBaseType); mRsKernelName += capitalizedRsType; mJavaArgumentsClassName += capitalizedBaseType; mJavaArgumentsNClassName += capitalizedBaseType; if (p->mVectorSize != "1") { mJavaArgumentsNClassName += "N"; } mJavaCheckMethodName += capitalizedRsType; mJavaVerifyMethodName += capitalizedRsType; } mJavaVerifierComputeMethodName = "compute" + trunk; mJavaVerifierVerifyMethodName = "verify" + trunk; } void PermutationWriter::writeJavaSection(set<string>* javaGeneratedArgumentClasses) const { // By default, we test the results using item by item comparison. const string test = mPermutation.getTest(); if (test == "scalar" || test == "limited") { writeJavaArgumentClass(true, javaGeneratedArgumentClasses); writeJavaCheckMethod(true); writeJavaVerifyScalarMethod(false); } else if (test == "custom") { writeJavaArgumentClass(true, javaGeneratedArgumentClasses); writeJavaCheckMethod(true); writeJavaVerifyScalarMethod(true); } else if (test == "vector") { writeJavaArgumentClass(false, javaGeneratedArgumentClasses); writeJavaCheckMethod(true); writeJavaVerifyVectorMethod(); } else if (test == "noverify") { writeJavaCheckMethod(false); } } void PermutationWriter::writeJavaArgumentClass(bool scalar, set<string>* javaGeneratedArgumentClasses) const { string name; if (scalar) { name = mJavaArgumentsClassName; } else { name = mJavaArgumentsNClassName; } // Make sure we have not generated the argument class already. if (!testAndSet(name, javaGeneratedArgumentClasses)) { mJava->indent() << "public class " << name; mJava->startBlock(); for (auto p : mAllInputsAndOutputs) { bool isFieldArray = !scalar && p->mVectorSize != "1"; bool isFloatyField = p->isOutParameter && p->isFloatType && mPermutation.getTest() != "custom"; mJava->indent() << "public "; if (isFloatyField) { *mJava << "Target.Floaty"; } else { *mJava << p->javaBaseType; } if (isFieldArray) { *mJava << "[]"; } *mJava << " " << p->variableName << ";\n"; // For Float16 parameters, add an extra 'double' field in the class // to hold the Double value converted from the input. if (p->isFloat16Parameter() && !isFloatyField) { mJava->indent() << "public double"; if (isFieldArray) { *mJava << "[]"; } *mJava << " " + p->variableName << "Double;\n"; } } mJava->endBlock(); *mJava << "\n"; } } void PermutationWriter::writeJavaCheckMethod(bool generateCallToVerifier) const { mJava->indent() << "private void " << mJavaCheckMethodName << "()"; mJava->startBlock(); // Generate the input allocations and initialization. for (auto p : mAllInputsAndOutputs) { if (!p->isOutParameter) { writeJavaInputAllocationDefinition(*p); } } // Generate code to enforce ordering between two allocations if needed. for (auto p : mAllInputsAndOutputs) { if (!p->isOutParameter && !p->smallerParameter.empty()) { string smallerAlloc = "in" + capitalize(p->smallerParameter); mJava->indent() << "enforceOrdering(" << smallerAlloc << ", " << p->javaAllocName << ");\n"; } } // Generate code to check the full and relaxed scripts. writeJavaCallToRs(false, generateCallToVerifier); writeJavaCallToRs(true, generateCallToVerifier); mJava->endBlock(); *mJava << "\n"; } void PermutationWriter::writeJavaInputAllocationDefinition(const ParameterDefinition& param) const { string dataType; char vectorSize; convertToRsType(param.rsType, &dataType, &vectorSize); const string seed = hashString(mJavaCheckMethodName + param.javaAllocName); mJava->indent() << "Allocation " << param.javaAllocName << " = "; if (param.compatibleTypeIndex >= 0) { if (TYPES[param.typeIndex].kind == FLOATING_POINT) { writeJavaRandomCompatibleFloatAllocation(dataType, seed, vectorSize, TYPES[param.compatibleTypeIndex], TYPES[param.typeIndex]); } else { writeJavaRandomCompatibleIntegerAllocation(dataType, seed, vectorSize, TYPES[param.compatibleTypeIndex], TYPES[param.typeIndex]); } } else if (!param.minValue.empty()) { *mJava << "createRandomFloatAllocation(mRS, Element.DataType." << dataType << ", " << vectorSize << ", " << seed << ", " << param.minValue << ", " << param.maxValue << ")"; } else { /* TODO Instead of passing always false, check whether we are doing a limited test. * Use instead: (mPermutation.getTest() == "limited" ? "false" : "true") */ *mJava << "createRandomAllocation(mRS, Element.DataType." << dataType << ", " << vectorSize << ", " << seed << ", false)"; } *mJava << ";\n"; } void PermutationWriter::writeJavaRandomCompatibleFloatAllocation( const string& dataType, const string& seed, char vectorSize, const NumericalType& compatibleType, const NumericalType& generatedType) const { *mJava << "createRandomFloatAllocation" << "(mRS, Element.DataType." << dataType << ", " << vectorSize << ", " << seed << ", "; double minValue = 0.0; double maxValue = 0.0; switch (compatibleType.kind) { case FLOATING_POINT: { // We're generating floating point values. We just worry about the exponent. // Subtract 1 for the exponent sign. int bits = min(compatibleType.exponentBits, generatedType.exponentBits) - 1; maxValue = ldexp(0.95, (1 << bits) - 1); minValue = -maxValue; break; } case UNSIGNED_INTEGER: maxValue = maxDoubleForInteger(compatibleType.significantBits, generatedType.significantBits); minValue = 0.0; break; case SIGNED_INTEGER: maxValue = maxDoubleForInteger(compatibleType.significantBits, generatedType.significantBits); minValue = -maxValue - 1.0; break; } *mJava << scientific << std::setprecision(19); *mJava << minValue << ", " << maxValue << ")"; mJava->unsetf(ios_base::floatfield); } void PermutationWriter::writeJavaRandomCompatibleIntegerAllocation( const string& dataType, const string& seed, char vectorSize, const NumericalType& compatibleType, const NumericalType& generatedType) const { *mJava << "createRandomIntegerAllocation" << "(mRS, Element.DataType." << dataType << ", " << vectorSize << ", " << seed << ", "; if (compatibleType.kind == FLOATING_POINT) { // Currently, all floating points can take any number we generate. bool isSigned = generatedType.kind == SIGNED_INTEGER; *mJava << (isSigned ? "true" : "false") << ", " << generatedType.significantBits; } else { bool isSigned = compatibleType.kind == SIGNED_INTEGER && generatedType.kind == SIGNED_INTEGER; *mJava << (isSigned ? "true" : "false") << ", " << min(compatibleType.significantBits, generatedType.significantBits); } *mJava << ")"; } void PermutationWriter::writeJavaOutputAllocationDefinition( const ParameterDefinition& param) const { string dataType; char vectorSize; convertToRsType(param.rsType, &dataType, &vectorSize); mJava->indent() << "Allocation " << param.javaAllocName << " = Allocation.createSized(mRS, " << "getElement(mRS, Element.DataType." << dataType << ", " << vectorSize << "), INPUTSIZE);\n"; } void PermutationWriter::writeJavaVerifyScalarMethod(bool verifierValidates) const { writeJavaVerifyMethodHeader(); mJava->startBlock(); string vectorSize = "1"; for (auto p : mAllInputsAndOutputs) { writeJavaArrayInitialization(*p); if (p->mVectorSize != "1" && p->mVectorSize != vectorSize) { if (vectorSize == "1") { vectorSize = p->mVectorSize; } else { cerr << "Error. Had vector " << vectorSize << " and " << p->mVectorSize << "\n"; } } } mJava->indent() << "StringBuilder message = new StringBuilder();\n"; mJava->indent() << "boolean errorFound = false;\n"; mJava->indent() << "for (int i = 0; i < INPUTSIZE; i++)"; mJava->startBlock(); mJava->indent() << "for (int j = 0; j < " << vectorSize << " ; j++)"; mJava->startBlock(); mJava->indent() << "// Extract the inputs.\n"; mJava->indent() << mJavaArgumentsClassName << " args = new " << mJavaArgumentsClassName << "();\n"; for (auto p : mAllInputsAndOutputs) { if (!p->isOutParameter) { mJava->indent() << "args." << p->variableName << " = " << p->javaArrayName << "[i"; if (p->vectorWidth != "1") { *mJava << " * " << p->vectorWidth << " + j"; } *mJava << "];\n"; // Convert the Float16 parameter to double and store it in the appropriate field in the // Arguments class. if (p->isFloat16Parameter()) { mJava->indent() << "args." << p->doubleVariableName << " = Float16Utils.convertFloat16ToDouble(args." << p->variableName << ");\n"; } } } const bool hasFloat = mPermutation.hasFloatAnswers(); if (verifierValidates) { mJava->indent() << "// Extract the outputs.\n"; for (auto p : mAllInputsAndOutputs) { if (p->isOutParameter) { mJava->indent() << "args." << p->variableName << " = " << p->javaArrayName << "[i * " << p->vectorWidth << " + j];\n"; if (p->isFloat16Parameter()) { mJava->indent() << "args." << p->doubleVariableName << " = Float16Utils.convertFloat16ToDouble(args." << p->variableName << ");\n"; } } } mJava->indent() << "// Ask the CoreMathVerifier to validate.\n"; if (hasFloat) { writeJavaCreateTarget(); } mJava->indent() << "String errorMessage = CoreMathVerifier." << mJavaVerifierVerifyMethodName << "(args"; if (hasFloat) { *mJava << ", target"; } *mJava << ");\n"; mJava->indent() << "boolean valid = errorMessage == null;\n"; } else { mJava->indent() << "// Figure out what the outputs should have been.\n"; if (hasFloat) { writeJavaCreateTarget(); } mJava->indent() << "CoreMathVerifier." << mJavaVerifierComputeMethodName << "(args"; if (hasFloat) { *mJava << ", target"; } *mJava << ");\n"; mJava->indent() << "// Validate the outputs.\n"; mJava->indent() << "boolean valid = true;\n"; for (auto p : mAllInputsAndOutputs) { if (p->isOutParameter) { writeJavaTestAndSetValid(*p, "", "[i * " + p->vectorWidth + " + j]"); } } } mJava->indent() << "if (!valid)"; mJava->startBlock(); mJava->indent() << "if (!errorFound)"; mJava->startBlock(); mJava->indent() << "errorFound = true;\n"; for (auto p : mAllInputsAndOutputs) { if (p->isOutParameter) { writeJavaAppendOutputToMessage(*p, "", "[i * " + p->vectorWidth + " + j]", verifierValidates); } else { writeJavaAppendInputToMessage(*p, "args." + p->variableName); } } if (verifierValidates) { mJava->indent() << "message.append(errorMessage);\n"; } mJava->indent() << "message.append(\"Errors at\");\n"; mJava->endBlock(); mJava->indent() << "message.append(\" [\");\n"; mJava->indent() << "message.append(Integer.toString(i));\n"; mJava->indent() << "message.append(\", \");\n"; mJava->indent() << "message.append(Integer.toString(j));\n"; mJava->indent() << "message.append(\"]\");\n"; mJava->endBlock(); mJava->endBlock(); mJava->endBlock(); mJava->indent() << "assertFalse(\"Incorrect output for " << mJavaCheckMethodName << "\" +\n"; mJava->indentPlus() << "(relaxed ? \"_relaxed\" : \"\") + \":\\n\" + message.toString(), errorFound);\n"; mJava->endBlock(); *mJava << "\n"; } void PermutationWriter::writeJavaVerifyVectorMethod() const { writeJavaVerifyMethodHeader(); mJava->startBlock(); for (auto p : mAllInputsAndOutputs) { writeJavaArrayInitialization(*p); } mJava->indent() << "StringBuilder message = new StringBuilder();\n"; mJava->indent() << "boolean errorFound = false;\n"; mJava->indent() << "for (int i = 0; i < INPUTSIZE; i++)"; mJava->startBlock(); mJava->indent() << mJavaArgumentsNClassName << " args = new " << mJavaArgumentsNClassName << "();\n"; mJava->indent() << "// Create the appropriate sized arrays in args\n"; for (auto p : mAllInputsAndOutputs) { if (p->mVectorSize != "1") { string type = p->javaBaseType; if (p->isOutParameter && p->isFloatType) { type = "Target.Floaty"; } mJava->indent() << "args." << p->variableName << " = new " << type << "[" << p->mVectorSize << "];\n"; if (p->isFloat16Parameter() && !p->isOutParameter) { mJava->indent() << "args." << p->variableName << "Double = new double[" << p->mVectorSize << "];\n"; } } } mJava->indent() << "// Fill args with the input values\n"; for (auto p : mAllInputsAndOutputs) { if (!p->isOutParameter) { if (p->mVectorSize == "1") { mJava->indent() << "args." << p->variableName << " = " << p->javaArrayName << "[i]" << ";\n"; // Convert the Float16 parameter to double and store it in the appropriate field in // the Arguments class. if (p->isFloat16Parameter()) { mJava->indent() << "args." << p->doubleVariableName << " = " << "Float16Utils.convertFloat16ToDouble(args." << p->variableName << ");\n"; } } else { mJava->indent() << "for (int j = 0; j < " << p->mVectorSize << " ; j++)"; mJava->startBlock(); mJava->indent() << "args." << p->variableName << "[j] = " << p->javaArrayName << "[i * " << p->vectorWidth << " + j]" << ";\n"; // Convert the Float16 parameter to double and store it in the appropriate field in // the Arguments class. if (p->isFloat16Parameter()) { mJava->indent() << "args." << p->doubleVariableName << "[j] = " << "Float16Utils.convertFloat16ToDouble(args." << p->variableName << "[j]);\n"; } mJava->endBlock(); } } } writeJavaCreateTarget(); mJava->indent() << "CoreMathVerifier." << mJavaVerifierComputeMethodName << "(args, target);\n\n"; mJava->indent() << "// Compare the expected outputs to the actual values returned by RS.\n"; mJava->indent() << "boolean valid = true;\n"; for (auto p : mAllInputsAndOutputs) { if (p->isOutParameter) { writeJavaVectorComparison(*p); } } mJava->indent() << "if (!valid)"; mJava->startBlock(); mJava->indent() << "if (!errorFound)"; mJava->startBlock(); mJava->indent() << "errorFound = true;\n"; for (auto p : mAllInputsAndOutputs) { if (p->isOutParameter) { writeJavaAppendVectorOutputToMessage(*p); } else { writeJavaAppendVectorInputToMessage(*p); } } mJava->indent() << "message.append(\"Errors at\");\n"; mJava->endBlock(); mJava->indent() << "message.append(\" [\");\n"; mJava->indent() << "message.append(Integer.toString(i));\n"; mJava->indent() << "message.append(\"]\");\n"; mJava->endBlock(); mJava->endBlock(); mJava->indent() << "assertFalse(\"Incorrect output for " << mJavaCheckMethodName << "\" +\n"; mJava->indentPlus() << "(relaxed ? \"_relaxed\" : \"\") + \":\\n\" + message.toString(), errorFound);\n"; mJava->endBlock(); *mJava << "\n"; } void PermutationWriter::writeJavaCreateTarget() const { string name = mPermutation.getName(); const char* functionType = "NORMAL"; size_t end = name.find('_'); if (end != string::npos) { if (name.compare(0, end, "native") == 0) { functionType = "NATIVE"; } else if (name.compare(0, end, "half") == 0) { functionType = "HALF"; } else if (name.compare(0, end, "fast") == 0) { functionType = "FAST"; } } string floatType = mReturnParam->specType; const char* precisionStr = ""; if (floatType.compare("f16") == 0) { precisionStr = "HALF"; } else if (floatType.compare("f32") == 0) { precisionStr = "FLOAT"; } else if (floatType.compare("f64") == 0) { precisionStr = "DOUBLE"; } else { cerr << "Error. Unreachable. Return type is not floating point\n"; } mJava->indent() << "Target target = new Target(Target.FunctionType." << functionType << ", Target.ReturnType." << precisionStr << ", relaxed);\n"; } void PermutationWriter::writeJavaVerifyMethodHeader() const { mJava->indent() << "private void " << mJavaVerifyMethodName << "("; for (auto p : mAllInputsAndOutputs) { *mJava << "Allocation " << p->javaAllocName << ", "; } *mJava << "boolean relaxed)"; } void PermutationWriter::writeJavaArrayInitialization(const ParameterDefinition& p) const { mJava->indent() << p.javaBaseType << "[] " << p.javaArrayName << " = new " << p.javaBaseType << "[INPUTSIZE * " << p.vectorWidth << "];\n"; /* For basic types, populate the array with values, to help understand failures. We have had * bugs where the output buffer was all 0. We were not sure if there was a failed copy or * the GPU driver was copying zeroes. */ if (p.typeIndex >= 0) { mJava->indent() << "Arrays.fill(" << p.javaArrayName << ", (" << TYPES[p.typeIndex].javaType << ") 42);\n"; } mJava->indent() << p.javaAllocName << ".copyTo(" << p.javaArrayName << ");\n"; } void PermutationWriter::writeJavaTestAndSetValid(const ParameterDefinition& p, const string& argsIndex, const string& actualIndex) const { writeJavaTestOneValue(p, argsIndex, actualIndex); mJava->startBlock(); mJava->indent() << "valid = false;\n"; mJava->endBlock(); } void PermutationWriter::writeJavaTestOneValue(const ParameterDefinition& p, const string& argsIndex, const string& actualIndex) const { string actualOut; if (p.isFloat16Parameter()) { // For Float16 values, the output needs to be converted to Double. actualOut = "Float16Utils.convertFloat16ToDouble(" + p.javaArrayName + actualIndex + ")"; } else { actualOut = p.javaArrayName + actualIndex; } mJava->indent() << "if ("; if (p.isFloatType) { *mJava << "!args." << p.variableName << argsIndex << ".couldBe(" << actualOut; const string s = mPermutation.getPrecisionLimit(); if (!s.empty()) { *mJava << ", " << s; } *mJava << ")"; } else { *mJava << "args." << p.variableName << argsIndex << " != " << p.javaArrayName << actualIndex; } if (p.undefinedIfOutIsNan && mReturnParam) { *mJava << " && !args." << mReturnParam->variableName << argsIndex << ".isNaN()"; } *mJava << ")"; } void PermutationWriter::writeJavaVectorComparison(const ParameterDefinition& p) const { if (p.mVectorSize == "1") { writeJavaTestAndSetValid(p, "", "[i]"); } else { mJava->indent() << "for (int j = 0; j < " << p.mVectorSize << " ; j++)"; mJava->startBlock(); writeJavaTestAndSetValid(p, "[j]", "[i * " + p.vectorWidth + " + j]"); mJava->endBlock(); } } void PermutationWriter::writeJavaAppendOutputToMessage(const ParameterDefinition& p, const string& argsIndex, const string& actualIndex, bool verifierValidates) const { if (verifierValidates) { mJava->indent() << "message.append(\"Output " << p.variableName << ": \");\n"; mJava->indent() << "appendVariableToMessage(message, args." << p.variableName << argsIndex << ");\n"; writeJavaAppendNewLineToMessage(); if (p.isFloat16Parameter()) { writeJavaAppendNewLineToMessage(); mJava->indent() << "message.append(\"Output " << p.variableName << " (in double): \");\n"; mJava->indent() << "appendVariableToMessage(message, args." << p.doubleVariableName << ");\n"; writeJavaAppendNewLineToMessage(); } } else { mJava->indent() << "message.append(\"Expected output " << p.variableName << ": \");\n"; mJava->indent() << "appendVariableToMessage(message, args." << p.variableName << argsIndex << ");\n"; writeJavaAppendNewLineToMessage(); mJava->indent() << "message.append(\"Actual output " << p.variableName << ": \");\n"; mJava->indent() << "appendVariableToMessage(message, " << p.javaArrayName << actualIndex << ");\n"; if (p.isFloat16Parameter()) { writeJavaAppendNewLineToMessage(); mJava->indent() << "message.append(\"Actual output " << p.variableName << " (in double): \");\n"; mJava->indent() << "appendVariableToMessage(message, Float16Utils.convertFloat16ToDouble(" << p.javaArrayName << actualIndex << "));\n"; } writeJavaTestOneValue(p, argsIndex, actualIndex); mJava->startBlock(); mJava->indent() << "message.append(\" FAIL\");\n"; mJava->endBlock(); writeJavaAppendNewLineToMessage(); } } void PermutationWriter::writeJavaAppendInputToMessage(const ParameterDefinition& p, const string& actual) const { mJava->indent() << "message.append(\"Input " << p.variableName << ": \");\n"; mJava->indent() << "appendVariableToMessage(message, " << actual << ");\n"; writeJavaAppendNewLineToMessage(); } void PermutationWriter::writeJavaAppendNewLineToMessage() const { mJava->indent() << "message.append(\"\\n\");\n"; } void PermutationWriter::writeJavaAppendVectorInputToMessage(const ParameterDefinition& p) const { if (p.mVectorSize == "1") { writeJavaAppendInputToMessage(p, p.javaArrayName + "[i]"); } else { mJava->indent() << "for (int j = 0; j < " << p.mVectorSize << " ; j++)"; mJava->startBlock(); writeJavaAppendInputToMessage(p, p.javaArrayName + "[i * " + p.vectorWidth + " + j]"); mJava->endBlock(); } } void PermutationWriter::writeJavaAppendVectorOutputToMessage(const ParameterDefinition& p) const { if (p.mVectorSize == "1") { writeJavaAppendOutputToMessage(p, "", "[i]", false); } else { mJava->indent() << "for (int j = 0; j < " << p.mVectorSize << " ; j++)"; mJava->startBlock(); writeJavaAppendOutputToMessage(p, "[j]", "[i * " + p.vectorWidth + " + j]", false); mJava->endBlock(); } } void PermutationWriter::writeJavaCallToRs(bool relaxed, bool generateCallToVerifier) const { string script = "script"; if (relaxed) { script += "Relaxed"; } mJava->indent() << "try"; mJava->startBlock(); for (auto p : mAllInputsAndOutputs) { if (p->isOutParameter) { writeJavaOutputAllocationDefinition(*p); } } for (auto p : mPermutation.getParams()) { if (p != mFirstInputParam) { mJava->indent() << script << ".set_" << p->rsAllocName << "(" << p->javaAllocName << ");\n"; } } mJava->indent() << script << ".forEach_" << mRsKernelName << "("; bool needComma = false; if (mFirstInputParam) { *mJava << mFirstInputParam->javaAllocName; needComma = true; } if (mReturnParam) { if (needComma) { *mJava << ", "; } *mJava << mReturnParam->variableName << ");\n"; } if (generateCallToVerifier) { mJava->indent() << mJavaVerifyMethodName << "("; for (auto p : mAllInputsAndOutputs) { *mJava << p->variableName << ", "; } if (relaxed) { *mJava << "true"; } else { *mJava << "false"; } *mJava << ");\n"; } mJava->decreaseIndent(); mJava->indent() << "} catch (Exception e) {\n"; mJava->increaseIndent(); mJava->indent() << "throw new RSRuntimeException(\"RenderScript. Can't invoke forEach_" << mRsKernelName << ": \" + e.toString());\n"; mJava->endBlock(); } /* Write the section of the .rs file for this permutation. * * We communicate the extra input and output parameters via global allocations. * For example, if we have a function that takes three arguments, two for input * and one for output: * * start: * name: gamn * ret: float3 * arg: float3 a * arg: int b * arg: float3 *c * end: * * We'll produce: * * rs_allocation gAllocInB; * rs_allocation gAllocOutC; * * float3 __attribute__((kernel)) test_gamn_float3_int_float3(float3 inA, unsigned int x) { * int inB; * float3 outC; * float2 out; * inB = rsGetElementAt_int(gAllocInB, x); * out = gamn(a, in_b, &outC); * rsSetElementAt_float4(gAllocOutC, &outC, x); * return out; * } * * We avoid re-using x and y from the definition because these have reserved * meanings in a .rs file. */ void PermutationWriter::writeRsSection(set<string>* rsAllocationsGenerated) const { // Write the allocation declarations we'll need. for (auto p : mPermutation.getParams()) { // Don't need allocation for one input and one return value. if (p != mFirstInputParam) { writeRsAllocationDefinition(*p, rsAllocationsGenerated); } } *mRs << "\n"; // Write the function header. if (mReturnParam) { *mRs << mReturnParam->rsType; } else { *mRs << "void"; } *mRs << " __attribute__((kernel)) " << mRsKernelName; *mRs << "("; bool needComma = false; if (mFirstInputParam) { *mRs << mFirstInputParam->rsType << " " << mFirstInputParam->variableName; needComma = true; } if (mPermutation.getOutputCount() > 1 || mPermutation.getInputCount() > 1) { if (needComma) { *mRs << ", "; } *mRs << "unsigned int x"; } *mRs << ")"; mRs->startBlock(); // Write the local variable declarations and initializations. for (auto p : mPermutation.getParams()) { if (p == mFirstInputParam) { continue; } mRs->indent() << p->rsType << " " << p->variableName; if (p->isOutParameter) { *mRs << " = 0;\n"; } else { *mRs << " = rsGetElementAt_" << p->rsType << "(" << p->rsAllocName << ", x);\n"; } } // Write the function call. if (mReturnParam) { if (mPermutation.getOutputCount() > 1) { mRs->indent() << mReturnParam->rsType << " " << mReturnParam->variableName << " = "; } else { mRs->indent() << "return "; } } *mRs << mPermutation.getName() << "("; needComma = false; for (auto p : mPermutation.getParams()) { if (needComma) { *mRs << ", "; } if (p->isOutParameter) { *mRs << "&"; } *mRs << p->variableName; needComma = true; } *mRs << ");\n"; if (mPermutation.getOutputCount() > 1) { // Write setting the extra out parameters into the allocations. for (auto p : mPermutation.getParams()) { if (p->isOutParameter) { mRs->indent() << "rsSetElementAt_" << p->rsType << "(" << p->rsAllocName << ", "; // Check if we need to use '&' for this type of argument. char lastChar = p->variableName.back(); if (lastChar >= '0' && lastChar <= '9') { *mRs << "&"; } *mRs << p->variableName << ", x);\n"; } } if (mReturnParam) { mRs->indent() << "return " << mReturnParam->variableName << ";\n"; } } mRs->endBlock(); } void PermutationWriter::writeRsAllocationDefinition(const ParameterDefinition& param, set<string>* rsAllocationsGenerated) const { if (!testAndSet(param.rsAllocName, rsAllocationsGenerated)) { *mRs << "rs_allocation " << param.rsAllocName << ";\n"; } } // Open the mJavaFile and writes the header. static bool startJavaFile(GeneratedFile* file, const Function& function, const string& directory, const string& testName, const string& relaxedTestName) { const string fileName = testName + ".java"; if (!file->start(directory, fileName)) { return false; } file->writeNotices(); *file << "package android.renderscript.cts;\n\n"; *file << "import android.renderscript.Allocation;\n"; *file << "import android.renderscript.RSRuntimeException;\n"; *file << "import android.renderscript.Element;\n"; *file << "import android.renderscript.cts.Target;\n\n"; *file << "import java.util.Arrays;\n\n"; *file << "public class " << testName << " extends RSBaseCompute"; file->startBlock(); // The corresponding endBlock() is in finishJavaFile() *file << "\n"; file->indent() << "private ScriptC_" << testName << " script;\n"; file->indent() << "private ScriptC_" << relaxedTestName << " scriptRelaxed;\n\n"; file->indent() << "@Override\n"; file->indent() << "protected void setUp() throws Exception"; file->startBlock(); file->indent() << "super.setUp();\n"; file->indent() << "script = new ScriptC_" << testName << "(mRS);\n"; file->indent() << "scriptRelaxed = new ScriptC_" << relaxedTestName << "(mRS);\n"; file->endBlock(); *file << "\n"; return true; } // Write the test method that calls all the generated Check methods. static void finishJavaFile(GeneratedFile* file, const Function& function, const vector<string>& javaCheckMethods) { file->indent() << "public void test" << function.getCapitalizedName() << "()"; file->startBlock(); for (auto m : javaCheckMethods) { file->indent() << m << "();\n"; } file->endBlock(); file->endBlock(); } // Open the script file and write its header. static bool startRsFile(GeneratedFile* file, const Function& function, const string& directory, const string& testName) { string fileName = testName + ".rs"; if (!file->start(directory, fileName)) { return false; } file->writeNotices(); *file << "#pragma version(1)\n"; *file << "#pragma rs java_package_name(android.renderscript.cts)\n\n"; return true; } // Write the entire *Relaxed.rs test file, as it only depends on the name. static bool writeRelaxedRsFile(const Function& function, const string& directory, const string& testName, const string& relaxedTestName) { string name = relaxedTestName + ".rs"; GeneratedFile file; if (!file.start(directory, name)) { return false; } file.writeNotices(); file << "#include \"" << testName << ".rs\"\n"; file << "#pragma rs_fp_relaxed\n"; file.close(); return true; } /* Write the .java and the two .rs test files. versionOfTestFiles is used to restrict which API * to test. */ static bool writeTestFilesForFunction(const Function& function, const string& directory, unsigned int versionOfTestFiles) { // Avoid creating empty files if we're not testing this function. if (!needTestFiles(function, versionOfTestFiles)) { return true; } const string testName = "Test" + function.getCapitalizedName(); const string relaxedTestName = testName + "Relaxed"; if (!writeRelaxedRsFile(function, directory, testName, relaxedTestName)) { return false; } GeneratedFile rsFile; // The Renderscript test file we're generating. GeneratedFile javaFile; // The Jave test file we're generating. if (!startRsFile(&rsFile, function, directory, testName)) { return false; } if (!startJavaFile(&javaFile, function, directory, testName, relaxedTestName)) { return false; } /* We keep track of the allocations generated in the .rs file and the argument classes defined * in the Java file, as we share these between the functions created for each specification. */ set<string> rsAllocationsGenerated; set<string> javaGeneratedArgumentClasses; // Lines of Java code to invoke the check methods. vector<string> javaCheckMethods; for (auto spec : function.getSpecifications()) { if (spec->hasTests(versionOfTestFiles)) { for (auto permutation : spec->getPermutations()) { PermutationWriter w(*permutation, &rsFile, &javaFile); w.writeRsSection(&rsAllocationsGenerated); w.writeJavaSection(&javaGeneratedArgumentClasses); // Store the check method to be called. javaCheckMethods.push_back(w.getJavaCheckMethodName()); } } } finishJavaFile(&javaFile, function, javaCheckMethods); // There's no work to wrap-up in the .rs file. rsFile.close(); javaFile.close(); return true; } bool generateTestFiles(const string& directory, unsigned int versionOfTestFiles) { bool success = true; for (auto f : systemSpecification.getFunctions()) { if (!writeTestFilesForFunction(*f.second, directory, versionOfTestFiles)) { success = false; } } return success; }