// // Copyright 2002 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // // Build the intermediate representation. // #include #include #include #include #include #include #include "common/mathutil.h" #include "common/matrix_utils.h" #include "common/utilities.h" #include "compiler/translator/Diagnostics.h" #include "compiler/translator/ImmutableString.h" #include "compiler/translator/IntermNode.h" #include "compiler/translator/SymbolTable.h" #include "compiler/translator/util.h" namespace sh { namespace { const float kPi = 3.14159265358979323846f; const float kDegreesToRadiansMultiplier = kPi / 180.0f; const float kRadiansToDegreesMultiplier = 180.0f / kPi; TPrecision GetHigherPrecision(TPrecision left, TPrecision right) { return left > right ? left : right; } TConstantUnion *Vectorize(const TConstantUnion &constant, size_t size) { TConstantUnion *constUnion = new TConstantUnion[size]; for (size_t i = 0; i < size; ++i) constUnion[i] = constant; return constUnion; } void UndefinedConstantFoldingError(const TSourceLoc &loc, const TFunction *function, TBasicType basicType, TDiagnostics *diagnostics, TConstantUnion *result) { diagnostics->warning(loc, "operation result is undefined for the values passed in", function->name().data()); switch (basicType) { case EbtFloat: result->setFConst(0.0f); break; case EbtInt: result->setIConst(0); break; case EbtUInt: result->setUConst(0u); break; case EbtBool: result->setBConst(false); break; default: break; } } float VectorLength(const TConstantUnion *paramArray, size_t paramArraySize) { float result = 0.0f; for (size_t i = 0; i < paramArraySize; i++) { float f = paramArray[i].getFConst(); result += f * f; } return sqrtf(result); } float VectorDotProduct(const TConstantUnion *paramArray1, const TConstantUnion *paramArray2, size_t paramArraySize) { float result = 0.0f; for (size_t i = 0; i < paramArraySize; i++) result += paramArray1[i].getFConst() * paramArray2[i].getFConst(); return result; } TIntermTyped *CreateFoldedNode(const TConstantUnion *constArray, const TIntermTyped *originalNode) { ASSERT(constArray != nullptr); // Note that we inherit whatever qualifier the folded node had. Nodes may be constant folded // without being qualified as constant. TIntermTyped *folded = new TIntermConstantUnion(constArray, originalNode->getType()); folded->setLine(originalNode->getLine()); return folded; } angle::Matrix GetMatrix(const TConstantUnion *paramArray, const unsigned int rows, const unsigned int cols) { std::vector elements; for (size_t i = 0; i < rows * cols; i++) elements.push_back(paramArray[i].getFConst()); // Transpose is used since the Matrix constructor expects arguments in row-major order, // whereas the paramArray is in column-major order. Rows/cols parameters are also flipped below // so that the created matrix will have the expected dimensions after the transpose. return angle::Matrix(elements, cols, rows).transpose(); } angle::Matrix GetMatrix(const TConstantUnion *paramArray, const unsigned int size) { std::vector elements; for (size_t i = 0; i < size * size; i++) elements.push_back(paramArray[i].getFConst()); // Transpose is used since the Matrix constructor expects arguments in row-major order, // whereas the paramArray is in column-major order. return angle::Matrix(elements, size).transpose(); } void SetUnionArrayFromMatrix(const angle::Matrix &m, TConstantUnion *resultArray) { // Transpose is used since the input Matrix is in row-major order, // whereas the actual result should be in column-major order. angle::Matrix result = m.transpose(); std::vector resultElements = result.elements(); for (size_t i = 0; i < resultElements.size(); i++) resultArray[i].setFConst(resultElements[i]); } bool CanFoldAggregateBuiltInOp(TOperator op) { switch (op) { case EOpAtan: case EOpPow: case EOpMod: case EOpMin: case EOpMax: case EOpClamp: case EOpMix: case EOpStep: case EOpSmoothstep: case EOpFma: case EOpLdexp: case EOpMatrixCompMult: case EOpOuterProduct: case EOpEqualComponentWise: case EOpNotEqualComponentWise: case EOpLessThanComponentWise: case EOpLessThanEqualComponentWise: case EOpGreaterThanComponentWise: case EOpGreaterThanEqualComponentWise: case EOpDistance: case EOpDot: case EOpCross: case EOpFaceforward: case EOpReflect: case EOpRefract: case EOpBitfieldExtract: case EOpBitfieldInsert: case EOpDFdx: case EOpDFdy: case EOpFwidth: return true; default: return false; } } void PropagatePrecisionIfApplicable(TIntermTyped *node, TPrecision precision) { if (precision == EbpUndefined || node->getPrecision() != EbpUndefined) { return; } if (IsPrecisionApplicableToType(node->getBasicType())) { node->propagatePrecision(precision); } } } // namespace //////////////////////////////////////////////////////////////// // // Member functions of the nodes used for building the tree. // //////////////////////////////////////////////////////////////// TIntermExpression::TIntermExpression(const TType &t) : TIntermTyped(), mType(t) {} #define REPLACE_IF_IS(node, type, original, replacement) \ do \ { \ if (node == original) \ { \ node = static_cast(replacement); \ return true; \ } \ } while (0) size_t TIntermSymbol::getChildCount() const { return 0; } TIntermNode *TIntermSymbol::getChildNode(size_t index) const { UNREACHABLE(); return nullptr; } size_t TIntermConstantUnion::getChildCount() const { return 0; } TIntermNode *TIntermConstantUnion::getChildNode(size_t index) const { UNREACHABLE(); return nullptr; } size_t TIntermLoop::getChildCount() const { return (mInit ? 1 : 0) + (mCond ? 1 : 0) + (mExpr ? 1 : 0) + (mBody ? 1 : 0); } TIntermNode *TIntermLoop::getChildNode(size_t index) const { TIntermNode *children[4]; unsigned int childIndex = 0; if (mInit) { children[childIndex] = mInit; ++childIndex; } if (mCond) { children[childIndex] = mCond; ++childIndex; } if (mExpr) { children[childIndex] = mExpr; ++childIndex; } if (mBody) { children[childIndex] = mBody; ++childIndex; } ASSERT(index < childIndex); return children[index]; } bool TIntermLoop::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { ASSERT(original != nullptr); // This risks replacing multiple children. REPLACE_IF_IS(mInit, TIntermNode, original, replacement); REPLACE_IF_IS(mCond, TIntermTyped, original, replacement); REPLACE_IF_IS(mExpr, TIntermTyped, original, replacement); REPLACE_IF_IS(mBody, TIntermBlock, original, replacement); return false; } TIntermBranch::TIntermBranch(const TIntermBranch &node) : TIntermBranch(node.mFlowOp, node.mExpression ? node.mExpression->deepCopy() : nullptr) {} size_t TIntermBranch::getChildCount() const { return (mExpression ? 1 : 0); } TIntermNode *TIntermBranch::getChildNode(size_t index) const { ASSERT(mExpression); ASSERT(index == 0); return mExpression; } bool TIntermBranch::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mExpression, TIntermTyped, original, replacement); return false; } size_t TIntermSwizzle::getChildCount() const { return 1; } TIntermNode *TIntermSwizzle::getChildNode(size_t index) const { ASSERT(mOperand); ASSERT(index == 0); return mOperand; } bool TIntermSwizzle::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { ASSERT(original->getAsTyped()->getType() == replacement->getAsTyped()->getType()); REPLACE_IF_IS(mOperand, TIntermTyped, original, replacement); return false; } size_t TIntermBinary::getChildCount() const { return 2; } TIntermNode *TIntermBinary::getChildNode(size_t index) const { ASSERT(index < 2); if (index == 0) { return mLeft; } return mRight; } bool TIntermBinary::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mLeft, TIntermTyped, original, replacement); REPLACE_IF_IS(mRight, TIntermTyped, original, replacement); return false; } size_t TIntermUnary::getChildCount() const { return 1; } TIntermNode *TIntermUnary::getChildNode(size_t index) const { ASSERT(mOperand); ASSERT(index == 0); return mOperand; } bool TIntermUnary::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { ASSERT(original->getAsTyped()->getType() == replacement->getAsTyped()->getType()); REPLACE_IF_IS(mOperand, TIntermTyped, original, replacement); return false; } size_t TIntermGlobalQualifierDeclaration::getChildCount() const { return 1; } TIntermNode *TIntermGlobalQualifierDeclaration::getChildNode(size_t index) const { ASSERT(mSymbol); ASSERT(index == 0); return mSymbol; } bool TIntermGlobalQualifierDeclaration::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mSymbol, TIntermSymbol, original, replacement); return false; } size_t TIntermFunctionDefinition::getChildCount() const { return 2; } TIntermNode *TIntermFunctionDefinition::getChildNode(size_t index) const { ASSERT(index < 2); if (index == 0) { return mPrototype; } return mBody; } bool TIntermFunctionDefinition::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mPrototype, TIntermFunctionPrototype, original, replacement); REPLACE_IF_IS(mBody, TIntermBlock, original, replacement); return false; } size_t TIntermAggregate::getChildCount() const { return mArguments.size(); } TIntermNode *TIntermAggregate::getChildNode(size_t index) const { return mArguments[index]; } bool TIntermAggregate::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { return replaceChildNodeInternal(original, replacement); } TIntermBlock::TIntermBlock(const TIntermBlock &node) { for (TIntermNode *intermNode : node.mStatements) { mStatements.push_back(intermNode->deepCopy()); } ASSERT(!node.mIsTreeRoot); mIsTreeRoot = false; } TIntermBlock::TIntermBlock(std::initializer_list stmts) { for (TIntermNode *stmt : stmts) { appendStatement(stmt); } } size_t TIntermBlock::getChildCount() const { return mStatements.size(); } TIntermNode *TIntermBlock::getChildNode(size_t index) const { return mStatements[index]; } bool TIntermBlock::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { return replaceChildNodeInternal(original, replacement); } void TIntermBlock::replaceAllChildren(const TIntermSequence &newStatements) { mStatements.clear(); mStatements.insert(mStatements.begin(), newStatements.begin(), newStatements.end()); } size_t TIntermFunctionPrototype::getChildCount() const { return 0; } TIntermNode *TIntermFunctionPrototype::getChildNode(size_t index) const { UNREACHABLE(); return nullptr; } bool TIntermFunctionPrototype::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { return false; } TIntermDeclaration::TIntermDeclaration(const TVariable *var, TIntermTyped *initExpr) { if (initExpr) { appendDeclarator( new TIntermBinary(TOperator::EOpInitialize, new TIntermSymbol(var), initExpr)); } else { appendDeclarator(new TIntermSymbol(var)); } } TIntermDeclaration::TIntermDeclaration(std::initializer_list declarators) : TIntermDeclaration() { for (const TVariable *d : declarators) { appendDeclarator(new TIntermSymbol(d)); } } TIntermDeclaration::TIntermDeclaration(std::initializer_list declarators) : TIntermDeclaration() { for (TIntermTyped *d : declarators) { appendDeclarator(d); } } size_t TIntermDeclaration::getChildCount() const { return mDeclarators.size(); } TIntermNode *TIntermDeclaration::getChildNode(size_t index) const { return mDeclarators[index]; } bool TIntermDeclaration::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { return replaceChildNodeInternal(original, replacement); } TIntermDeclaration::TIntermDeclaration(const TIntermDeclaration &node) { for (TIntermNode *intermNode : node.mDeclarators) { mDeclarators.push_back(intermNode->deepCopy()); } } bool TIntermAggregateBase::replaceChildNodeInternal(TIntermNode *original, TIntermNode *replacement) { for (size_t ii = 0; ii < getSequence()->size(); ++ii) { REPLACE_IF_IS((*getSequence())[ii], TIntermNode, original, replacement); } return false; } bool TIntermAggregateBase::replaceChildNodeWithMultiple(TIntermNode *original, const TIntermSequence &replacements) { for (auto it = getSequence()->begin(); it < getSequence()->end(); ++it) { if (*it == original) { it = getSequence()->erase(it); getSequence()->insert(it, replacements.begin(), replacements.end()); return true; } } return false; } bool TIntermAggregateBase::insertChildNodes(TIntermSequence::size_type position, const TIntermSequence &insertions) { if (position > getSequence()->size()) { return false; } auto it = getSequence()->begin() + position; getSequence()->insert(it, insertions.begin(), insertions.end()); return true; } TIntermSymbol::TIntermSymbol(const TVariable *variable) : TIntermTyped(), mVariable(variable) {} bool TIntermSymbol::hasConstantValue() const { return variable().getConstPointer() != nullptr; } const TConstantUnion *TIntermSymbol::getConstantValue() const { return variable().getConstPointer(); } const TSymbolUniqueId &TIntermSymbol::uniqueId() const { return mVariable->uniqueId(); } ImmutableString TIntermSymbol::getName() const { return mVariable->name(); } const TType &TIntermSymbol::getType() const { return mVariable->getType(); } void TIntermSymbol::propagatePrecision(TPrecision precision) { // Every declared variable should already have a precision. Some built-ins don't have a defined // precision. This is not asserted however: // // - A shader with no precision specified either globally or on a variable will fail with a // compilation error later on. // - Transformations declaring variables without precision will be caught by AST validation. } TIntermAggregate *TIntermAggregate::CreateFunctionCall(const TFunction &func, TIntermSequence *arguments) { return new TIntermAggregate(&func, func.getReturnType(), EOpCallFunctionInAST, arguments); } TIntermAggregate *TIntermAggregate::CreateRawFunctionCall(const TFunction &func, TIntermSequence *arguments) { return new TIntermAggregate(&func, func.getReturnType(), EOpCallInternalRawFunction, arguments); } TIntermAggregate *TIntermAggregate::CreateBuiltInFunctionCall(const TFunction &func, TIntermSequence *arguments) { // Every built-in function should have an op. ASSERT(func.getBuiltInOp() != EOpNull); return new TIntermAggregate(&func, func.getReturnType(), func.getBuiltInOp(), arguments); } TIntermAggregate *TIntermAggregate::CreateConstructor(const TType &type, TIntermSequence *arguments) { return new TIntermAggregate(nullptr, type, EOpConstruct, arguments); } TIntermAggregate *TIntermAggregate::CreateConstructor( const TType &type, const std::initializer_list &arguments) { TIntermSequence argSequence(arguments); return CreateConstructor(type, &argSequence); } TIntermAggregate::TIntermAggregate(const TFunction *func, const TType &type, TOperator op, TIntermSequence *arguments) : TIntermOperator(op, type), mUseEmulatedFunction(false), mFunction(func) { if (arguments != nullptr) { mArguments.swap(*arguments); } ASSERT(mFunction == nullptr || mFunction->symbolType() != SymbolType::Empty); setPrecisionAndQualifier(); } void TIntermAggregate::setPrecisionAndQualifier() { mType.setQualifier(EvqTemporary); if ((!BuiltInGroup::IsBuiltIn(mOp) && !isFunctionCall()) || BuiltInGroup::IsMath(mOp)) { if (areChildrenConstQualified()) { mType.setQualifier(EvqConst); } } propagatePrecision(derivePrecision()); } bool TIntermAggregate::areChildrenConstQualified() { for (TIntermNode *arg : mArguments) { TIntermTyped *typedArg = arg->getAsTyped(); if (typedArg && typedArg->getQualifier() != EvqConst) { return false; } } return true; } // Derive precision from children nodes TPrecision TIntermAggregate::derivePrecision() const { if (getBasicType() == EbtBool || getBasicType() == EbtVoid || getBasicType() == EbtStruct) { return EbpUndefined; } // For AST function calls, take the qualifier from the declared one. if (isFunctionCall()) { return mType.getPrecision(); } // Some built-ins explicitly specify their precision. switch (mOp) { case EOpBitfieldExtract: return mArguments[0]->getAsTyped()->getPrecision(); case EOpBitfieldInsert: return GetHigherPrecision(mArguments[0]->getAsTyped()->getPrecision(), mArguments[1]->getAsTyped()->getPrecision()); case EOpTextureSize: case EOpImageSize: case EOpUaddCarry: case EOpUsubBorrow: case EOpUmulExtended: case EOpImulExtended: case EOpFrexp: case EOpLdexp: return EbpHigh; default: break; } // The rest of the math operations and constructors get their precision from their arguments. if (BuiltInGroup::IsMath(mOp) || mOp == EOpConstruct) { TPrecision precision = EbpUndefined; for (TIntermNode *argument : mArguments) { precision = GetHigherPrecision(argument->getAsTyped()->getPrecision(), precision); } return precision; } // Atomic operations return highp. if (BuiltInGroup::IsImageAtomic(mOp) || BuiltInGroup::IsAtomicCounter(mOp) || BuiltInGroup::IsAtomicMemory(mOp)) { return EbpHigh; } // Texture functions return the same precision as that of the sampler (textureSize returns // highp, but that's handled above). imageLoad similar takes the precision of the image. The // same is true for dFd*, interpolateAt* and subpassLoad operations. if (BuiltInGroup::IsTexture(mOp) || BuiltInGroup::IsImageLoad(mOp) || BuiltInGroup::IsDerivativesFS(mOp) || BuiltInGroup::IsInterpolationFS(mOp) || mOp == EOpSubpassLoad) { return mArguments[0]->getAsTyped()->getPrecision(); } // Every possibility must be explicitly handled, except for desktop-GLSL-specific built-ins // for which precision does't matter. return EbpUndefined; } // Propagate precision to children nodes that don't already have it defined. void TIntermAggregate::propagatePrecision(TPrecision precision) { mType.setPrecision(precision); // For constructors, propagate precision to arguments. if (isConstructor()) { for (TIntermNode *arg : mArguments) { PropagatePrecisionIfApplicable(arg->getAsTyped(), precision); } return; } // For function calls, propagate precision of each parameter to its corresponding argument. if (isFunctionCall()) { for (size_t paramIndex = 0; paramIndex < mFunction->getParamCount(); ++paramIndex) { const TVariable *paramVariable = mFunction->getParam(paramIndex); PropagatePrecisionIfApplicable(mArguments[paramIndex]->getAsTyped(), paramVariable->getType().getPrecision()); } return; } // Some built-ins explicitly specify the precision of their parameters. switch (mOp) { case EOpUaddCarry: case EOpUsubBorrow: case EOpUmulExtended: case EOpImulExtended: PropagatePrecisionIfApplicable(mArguments[0]->getAsTyped(), EbpHigh); PropagatePrecisionIfApplicable(mArguments[1]->getAsTyped(), EbpHigh); break; case EOpFindMSB: case EOpFrexp: case EOpLdexp: PropagatePrecisionIfApplicable(mArguments[0]->getAsTyped(), EbpHigh); break; default: break; } } const char *TIntermAggregate::functionName() const { ASSERT(!isConstructor()); switch (mOp) { case EOpCallInternalRawFunction: case EOpCallFunctionInAST: return mFunction->name().data(); default: if (BuiltInGroup::IsBuiltIn(mOp)) { return mFunction->name().data(); } return GetOperatorString(mOp); } } bool TIntermAggregate::hasConstantValue() const { if (!isConstructor()) { return false; } for (TIntermNode *constructorArg : mArguments) { if (!constructorArg->getAsTyped()->hasConstantValue()) { return false; } } return true; } bool TIntermAggregate::isConstantNullValue() const { if (!isConstructor()) { return false; } for (TIntermNode *constructorArg : mArguments) { if (!constructorArg->getAsTyped()->isConstantNullValue()) { return false; } } return true; } const TConstantUnion *TIntermAggregate::getConstantValue() const { if (!hasConstantValue()) { return nullptr; } ASSERT(isConstructor()); ASSERT(mArguments.size() > 0u); TConstantUnion *constArray = nullptr; if (isArray()) { size_t elementSize = mArguments.front()->getAsTyped()->getType().getObjectSize(); constArray = new TConstantUnion[elementSize * getOutermostArraySize()]; size_t elementOffset = 0u; for (TIntermNode *constructorArg : mArguments) { const TConstantUnion *elementConstArray = constructorArg->getAsTyped()->getConstantValue(); ASSERT(elementConstArray); size_t elementSizeBytes = sizeof(TConstantUnion) * elementSize; memcpy(static_cast(&constArray[elementOffset]), static_cast(elementConstArray), elementSizeBytes); elementOffset += elementSize; } return constArray; } size_t resultSize = getType().getObjectSize(); constArray = new TConstantUnion[resultSize]; TBasicType basicType = getBasicType(); size_t resultIndex = 0u; if (mArguments.size() == 1u) { TIntermNode *argument = mArguments.front(); TIntermTyped *argumentTyped = argument->getAsTyped(); const TConstantUnion *argumentConstantValue = argumentTyped->getConstantValue(); // Check the special case of constructing a matrix diagonal from a single scalar, // or a vector from a single scalar. if (argumentTyped->getType().getObjectSize() == 1u) { if (isMatrix()) { const uint8_t resultCols = getType().getCols(); const uint8_t resultRows = getType().getRows(); for (uint8_t col = 0; col < resultCols; ++col) { for (uint8_t row = 0; row < resultRows; ++row) { if (col == row) { constArray[resultIndex].cast(basicType, argumentConstantValue[0]); } else { constArray[resultIndex].setFConst(0.0f); } ++resultIndex; } } } else { while (resultIndex < resultSize) { constArray[resultIndex].cast(basicType, argumentConstantValue[0]); ++resultIndex; } } ASSERT(resultIndex == resultSize); return constArray; } else if (isMatrix() && argumentTyped->isMatrix()) { // The special case of constructing a matrix from a matrix. const uint8_t argumentCols = argumentTyped->getType().getCols(); const uint8_t argumentRows = argumentTyped->getType().getRows(); const uint8_t resultCols = getType().getCols(); const uint8_t resultRows = getType().getRows(); for (uint8_t col = 0; col < resultCols; ++col) { for (uint8_t row = 0; row < resultRows; ++row) { if (col < argumentCols && row < argumentRows) { constArray[resultIndex].cast( basicType, argumentConstantValue[col * argumentRows + row]); } else if (col == row) { constArray[resultIndex].setFConst(1.0f); } else { constArray[resultIndex].setFConst(0.0f); } ++resultIndex; } } ASSERT(resultIndex == resultSize); return constArray; } } for (TIntermNode *argument : mArguments) { TIntermTyped *argumentTyped = argument->getAsTyped(); size_t argumentSize = argumentTyped->getType().getObjectSize(); const TConstantUnion *argumentConstantValue = argumentTyped->getConstantValue(); for (size_t i = 0u; i < argumentSize; ++i) { if (resultIndex >= resultSize) break; constArray[resultIndex].cast(basicType, argumentConstantValue[i]); ++resultIndex; } } ASSERT(resultIndex == resultSize); return constArray; } bool TIntermAggregate::hasSideEffects() const { if (getQualifier() == EvqConst) { return false; } // If the function itself is known to have a side effect, the expression has a side effect. const bool calledFunctionHasSideEffects = mFunction != nullptr && !mFunction->isKnownToNotHaveSideEffects(); if (calledFunctionHasSideEffects) { return true; } // Otherwise it only has a side effect if one of the arguments does. for (TIntermNode *arg : mArguments) { if (arg->getAsTyped()->hasSideEffects()) { return true; } } return false; } void TIntermBlock::appendStatement(TIntermNode *statement) { // Declaration nodes with no children can appear if it was an empty declaration or if all the // declarators just added constants to the symbol table instead of generating code. We still // need to add the declaration to the AST in that case because it might be relevant to the // validity of switch/case. if (statement != nullptr) { mStatements.push_back(statement); } } void TIntermBlock::insertStatement(size_t insertPosition, TIntermNode *statement) { ASSERT(statement != nullptr); mStatements.insert(mStatements.begin() + insertPosition, statement); } void TIntermDeclaration::appendDeclarator(TIntermTyped *declarator) { ASSERT(declarator != nullptr); ASSERT(declarator->getAsSymbolNode() != nullptr || (declarator->getAsBinaryNode() != nullptr && declarator->getAsBinaryNode()->getOp() == EOpInitialize)); ASSERT(mDeclarators.empty() || declarator->getType().sameNonArrayType(mDeclarators.back()->getAsTyped()->getType())); mDeclarators.push_back(declarator); } size_t TIntermTernary::getChildCount() const { return 3; } TIntermNode *TIntermTernary::getChildNode(size_t index) const { ASSERT(index < 3); if (index == 0) { return mCondition; } if (index == 1) { return mTrueExpression; } return mFalseExpression; } bool TIntermTernary::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mCondition, TIntermTyped, original, replacement); REPLACE_IF_IS(mTrueExpression, TIntermTyped, original, replacement); REPLACE_IF_IS(mFalseExpression, TIntermTyped, original, replacement); return false; } size_t TIntermIfElse::getChildCount() const { return 1 + (mTrueBlock ? 1 : 0) + (mFalseBlock ? 1 : 0); } TIntermNode *TIntermIfElse::getChildNode(size_t index) const { if (index == 0) { return mCondition; } if (mTrueBlock && index == 1) { return mTrueBlock; } return mFalseBlock; } bool TIntermIfElse::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mCondition, TIntermTyped, original, replacement); REPLACE_IF_IS(mTrueBlock, TIntermBlock, original, replacement); REPLACE_IF_IS(mFalseBlock, TIntermBlock, original, replacement); return false; } size_t TIntermSwitch::getChildCount() const { return 2; } TIntermNode *TIntermSwitch::getChildNode(size_t index) const { ASSERT(index < 2); if (index == 0) { return mInit; } return mStatementList; } bool TIntermSwitch::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mInit, TIntermTyped, original, replacement); REPLACE_IF_IS(mStatementList, TIntermBlock, original, replacement); ASSERT(mStatementList); return false; } TIntermCase::TIntermCase(const TIntermCase &node) : TIntermCase(node.mCondition->deepCopy()) {} size_t TIntermCase::getChildCount() const { return (mCondition ? 1 : 0); } TIntermNode *TIntermCase::getChildNode(size_t index) const { ASSERT(index == 0); ASSERT(mCondition); return mCondition; } bool TIntermCase::replaceChildNode(TIntermNode *original, TIntermNode *replacement) { REPLACE_IF_IS(mCondition, TIntermTyped, original, replacement); return false; } TIntermTyped::TIntermTyped() : mIsPrecise(false) {} TIntermTyped::TIntermTyped(const TIntermTyped &node) : TIntermTyped() { // Copy constructor is disallowed for TIntermNode in order to disallow it for subclasses that // don't explicitly allow it, so normal TIntermNode constructor is used to construct the copy. // We need to manually copy any fields of TIntermNode. mLine = node.mLine; // Once deteremined, the tree is not expected to transform. ASSERT(!mIsPrecise); } bool TIntermTyped::hasConstantValue() const { return false; } bool TIntermTyped::isConstantNullValue() const { return false; } const TConstantUnion *TIntermTyped::getConstantValue() const { return nullptr; } TPrecision TIntermTyped::derivePrecision() const { UNREACHABLE(); return EbpUndefined; } void TIntermTyped::propagatePrecision(TPrecision precision) { UNREACHABLE(); } TIntermConstantUnion::TIntermConstantUnion(const TIntermConstantUnion &node) : TIntermExpression(node) { mUnionArrayPointer = node.mUnionArrayPointer; } TIntermFunctionPrototype::TIntermFunctionPrototype(const TFunction *function) : TIntermTyped(), mFunction(function) { ASSERT(mFunction->symbolType() != SymbolType::Empty); } const TType &TIntermFunctionPrototype::getType() const { return mFunction->getReturnType(); } TIntermAggregate::TIntermAggregate(const TIntermAggregate &node) : TIntermOperator(node), mUseEmulatedFunction(node.mUseEmulatedFunction), mFunction(node.mFunction) { for (TIntermNode *arg : node.mArguments) { TIntermTyped *typedArg = arg->getAsTyped(); ASSERT(typedArg != nullptr); TIntermTyped *argCopy = typedArg->deepCopy(); mArguments.push_back(argCopy); } } TIntermAggregate *TIntermAggregate::shallowCopy() const { TIntermSequence copySeq; copySeq.insert(copySeq.begin(), getSequence()->begin(), getSequence()->end()); TIntermAggregate *copyNode = new TIntermAggregate(mFunction, mType, mOp, ©Seq); copyNode->setLine(mLine); return copyNode; } TIntermSwizzle::TIntermSwizzle(const TIntermSwizzle &node) : TIntermExpression(node) { TIntermTyped *operandCopy = node.mOperand->deepCopy(); ASSERT(operandCopy != nullptr); mOperand = operandCopy; mSwizzleOffsets = node.mSwizzleOffsets; mHasFoldedDuplicateOffsets = node.mHasFoldedDuplicateOffsets; } TIntermBinary::TIntermBinary(const TIntermBinary &node) : TIntermOperator(node) { TIntermTyped *leftCopy = node.mLeft->deepCopy(); TIntermTyped *rightCopy = node.mRight->deepCopy(); ASSERT(leftCopy != nullptr && rightCopy != nullptr); mLeft = leftCopy; mRight = rightCopy; } TIntermUnary::TIntermUnary(const TIntermUnary &node) : TIntermOperator(node), mUseEmulatedFunction(node.mUseEmulatedFunction), mFunction(node.mFunction) { TIntermTyped *operandCopy = node.mOperand->deepCopy(); ASSERT(operandCopy != nullptr); mOperand = operandCopy; } TIntermTernary::TIntermTernary(const TIntermTernary &node) : TIntermExpression(node) { TIntermTyped *conditionCopy = node.mCondition->deepCopy(); TIntermTyped *trueCopy = node.mTrueExpression->deepCopy(); TIntermTyped *falseCopy = node.mFalseExpression->deepCopy(); ASSERT(conditionCopy != nullptr && trueCopy != nullptr && falseCopy != nullptr); mCondition = conditionCopy; mTrueExpression = trueCopy; mFalseExpression = falseCopy; } bool TIntermOperator::isAssignment() const { return IsAssignment(mOp); } bool TIntermOperator::isMultiplication() const { switch (mOp) { case EOpMul: case EOpMatrixTimesMatrix: case EOpMatrixTimesVector: case EOpMatrixTimesScalar: case EOpVectorTimesMatrix: case EOpVectorTimesScalar: return true; default: return false; } } bool TIntermOperator::isConstructor() const { return (mOp == EOpConstruct); } bool TIntermOperator::isFunctionCall() const { switch (mOp) { case EOpCallFunctionInAST: case EOpCallInternalRawFunction: return true; default: return false; } } TOperator TIntermBinary::GetMulOpBasedOnOperands(const TType &left, const TType &right) { if (left.isMatrix()) { if (right.isMatrix()) { return EOpMatrixTimesMatrix; } else { if (right.isVector()) { return EOpMatrixTimesVector; } else { return EOpMatrixTimesScalar; } } } else { if (right.isMatrix()) { if (left.isVector()) { return EOpVectorTimesMatrix; } else { return EOpMatrixTimesScalar; } } else { // Neither operand is a matrix. if (left.isVector() == right.isVector()) { // Leave as component product. return EOpMul; } else { return EOpVectorTimesScalar; } } } } TOperator TIntermBinary::GetMulAssignOpBasedOnOperands(const TType &left, const TType &right) { if (left.isMatrix()) { if (right.isMatrix()) { return EOpMatrixTimesMatrixAssign; } else { // right should be scalar, but this may not be validated yet. return EOpMatrixTimesScalarAssign; } } else { if (right.isMatrix()) { // Left should be a vector, but this may not be validated yet. return EOpVectorTimesMatrixAssign; } else { // Neither operand is a matrix. if (left.isVector() == right.isVector()) { // Leave as component product. return EOpMulAssign; } else { // left should be vector and right should be scalar, but this may not be validated // yet. return EOpVectorTimesScalarAssign; } } } } // // Make sure the type of a unary operator is appropriate for its // combination of operation and operand type. // void TIntermUnary::promote() { if (mOp == EOpArrayLength) { // Special case: the qualifier of .length() doesn't depend on the operand qualifier. setType(TType(EbtInt, EbpHigh, EvqConst)); return; } TQualifier resultQualifier = EvqTemporary; if (mOperand->getQualifier() == EvqConst) resultQualifier = EvqConst; TType resultType = mOperand->getType(); resultType.setQualifier(resultQualifier); // Result is an intermediate value, so make sure it's identified as such. resultType.setInterfaceBlock(nullptr); // Override type properties for special built-ins. Precision is determined later by // |derivePrecision|. switch (mOp) { case EOpFloatBitsToInt: resultType.setBasicType(EbtInt); break; case EOpFloatBitsToUint: resultType.setBasicType(EbtUInt); break; case EOpIntBitsToFloat: case EOpUintBitsToFloat: resultType.setBasicType(EbtFloat); break; case EOpPackSnorm2x16: case EOpPackUnorm2x16: case EOpPackHalf2x16: case EOpPackUnorm4x8: case EOpPackSnorm4x8: resultType.setBasicType(EbtUInt); resultType.setPrimarySize(1); break; case EOpUnpackSnorm2x16: case EOpUnpackUnorm2x16: case EOpUnpackHalf2x16: resultType.setBasicType(EbtFloat); resultType.setPrimarySize(2); break; case EOpUnpackUnorm4x8: case EOpUnpackSnorm4x8: resultType.setBasicType(EbtFloat); resultType.setPrimarySize(4); break; case EOpAny: case EOpAll: resultType.setBasicType(EbtBool); resultType.setPrimarySize(1); break; case EOpLength: case EOpDeterminant: resultType.setBasicType(EbtFloat); resultType.setPrimarySize(1); resultType.setSecondarySize(1); break; case EOpTranspose: ASSERT(resultType.getBasicType() == EbtFloat); resultType.setPrimarySize(mOperand->getType().getRows()); resultType.setSecondarySize(mOperand->getType().getCols()); break; case EOpIsinf: case EOpIsnan: resultType.setBasicType(EbtBool); break; case EOpBitCount: case EOpFindLSB: case EOpFindMSB: resultType.setBasicType(EbtInt); break; default: break; } setType(resultType); propagatePrecision(derivePrecision()); } // Derive precision from children nodes TPrecision TIntermUnary::derivePrecision() const { // Unary operators generally derive their precision from their operand, except for a few // built-ins where this is overriden. switch (mOp) { case EOpArrayLength: case EOpFloatBitsToInt: case EOpFloatBitsToUint: case EOpIntBitsToFloat: case EOpUintBitsToFloat: case EOpPackSnorm2x16: case EOpPackUnorm2x16: case EOpPackHalf2x16: case EOpPackUnorm4x8: case EOpPackSnorm4x8: case EOpUnpackSnorm2x16: case EOpUnpackUnorm2x16: case EOpBitfieldReverse: return EbpHigh; case EOpUnpackHalf2x16: case EOpUnpackUnorm4x8: case EOpUnpackSnorm4x8: return EbpMedium; case EOpBitCount: case EOpFindLSB: case EOpFindMSB: return EbpLow; case EOpAny: case EOpAll: case EOpIsinf: case EOpIsnan: return EbpUndefined; default: return mOperand->getPrecision(); } } void TIntermUnary::propagatePrecision(TPrecision precision) { mType.setPrecision(precision); // Generally precision of the operand and the precision of the result match. A few built-ins // are exceptional. switch (mOp) { case EOpArrayLength: case EOpPackSnorm2x16: case EOpPackUnorm2x16: case EOpPackUnorm4x8: case EOpPackSnorm4x8: case EOpPackHalf2x16: case EOpBitCount: case EOpFindLSB: case EOpFindMSB: case EOpIsinf: case EOpIsnan: // Precision of result does not affect the operand in any way. break; case EOpFloatBitsToInt: case EOpFloatBitsToUint: case EOpIntBitsToFloat: case EOpUintBitsToFloat: case EOpUnpackSnorm2x16: case EOpUnpackUnorm2x16: case EOpUnpackUnorm4x8: case EOpUnpackSnorm4x8: case EOpUnpackHalf2x16: case EOpBitfieldReverse: PropagatePrecisionIfApplicable(mOperand, EbpHigh); break; default: PropagatePrecisionIfApplicable(mOperand, precision); } } TIntermSwizzle::TIntermSwizzle(TIntermTyped *operand, const TVector &swizzleOffsets) : TIntermExpression(TType(EbtFloat, EbpUndefined)), mOperand(operand), mSwizzleOffsets(swizzleOffsets), mHasFoldedDuplicateOffsets(false) { ASSERT(mOperand); ASSERT(mOperand->getType().isVector()); ASSERT(mSwizzleOffsets.size() <= 4); promote(); } TIntermUnary::TIntermUnary(TOperator op, TIntermTyped *operand, const TFunction *function) : TIntermOperator(op), mOperand(operand), mUseEmulatedFunction(false), mFunction(function) { ASSERT(mOperand); ASSERT(!BuiltInGroup::IsBuiltIn(op) || (function != nullptr && function->getBuiltInOp() == op)); promote(); } TIntermBinary::TIntermBinary(TOperator op, TIntermTyped *left, TIntermTyped *right) : TIntermOperator(op), mLeft(left), mRight(right) { ASSERT(mLeft); ASSERT(mRight); promote(); } TIntermBinary *TIntermBinary::CreateComma(TIntermTyped *left, TIntermTyped *right, int shaderVersion) { TIntermBinary *node = new TIntermBinary(EOpComma, left, right); node->getTypePointer()->setQualifier(GetCommaQualifier(shaderVersion, left, right)); return node; } TIntermGlobalQualifierDeclaration::TIntermGlobalQualifierDeclaration(TIntermSymbol *symbol, bool isPrecise, const TSourceLoc &line) : TIntermNode(), mSymbol(symbol), mIsPrecise(isPrecise) { ASSERT(symbol); setLine(line); } TIntermGlobalQualifierDeclaration::TIntermGlobalQualifierDeclaration( const TIntermGlobalQualifierDeclaration &node) : TIntermGlobalQualifierDeclaration(static_cast(node.mSymbol->deepCopy()), node.mIsPrecise, node.mLine) {} TIntermTernary::TIntermTernary(TIntermTyped *cond, TIntermTyped *trueExpression, TIntermTyped *falseExpression) : TIntermExpression(trueExpression->getType()), mCondition(cond), mTrueExpression(trueExpression), mFalseExpression(falseExpression) { ASSERT(mCondition); ASSERT(mTrueExpression); ASSERT(mFalseExpression); getTypePointer()->setQualifier( TIntermTernary::DetermineQualifier(cond, trueExpression, falseExpression)); propagatePrecision(derivePrecision()); } TIntermLoop::TIntermLoop(TLoopType type, TIntermNode *init, TIntermTyped *cond, TIntermTyped *expr, TIntermBlock *body) : mType(type), mInit(init), mCond(cond), mExpr(expr), mBody(body) { // Declaration nodes with no children can appear if all the declarators just added constants to // the symbol table instead of generating code. They're no-ops so don't add them to the tree. if (mInit && mInit->getAsDeclarationNode() && mInit->getAsDeclarationNode()->getSequence()->empty()) { mInit = nullptr; } } TIntermLoop::TIntermLoop(const TIntermLoop &node) : TIntermLoop(node.mType, node.mInit ? node.mInit->deepCopy() : nullptr, node.mCond ? node.mCond->deepCopy() : nullptr, node.mExpr ? node.mExpr->deepCopy() : nullptr, node.mBody ? node.mBody->deepCopy() : nullptr) {} TIntermIfElse::TIntermIfElse(TIntermTyped *cond, TIntermBlock *trueB, TIntermBlock *falseB) : TIntermNode(), mCondition(cond), mTrueBlock(trueB), mFalseBlock(falseB) { ASSERT(mCondition); // Prune empty false blocks so that there won't be unnecessary operations done on it. if (mFalseBlock && mFalseBlock->getSequence()->empty()) { mFalseBlock = nullptr; } } TIntermIfElse::TIntermIfElse(const TIntermIfElse &node) : TIntermIfElse(node.mCondition->deepCopy(), node.mTrueBlock->deepCopy(), node.mFalseBlock ? node.mFalseBlock->deepCopy() : nullptr) {} TIntermSwitch::TIntermSwitch(TIntermTyped *init, TIntermBlock *statementList) : TIntermNode(), mInit(init), mStatementList(statementList) { ASSERT(mInit); ASSERT(mStatementList); } TIntermSwitch::TIntermSwitch(const TIntermSwitch &node) : TIntermSwitch(node.mInit->deepCopy(), node.mStatementList->deepCopy()) {} void TIntermSwitch::setStatementList(TIntermBlock *statementList) { ASSERT(statementList); mStatementList = statementList; } // static TQualifier TIntermTernary::DetermineQualifier(TIntermTyped *cond, TIntermTyped *trueExpression, TIntermTyped *falseExpression) { if (cond->getQualifier() == EvqConst && trueExpression->getQualifier() == EvqConst && falseExpression->getQualifier() == EvqConst) { return EvqConst; } return EvqTemporary; } // Derive precision from children nodes TPrecision TIntermTernary::derivePrecision() const { return GetHigherPrecision(mTrueExpression->getPrecision(), mFalseExpression->getPrecision()); } void TIntermTernary::propagatePrecision(TPrecision precision) { mType.setPrecision(precision); PropagatePrecisionIfApplicable(mTrueExpression, precision); PropagatePrecisionIfApplicable(mFalseExpression, precision); } TIntermTyped *TIntermTernary::fold(TDiagnostics * /* diagnostics */) { if (mCondition->getAsConstantUnion()) { if (mCondition->getAsConstantUnion()->getBConst(0)) { return mTrueExpression; } else { return mFalseExpression; } } return this; } void TIntermSwizzle::promote() { TQualifier resultQualifier = EvqTemporary; if (mOperand->getQualifier() == EvqConst) resultQualifier = EvqConst; size_t numFields = mSwizzleOffsets.size(); setType(TType(mOperand->getBasicType(), EbpUndefined, resultQualifier, static_cast(numFields))); propagatePrecision(derivePrecision()); } // Derive precision from children nodes TPrecision TIntermSwizzle::derivePrecision() const { return mOperand->getPrecision(); } void TIntermSwizzle::propagatePrecision(TPrecision precision) { mType.setPrecision(precision); PropagatePrecisionIfApplicable(mOperand, precision); } bool TIntermSwizzle::hasDuplicateOffsets() const { if (mHasFoldedDuplicateOffsets) { return true; } int offsetCount[4] = {0u, 0u, 0u, 0u}; for (const auto offset : mSwizzleOffsets) { offsetCount[offset]++; if (offsetCount[offset] > 1) { return true; } } return false; } void TIntermSwizzle::setHasFoldedDuplicateOffsets(bool hasFoldedDuplicateOffsets) { mHasFoldedDuplicateOffsets = hasFoldedDuplicateOffsets; } bool TIntermSwizzle::offsetsMatch(int offset) const { return mSwizzleOffsets.size() == 1 && mSwizzleOffsets[0] == offset; } void TIntermSwizzle::writeOffsetsAsXYZW(TInfoSinkBase *out) const { for (const int offset : mSwizzleOffsets) { switch (offset) { case 0: *out << "x"; break; case 1: *out << "y"; break; case 2: *out << "z"; break; case 3: *out << "w"; break; default: UNREACHABLE(); } } } TQualifier TIntermBinary::GetCommaQualifier(int shaderVersion, const TIntermTyped *left, const TIntermTyped *right) { // ESSL3.00 section 12.43: The result of a sequence operator is not a constant-expression. if (shaderVersion >= 300 || left->getQualifier() != EvqConst || right->getQualifier() != EvqConst) { return EvqTemporary; } return EvqConst; } // Establishes the type of the result of the binary operation. void TIntermBinary::promote() { ASSERT(!isMultiplication() || mOp == GetMulOpBasedOnOperands(mLeft->getType(), mRight->getType())); // Comma is handled as a special case. Note that the comma node qualifier depends on the shader // version and so is not being set here. if (mOp == EOpComma) { setType(mRight->getType()); return; } // Base assumption: just make the type the same as the left // operand. Then only deviations from this need be coded. setType(mLeft->getType()); TQualifier resultQualifier = EvqConst; // Binary operations results in temporary variables unless both // operands are const. If initializing a specialization constant, make the declarator also // EvqSpecConst. const bool isSpecConstInit = mOp == EOpInitialize && mLeft->getQualifier() == EvqSpecConst; const bool isEitherNonConst = mLeft->getQualifier() != EvqConst || mRight->getQualifier() != EvqConst; if (!isSpecConstInit && isEitherNonConst) { resultQualifier = EvqTemporary; getTypePointer()->setQualifier(EvqTemporary); } // Result is an intermediate value, so make sure it's identified as such. That's not true for // interface block arrays being indexed. if (mOp != EOpIndexDirect && mOp != EOpIndexIndirect) { getTypePointer()->setInterfaceBlock(nullptr); } // Handle indexing ops. switch (mOp) { case EOpIndexDirect: case EOpIndexIndirect: if (mLeft->isArray()) { mType.toArrayElementType(); } else if (mLeft->isMatrix()) { mType.toMatrixColumnType(); } else if (mLeft->isVector()) { mType.toComponentType(); } else { UNREACHABLE(); } return; case EOpIndexDirectStruct: { const TFieldList &fields = mLeft->getType().getStruct()->fields(); const int fieldIndex = mRight->getAsConstantUnion()->getIConst(0); setType(*fields[fieldIndex]->type()); getTypePointer()->setQualifier(resultQualifier); return; } case EOpIndexDirectInterfaceBlock: { const TFieldList &fields = mLeft->getType().getInterfaceBlock()->fields(); const int fieldIndex = mRight->getAsConstantUnion()->getIConst(0); setType(*fields[fieldIndex]->type()); getTypePointer()->setQualifier(resultQualifier); return; } default: break; } ASSERT(mLeft->isArray() == mRight->isArray()); const uint8_t nominalSize = std::max(mLeft->getNominalSize(), mRight->getNominalSize()); switch (mOp) { case EOpMul: break; case EOpMatrixTimesScalar: if (mRight->isMatrix()) { getTypePointer()->setPrimarySize(mRight->getCols()); getTypePointer()->setSecondarySize(mRight->getRows()); } break; case EOpMatrixTimesVector: getTypePointer()->setPrimarySize(mLeft->getRows()); getTypePointer()->setSecondarySize(1); break; case EOpMatrixTimesMatrix: getTypePointer()->setPrimarySize(mRight->getCols()); getTypePointer()->setSecondarySize(mLeft->getRows()); break; case EOpVectorTimesScalar: getTypePointer()->setPrimarySize(nominalSize); break; case EOpVectorTimesMatrix: getTypePointer()->setPrimarySize(mRight->getCols()); ASSERT(getType().getSecondarySize() == 1); break; case EOpMulAssign: case EOpVectorTimesScalarAssign: case EOpVectorTimesMatrixAssign: case EOpMatrixTimesScalarAssign: case EOpMatrixTimesMatrixAssign: ASSERT(mOp == GetMulAssignOpBasedOnOperands(mLeft->getType(), mRight->getType())); break; case EOpAssign: case EOpInitialize: ASSERT((mLeft->getNominalSize() == mRight->getNominalSize()) && (mLeft->getSecondarySize() == mRight->getSecondarySize())); break; case EOpAdd: case EOpSub: case EOpDiv: case EOpIMod: case EOpBitShiftLeft: case EOpBitShiftRight: case EOpBitwiseAnd: case EOpBitwiseXor: case EOpBitwiseOr: case EOpAddAssign: case EOpSubAssign: case EOpDivAssign: case EOpIModAssign: case EOpBitShiftLeftAssign: case EOpBitShiftRightAssign: case EOpBitwiseAndAssign: case EOpBitwiseXorAssign: case EOpBitwiseOrAssign: { ASSERT(!mLeft->isArray() && !mRight->isArray()); const uint8_t secondarySize = std::max(mLeft->getSecondarySize(), mRight->getSecondarySize()); getTypePointer()->setPrimarySize(nominalSize); getTypePointer()->setSecondarySize(secondarySize); break; } case EOpEqual: case EOpNotEqual: case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: ASSERT((mLeft->getNominalSize() == mRight->getNominalSize()) && (mLeft->getSecondarySize() == mRight->getSecondarySize())); setType(TType(EbtBool, EbpUndefined, resultQualifier)); break; // // And and Or operate on conditionals // case EOpLogicalAnd: case EOpLogicalXor: case EOpLogicalOr: ASSERT(mLeft->getBasicType() == EbtBool && mRight->getBasicType() == EbtBool); break; case EOpIndexDirect: case EOpIndexIndirect: case EOpIndexDirectInterfaceBlock: case EOpIndexDirectStruct: // These ops should be already fully handled. UNREACHABLE(); break; default: UNREACHABLE(); break; } propagatePrecision(derivePrecision()); } // Derive precision from children nodes TPrecision TIntermBinary::derivePrecision() const { // Assignments use the type and precision of the lvalue-expression // GLSL ES spec section 5.8: Assignments // "The assignment operator stores the value of rvalue-expression into the l-value and returns // an r-value with the type and precision of lvalue-expression." if (IsAssignment(mOp)) { return mLeft->getPrecision(); } const TPrecision higherPrecision = GetHigherPrecision(mLeft->getPrecision(), mRight->getPrecision()); switch (mOp) { case EOpComma: // Comma takes the right node's value. return mRight->getPrecision(); case EOpIndexDirect: case EOpIndexIndirect: case EOpBitShiftLeft: case EOpBitShiftRight: // When indexing an array, the precision of the array is preserved (which is the left // node). // For shift operations, the precision is derived from the expression being shifted // (which is also the left node). return mLeft->getPrecision(); case EOpIndexDirectStruct: case EOpIndexDirectInterfaceBlock: { // When selecting the field of a block, the precision is taken from the field's // declaration. const TFieldList &fields = mOp == EOpIndexDirectStruct ? mLeft->getType().getStruct()->fields() : mLeft->getType().getInterfaceBlock()->fields(); const int fieldIndex = mRight->getAsConstantUnion()->getIConst(0); return fields[fieldIndex]->type()->getPrecision(); } case EOpEqual: case EOpNotEqual: case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: case EOpLogicalAnd: case EOpLogicalXor: case EOpLogicalOr: // No precision specified on bool results. return EbpUndefined; default: // All other operations are evaluated at the higher of the two operands' precisions. return higherPrecision; } } void TIntermBinary::propagatePrecision(TPrecision precision) { getTypePointer()->setPrecision(precision); if (mOp != EOpComma) { PropagatePrecisionIfApplicable(mLeft, precision); } if (mOp != EOpIndexDirect && mOp != EOpIndexIndirect && mOp != EOpIndexDirectStruct && mOp != EOpIndexDirectInterfaceBlock) { PropagatePrecisionIfApplicable(mRight, precision); } // For indices, always apply highp. This is purely for the purpose of making sure constant and // constructor nodes are also given a precision, so if they are hoisted to a temp variable, // there would be a precision to apply to that variable. if (mOp == EOpIndexDirect || mOp == EOpIndexIndirect) { PropagatePrecisionIfApplicable(mRight, EbpHigh); } } bool TIntermConstantUnion::hasConstantValue() const { return true; } bool TIntermConstantUnion::isConstantNullValue() const { const size_t size = mType.getObjectSize(); for (size_t index = 0; index < size; ++index) { if (!mUnionArrayPointer[index].isZero()) { return false; } } return true; } const TConstantUnion *TIntermConstantUnion::getConstantValue() const { return mUnionArrayPointer; } const TConstantUnion *TIntermConstantUnion::FoldIndexing(const TType &type, const TConstantUnion *constArray, int index) { if (type.isArray()) { ASSERT(index < static_cast(type.getOutermostArraySize())); TType arrayElementType(type); arrayElementType.toArrayElementType(); size_t arrayElementSize = arrayElementType.getObjectSize(); return &constArray[arrayElementSize * index]; } else if (type.isMatrix()) { ASSERT(index < type.getCols()); const uint8_t size = type.getRows(); return &constArray[size * index]; } else if (type.isVector()) { ASSERT(index < type.getNominalSize()); return &constArray[index]; } else { UNREACHABLE(); return nullptr; } } TIntermTyped *TIntermSwizzle::fold(TDiagnostics * /* diagnostics */) { TIntermSwizzle *operandSwizzle = mOperand->getAsSwizzleNode(); if (operandSwizzle) { // We need to fold the two swizzles into one, so that repeated swizzling can't cause stack // overflow in ParseContext::checkCanBeLValue(). bool hadDuplicateOffsets = operandSwizzle->hasDuplicateOffsets(); TVector foldedOffsets; for (int offset : mSwizzleOffsets) { // Offset should already be validated. ASSERT(static_cast(offset) < operandSwizzle->mSwizzleOffsets.size()); foldedOffsets.push_back(operandSwizzle->mSwizzleOffsets[offset]); } operandSwizzle->mSwizzleOffsets = foldedOffsets; operandSwizzle->setType(getType()); operandSwizzle->setHasFoldedDuplicateOffsets(hadDuplicateOffsets); return operandSwizzle; } TIntermConstantUnion *operandConstant = mOperand->getAsConstantUnion(); if (operandConstant == nullptr) { return this; } TConstantUnion *constArray = new TConstantUnion[mSwizzleOffsets.size()]; for (size_t i = 0; i < mSwizzleOffsets.size(); ++i) { constArray[i] = *TIntermConstantUnion::FoldIndexing( operandConstant->getType(), operandConstant->getConstantValue(), mSwizzleOffsets.at(i)); } return CreateFoldedNode(constArray, this); } TIntermTyped *TIntermBinary::fold(TDiagnostics *diagnostics) { const TConstantUnion *rightConstant = mRight->getConstantValue(); switch (mOp) { case EOpComma: { if (mLeft->hasSideEffects()) { return this; } return mRight; } case EOpIndexDirect: case EOpIndexDirectStruct: { if (rightConstant == nullptr) { return this; } size_t index = static_cast(rightConstant->getIConst()); TIntermAggregate *leftAggregate = mLeft->getAsAggregate(); if (leftAggregate && leftAggregate->isConstructor() && leftAggregate->isArray() && !leftAggregate->hasSideEffects()) { ASSERT(index < leftAggregate->getSequence()->size()); // This transformation can't add complexity as we're eliminating the constructor // entirely. return leftAggregate->getSequence()->at(index)->getAsTyped(); } // If the indexed value is already a constant union, we can't increase duplication of // data by folding the indexing. Also fold the node in case it's generally beneficial to // replace this type of node with a constant union even if that would mean duplicating // data. if (mLeft->getAsConstantUnion() || getType().canReplaceWithConstantUnion()) { const TConstantUnion *constantValue = getConstantValue(); if (constantValue == nullptr) { return this; } return CreateFoldedNode(constantValue, this); } return this; } case EOpIndexIndirect: case EOpIndexDirectInterfaceBlock: case EOpInitialize: // Can never be constant folded. return this; default: { if (rightConstant == nullptr) { return this; } const TConstantUnion *leftConstant = mLeft->getConstantValue(); if (leftConstant == nullptr) { return this; } const TConstantUnion *constArray = TIntermConstantUnion::FoldBinary(mOp, leftConstant, mLeft->getType(), rightConstant, mRight->getType(), diagnostics, mLeft->getLine()); if (!constArray) { return this; } return CreateFoldedNode(constArray, this); } } } bool TIntermBinary::hasConstantValue() const { switch (mOp) { case EOpIndexDirect: case EOpIndexDirectStruct: { if (mLeft->hasConstantValue() && mRight->hasConstantValue()) { return true; } break; } default: break; } return false; } const TConstantUnion *TIntermBinary::getConstantValue() const { if (!hasConstantValue()) { return nullptr; } const TConstantUnion *leftConstantValue = mLeft->getConstantValue(); int index = mRight->getConstantValue()->getIConst(); const TConstantUnion *constIndexingResult = nullptr; if (mOp == EOpIndexDirect) { constIndexingResult = TIntermConstantUnion::FoldIndexing(mLeft->getType(), leftConstantValue, index); } else { ASSERT(mOp == EOpIndexDirectStruct); const TFieldList &fields = mLeft->getType().getStruct()->fields(); size_t previousFieldsSize = 0; for (int i = 0; i < index; ++i) { previousFieldsSize += fields[i]->type()->getObjectSize(); } constIndexingResult = leftConstantValue + previousFieldsSize; } return constIndexingResult; } const ImmutableString &TIntermBinary::getIndexStructFieldName() const { ASSERT(mOp == EOpIndexDirectStruct); const TType &lhsType = mLeft->getType(); const TStructure *structure = lhsType.getStruct(); const int index = mRight->getAsConstantUnion()->getIConst(0); return structure->fields()[index]->name(); } TIntermTyped *TIntermUnary::fold(TDiagnostics *diagnostics) { TConstantUnion *constArray = nullptr; if (mOp == EOpArrayLength) { // The size of runtime-sized arrays may only be determined at runtime. if (mOperand->hasSideEffects() || mOperand->getType().isUnsizedArray()) { return this; } constArray = new TConstantUnion[1]; constArray->setIConst(mOperand->getOutermostArraySize()); } else { TIntermConstantUnion *operandConstant = mOperand->getAsConstantUnion(); if (operandConstant == nullptr) { return this; } switch (mOp) { case EOpAny: case EOpAll: case EOpLength: case EOpTranspose: case EOpDeterminant: case EOpInverse: case EOpPackSnorm2x16: case EOpUnpackSnorm2x16: case EOpPackUnorm2x16: case EOpUnpackUnorm2x16: case EOpPackHalf2x16: case EOpUnpackHalf2x16: case EOpPackUnorm4x8: case EOpPackSnorm4x8: case EOpUnpackUnorm4x8: case EOpUnpackSnorm4x8: constArray = operandConstant->foldUnaryNonComponentWise(mOp); break; default: constArray = operandConstant->foldUnaryComponentWise(mOp, mFunction, diagnostics); break; } } if (constArray == nullptr) { return this; } return CreateFoldedNode(constArray, this); } TIntermTyped *TIntermAggregate::fold(TDiagnostics *diagnostics) { // Make sure that all params are constant before actual constant folding. for (auto *param : *getSequence()) { if (param->getAsConstantUnion() == nullptr) { return this; } } const TConstantUnion *constArray = nullptr; if (isConstructor()) { if (mType.canReplaceWithConstantUnion()) { constArray = getConstantValue(); if (constArray && mType.getBasicType() == EbtUInt) { // Check if we converted a negative float to uint and issue a warning in that case. size_t sizeRemaining = mType.getObjectSize(); for (TIntermNode *arg : mArguments) { TIntermTyped *typedArg = arg->getAsTyped(); if (typedArg->getBasicType() == EbtFloat) { const TConstantUnion *argValue = typedArg->getConstantValue(); size_t castSize = std::min(typedArg->getType().getObjectSize(), sizeRemaining); for (size_t i = 0; i < castSize; ++i) { if (argValue[i].getFConst() < 0.0f) { // ESSL 3.00.6 section 5.4.1. diagnostics->warning( mLine, "casting a negative float to uint is undefined", mType.getBuiltInTypeNameString()); } } } sizeRemaining -= typedArg->getType().getObjectSize(); } } } } else if (CanFoldAggregateBuiltInOp(mOp)) { constArray = TIntermConstantUnion::FoldAggregateBuiltIn(this, diagnostics); } if (constArray == nullptr) { return this; } return CreateFoldedNode(constArray, this); } // // The fold functions see if an operation on a constant can be done in place, // without generating run-time code. // // Returns the constant value to keep using or nullptr. // const TConstantUnion *TIntermConstantUnion::FoldBinary(TOperator op, const TConstantUnion *leftArray, const TType &leftType, const TConstantUnion *rightArray, const TType &rightType, TDiagnostics *diagnostics, const TSourceLoc &line) { ASSERT(leftArray && rightArray); size_t objectSize = leftType.getObjectSize(); // for a case like float f = vec4(2, 3, 4, 5) + 1.2; if (rightType.getObjectSize() == 1 && objectSize > 1) { rightArray = Vectorize(*rightArray, objectSize); } else if (rightType.getObjectSize() > 1 && objectSize == 1) { // for a case like float f = 1.2 + vec4(2, 3, 4, 5); leftArray = Vectorize(*leftArray, rightType.getObjectSize()); objectSize = rightType.getObjectSize(); } TConstantUnion *resultArray = nullptr; switch (op) { case EOpAdd: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = TConstantUnion::add(leftArray[i], rightArray[i], diagnostics, line); break; case EOpSub: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = TConstantUnion::sub(leftArray[i], rightArray[i], diagnostics, line); break; case EOpMul: case EOpVectorTimesScalar: case EOpMatrixTimesScalar: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = TConstantUnion::mul(leftArray[i], rightArray[i], diagnostics, line); break; case EOpMatrixTimesMatrix: { // TODO(jmadll): This code should check for overflows. ASSERT(leftType.getBasicType() == EbtFloat && rightType.getBasicType() == EbtFloat); const uint8_t leftCols = leftType.getCols(); const uint8_t leftRows = leftType.getRows(); const uint8_t rightCols = rightType.getCols(); const uint8_t rightRows = rightType.getRows(); const uint8_t resultCols = rightCols; const uint8_t resultRows = leftRows; resultArray = new TConstantUnion[resultCols * resultRows]; for (uint8_t row = 0; row < resultRows; row++) { for (uint8_t column = 0; column < resultCols; column++) { resultArray[resultRows * column + row].setFConst(0.0f); for (uint8_t i = 0; i < leftCols; i++) { resultArray[resultRows * column + row].setFConst( resultArray[resultRows * column + row].getFConst() + leftArray[i * leftRows + row].getFConst() * rightArray[column * rightRows + i].getFConst()); } } } } break; case EOpDiv: case EOpIMod: { resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) { if (IsFloatDivision(leftType.getBasicType(), rightType.getBasicType())) { // Float division requested, possibly with implicit conversion ASSERT(op == EOpDiv); float dividend = leftArray[i].getFConst(); float divisor = rightArray[i].getFConst(); if (divisor == 0.0f) { if (dividend == 0.0f) { diagnostics->warning(line, "Zero divided by zero during constant " "folding generated NaN", "/"); resultArray[i].setFConst(std::numeric_limits::quiet_NaN()); } else { diagnostics->warning(line, "Divide by zero during constant folding", "/"); bool negativeResult = std::signbit(dividend) != std::signbit(divisor); resultArray[i].setFConst(negativeResult ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); } } else if (gl::isInf(dividend) && gl::isInf(divisor)) { diagnostics->warning(line, "Infinity divided by infinity during constant " "folding generated NaN", "/"); resultArray[i].setFConst(std::numeric_limits::quiet_NaN()); } else { float result = dividend / divisor; if (!gl::isInf(dividend) && gl::isInf(result)) { diagnostics->warning( line, "Constant folded division overflowed to infinity", "/"); } resultArray[i].setFConst(result); } } else { // Types are either both int or both uint switch (leftType.getBasicType()) { case EbtInt: { if (rightArray[i] == 0) { diagnostics->warning( line, "Divide by zero error during constant folding", "/"); resultArray[i].setIConst(INT_MAX); } else { int lhs = leftArray[i].getIConst(); int divisor = rightArray[i].getIConst(); if (op == EOpDiv) { // Check for the special case where the minimum // representable number is divided by -1. If left alone this // leads to integer overflow in C++. ESSL 3.00.6 // section 4.1.3 Integers: "However, for the case where the // minimum representable value is divided by -1, it is // allowed to return either the minimum representable value // or the maximum representable value." if (lhs == -0x7fffffff - 1 && divisor == -1) { resultArray[i].setIConst(0x7fffffff); } else { resultArray[i].setIConst(lhs / divisor); } } else { ASSERT(op == EOpIMod); if (lhs < 0 || divisor < 0) { // ESSL 3.00.6 section 5.9: Results of modulus are // undefined when either one of the operands is // negative. diagnostics->warning(line, "Negative modulus operator operand " "encountered during constant folding. " "Results are undefined.", "%"); resultArray[i].setIConst(0); } else { resultArray[i].setIConst(lhs % divisor); } } } break; } case EbtUInt: { if (rightArray[i] == 0) { diagnostics->warning( line, "Divide by zero error during constant folding", "/"); resultArray[i].setUConst(UINT_MAX); } else { if (op == EOpDiv) { resultArray[i].setUConst(leftArray[i].getUConst() / rightArray[i].getUConst()); } else { ASSERT(op == EOpIMod); resultArray[i].setUConst(leftArray[i].getUConst() % rightArray[i].getUConst()); } } break; } default: UNREACHABLE(); return nullptr; } } } } break; case EOpMatrixTimesVector: { // TODO(jmadll): This code should check for overflows. ASSERT(rightType.getBasicType() == EbtFloat); const uint8_t matrixCols = leftType.getCols(); const uint8_t matrixRows = leftType.getRows(); resultArray = new TConstantUnion[matrixRows]; for (uint8_t matrixRow = 0; matrixRow < matrixRows; matrixRow++) { resultArray[matrixRow].setFConst(0.0f); for (uint8_t col = 0; col < matrixCols; col++) { resultArray[matrixRow].setFConst( resultArray[matrixRow].getFConst() + leftArray[col * matrixRows + matrixRow].getFConst() * rightArray[col].getFConst()); } } } break; case EOpVectorTimesMatrix: { // TODO(jmadll): This code should check for overflows. ASSERT(leftType.getBasicType() == EbtFloat); const uint8_t matrixCols = rightType.getCols(); const uint8_t matrixRows = rightType.getRows(); resultArray = new TConstantUnion[matrixCols]; for (uint8_t matrixCol = 0; matrixCol < matrixCols; matrixCol++) { resultArray[matrixCol].setFConst(0.0f); for (uint8_t matrixRow = 0; matrixRow < matrixRows; matrixRow++) { resultArray[matrixCol].setFConst( resultArray[matrixCol].getFConst() + leftArray[matrixRow].getFConst() * rightArray[matrixCol * matrixRows + matrixRow].getFConst()); } } } break; case EOpLogicalAnd: { resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) { resultArray[i] = leftArray[i] && rightArray[i]; } } break; case EOpLogicalOr: { resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) { resultArray[i] = leftArray[i] || rightArray[i]; } } break; case EOpLogicalXor: { ASSERT(leftType.getBasicType() == EbtBool); resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) { resultArray[i].setBConst(leftArray[i] != rightArray[i]); } } break; case EOpBitwiseAnd: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = leftArray[i] & rightArray[i]; break; case EOpBitwiseXor: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = leftArray[i] ^ rightArray[i]; break; case EOpBitwiseOr: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = leftArray[i] | rightArray[i]; break; case EOpBitShiftLeft: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = TConstantUnion::lshift(leftArray[i], rightArray[i], diagnostics, line); break; case EOpBitShiftRight: resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) resultArray[i] = TConstantUnion::rshift(leftArray[i], rightArray[i], diagnostics, line); break; case EOpLessThan: ASSERT(objectSize == 1); resultArray = new TConstantUnion[1]; resultArray->setBConst(*leftArray < *rightArray); break; case EOpGreaterThan: ASSERT(objectSize == 1); resultArray = new TConstantUnion[1]; resultArray->setBConst(*leftArray > *rightArray); break; case EOpLessThanEqual: ASSERT(objectSize == 1); resultArray = new TConstantUnion[1]; resultArray->setBConst(!(*leftArray > *rightArray)); break; case EOpGreaterThanEqual: ASSERT(objectSize == 1); resultArray = new TConstantUnion[1]; resultArray->setBConst(!(*leftArray < *rightArray)); break; case EOpEqual: case EOpNotEqual: { resultArray = new TConstantUnion[1]; bool equal = true; for (size_t i = 0; i < objectSize; i++) { if (leftArray[i] != rightArray[i]) { equal = false; break; // break out of for loop } } if (op == EOpEqual) { resultArray->setBConst(equal); } else { resultArray->setBConst(!equal); } } break; default: UNREACHABLE(); return nullptr; } return resultArray; } // The fold functions do operations on a constant at GLSL compile time, without generating run-time // code. Returns the constant value to keep using. Nullptr should not be returned. TConstantUnion *TIntermConstantUnion::foldUnaryNonComponentWise(TOperator op) { // Do operations where the return type may have a different number of components compared to the // operand type. const TConstantUnion *operandArray = getConstantValue(); ASSERT(operandArray); size_t objectSize = getType().getObjectSize(); TConstantUnion *resultArray = nullptr; switch (op) { case EOpAny: ASSERT(getType().getBasicType() == EbtBool); resultArray = new TConstantUnion(); resultArray->setBConst(false); for (size_t i = 0; i < objectSize; i++) { if (operandArray[i].getBConst()) { resultArray->setBConst(true); break; } } break; case EOpAll: ASSERT(getType().getBasicType() == EbtBool); resultArray = new TConstantUnion(); resultArray->setBConst(true); for (size_t i = 0; i < objectSize; i++) { if (!operandArray[i].getBConst()) { resultArray->setBConst(false); break; } } break; case EOpLength: ASSERT(getType().getBasicType() == EbtFloat); resultArray = new TConstantUnion(); resultArray->setFConst(VectorLength(operandArray, objectSize)); break; case EOpTranspose: { ASSERT(getType().getBasicType() == EbtFloat); resultArray = new TConstantUnion[objectSize]; angle::Matrix result = GetMatrix(operandArray, getType().getRows(), getType().getCols()).transpose(); SetUnionArrayFromMatrix(result, resultArray); break; } case EOpDeterminant: { ASSERT(getType().getBasicType() == EbtFloat); const uint8_t size = getType().getNominalSize(); ASSERT(size >= 2 && size <= 4); resultArray = new TConstantUnion(); resultArray->setFConst(GetMatrix(operandArray, size).determinant()); break; } case EOpInverse: { ASSERT(getType().getBasicType() == EbtFloat); const uint8_t size = getType().getNominalSize(); ASSERT(size >= 2 && size <= 4); resultArray = new TConstantUnion[objectSize]; angle::Matrix result = GetMatrix(operandArray, size).inverse(); SetUnionArrayFromMatrix(result, resultArray); break; } case EOpPackSnorm2x16: ASSERT(getType().getBasicType() == EbtFloat); ASSERT(getType().getNominalSize() == 2); resultArray = new TConstantUnion(); resultArray->setUConst( gl::packSnorm2x16(operandArray[0].getFConst(), operandArray[1].getFConst())); break; case EOpUnpackSnorm2x16: { ASSERT(getType().getBasicType() == EbtUInt); resultArray = new TConstantUnion[2]; float f1, f2; gl::unpackSnorm2x16(operandArray[0].getUConst(), &f1, &f2); resultArray[0].setFConst(f1); resultArray[1].setFConst(f2); break; } case EOpPackUnorm2x16: ASSERT(getType().getBasicType() == EbtFloat); ASSERT(getType().getNominalSize() == 2); resultArray = new TConstantUnion(); resultArray->setUConst( gl::packUnorm2x16(operandArray[0].getFConst(), operandArray[1].getFConst())); break; case EOpUnpackUnorm2x16: { ASSERT(getType().getBasicType() == EbtUInt); resultArray = new TConstantUnion[2]; float f1, f2; gl::unpackUnorm2x16(operandArray[0].getUConst(), &f1, &f2); resultArray[0].setFConst(f1); resultArray[1].setFConst(f2); break; } case EOpPackHalf2x16: ASSERT(getType().getBasicType() == EbtFloat); ASSERT(getType().getNominalSize() == 2); resultArray = new TConstantUnion(); resultArray->setUConst( gl::packHalf2x16(operandArray[0].getFConst(), operandArray[1].getFConst())); break; case EOpUnpackHalf2x16: { ASSERT(getType().getBasicType() == EbtUInt); resultArray = new TConstantUnion[2]; float f1, f2; gl::unpackHalf2x16(operandArray[0].getUConst(), &f1, &f2); resultArray[0].setFConst(f1); resultArray[1].setFConst(f2); break; } case EOpPackUnorm4x8: { ASSERT(getType().getBasicType() == EbtFloat); resultArray = new TConstantUnion(); resultArray->setUConst( gl::PackUnorm4x8(operandArray[0].getFConst(), operandArray[1].getFConst(), operandArray[2].getFConst(), operandArray[3].getFConst())); break; } case EOpPackSnorm4x8: { ASSERT(getType().getBasicType() == EbtFloat); resultArray = new TConstantUnion(); resultArray->setUConst( gl::PackSnorm4x8(operandArray[0].getFConst(), operandArray[1].getFConst(), operandArray[2].getFConst(), operandArray[3].getFConst())); break; } case EOpUnpackUnorm4x8: { ASSERT(getType().getBasicType() == EbtUInt); resultArray = new TConstantUnion[4]; float f[4]; gl::UnpackUnorm4x8(operandArray[0].getUConst(), f); for (size_t i = 0; i < 4; ++i) { resultArray[i].setFConst(f[i]); } break; } case EOpUnpackSnorm4x8: { ASSERT(getType().getBasicType() == EbtUInt); resultArray = new TConstantUnion[4]; float f[4]; gl::UnpackSnorm4x8(operandArray[0].getUConst(), f); for (size_t i = 0; i < 4; ++i) { resultArray[i].setFConst(f[i]); } break; } default: UNREACHABLE(); break; } return resultArray; } TConstantUnion *TIntermConstantUnion::foldUnaryComponentWise(TOperator op, const TFunction *function, TDiagnostics *diagnostics) { // Do unary operations where each component of the result is computed based on the corresponding // component of the operand. Also folds normalize, though the divisor in that case takes all // components into account. const TConstantUnion *operandArray = getConstantValue(); ASSERT(operandArray); size_t objectSize = getType().getObjectSize(); TConstantUnion *resultArray = new TConstantUnion[objectSize]; for (size_t i = 0; i < objectSize; i++) { switch (op) { case EOpNegative: switch (getType().getBasicType()) { case EbtFloat: resultArray[i].setFConst(-operandArray[i].getFConst()); break; case EbtInt: if (operandArray[i] == std::numeric_limits::min()) { // The minimum representable integer doesn't have a positive // counterpart, rather the negation overflows and in ESSL is supposed to // wrap back to the minimum representable integer. Make sure that we // don't actually let the negation overflow, which has undefined // behavior in C++. resultArray[i].setIConst(std::numeric_limits::min()); } else { resultArray[i].setIConst(-operandArray[i].getIConst()); } break; case EbtUInt: if (operandArray[i] == 0x80000000u) { resultArray[i].setUConst(0x80000000u); } else { resultArray[i].setUConst(static_cast( -static_cast(operandArray[i].getUConst()))); } break; default: UNREACHABLE(); return nullptr; } break; case EOpPositive: switch (getType().getBasicType()) { case EbtFloat: resultArray[i].setFConst(operandArray[i].getFConst()); break; case EbtInt: resultArray[i].setIConst(operandArray[i].getIConst()); break; case EbtUInt: resultArray[i].setUConst(static_cast( static_cast(operandArray[i].getUConst()))); break; default: UNREACHABLE(); return nullptr; } break; case EOpLogicalNot: switch (getType().getBasicType()) { case EbtBool: resultArray[i].setBConst(!operandArray[i].getBConst()); break; default: UNREACHABLE(); return nullptr; } break; case EOpBitwiseNot: switch (getType().getBasicType()) { case EbtInt: resultArray[i].setIConst(~operandArray[i].getIConst()); break; case EbtUInt: resultArray[i].setUConst(~operandArray[i].getUConst()); break; default: UNREACHABLE(); return nullptr; } break; case EOpRadians: ASSERT(getType().getBasicType() == EbtFloat); resultArray[i].setFConst(kDegreesToRadiansMultiplier * operandArray[i].getFConst()); break; case EOpDegrees: ASSERT(getType().getBasicType() == EbtFloat); resultArray[i].setFConst(kRadiansToDegreesMultiplier * operandArray[i].getFConst()); break; case EOpSin: foldFloatTypeUnary(operandArray[i], &sinf, &resultArray[i]); break; case EOpCos: foldFloatTypeUnary(operandArray[i], &cosf, &resultArray[i]); break; case EOpTan: foldFloatTypeUnary(operandArray[i], &tanf, &resultArray[i]); break; case EOpAsin: // For asin(x), results are undefined if |x| > 1, we are choosing to set result to // 0. if (fabsf(operandArray[i].getFConst()) > 1.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else foldFloatTypeUnary(operandArray[i], &asinf, &resultArray[i]); break; case EOpAcos: // For acos(x), results are undefined if |x| > 1, we are choosing to set result to // 0. if (fabsf(operandArray[i].getFConst()) > 1.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else foldFloatTypeUnary(operandArray[i], &acosf, &resultArray[i]); break; case EOpAtan: foldFloatTypeUnary(operandArray[i], &atanf, &resultArray[i]); break; case EOpSinh: foldFloatTypeUnary(operandArray[i], &sinhf, &resultArray[i]); break; case EOpCosh: foldFloatTypeUnary(operandArray[i], &coshf, &resultArray[i]); break; case EOpTanh: foldFloatTypeUnary(operandArray[i], &tanhf, &resultArray[i]); break; case EOpAsinh: foldFloatTypeUnary(operandArray[i], &asinhf, &resultArray[i]); break; case EOpAcosh: // For acosh(x), results are undefined if x < 1, we are choosing to set result to 0. if (operandArray[i].getFConst() < 1.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else foldFloatTypeUnary(operandArray[i], &acoshf, &resultArray[i]); break; case EOpAtanh: // For atanh(x), results are undefined if |x| >= 1, we are choosing to set result to // 0. if (fabsf(operandArray[i].getFConst()) >= 1.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else foldFloatTypeUnary(operandArray[i], &atanhf, &resultArray[i]); break; case EOpAbs: switch (getType().getBasicType()) { case EbtFloat: resultArray[i].setFConst(fabsf(operandArray[i].getFConst())); break; case EbtInt: resultArray[i].setIConst(abs(operandArray[i].getIConst())); break; default: UNREACHABLE(); return nullptr; } break; case EOpSign: switch (getType().getBasicType()) { case EbtFloat: { float fConst = operandArray[i].getFConst(); float fResult = 0.0f; if (fConst > 0.0f) fResult = 1.0f; else if (fConst < 0.0f) fResult = -1.0f; resultArray[i].setFConst(fResult); break; } case EbtInt: { int iConst = operandArray[i].getIConst(); int iResult = 0; if (iConst > 0) iResult = 1; else if (iConst < 0) iResult = -1; resultArray[i].setIConst(iResult); break; } default: UNREACHABLE(); return nullptr; } break; case EOpFloor: foldFloatTypeUnary(operandArray[i], &floorf, &resultArray[i]); break; case EOpTrunc: foldFloatTypeUnary(operandArray[i], &truncf, &resultArray[i]); break; case EOpRound: foldFloatTypeUnary(operandArray[i], &roundf, &resultArray[i]); break; case EOpRoundEven: { ASSERT(getType().getBasicType() == EbtFloat); float x = operandArray[i].getFConst(); float result; float fractPart = modff(x, &result); if (fabsf(fractPart) == 0.5f) result = 2.0f * roundf(x / 2.0f); else result = roundf(x); resultArray[i].setFConst(result); break; } case EOpCeil: foldFloatTypeUnary(operandArray[i], &ceilf, &resultArray[i]); break; case EOpFract: { ASSERT(getType().getBasicType() == EbtFloat); float x = operandArray[i].getFConst(); resultArray[i].setFConst(x - floorf(x)); break; } case EOpIsnan: ASSERT(getType().getBasicType() == EbtFloat); resultArray[i].setBConst(gl::isNaN(operandArray[0].getFConst())); break; case EOpIsinf: ASSERT(getType().getBasicType() == EbtFloat); resultArray[i].setBConst(gl::isInf(operandArray[0].getFConst())); break; case EOpFloatBitsToInt: ASSERT(getType().getBasicType() == EbtFloat); resultArray[i].setIConst(gl::bitCast(operandArray[0].getFConst())); break; case EOpFloatBitsToUint: ASSERT(getType().getBasicType() == EbtFloat); resultArray[i].setUConst(gl::bitCast(operandArray[0].getFConst())); break; case EOpIntBitsToFloat: ASSERT(getType().getBasicType() == EbtInt); resultArray[i].setFConst(gl::bitCast(operandArray[0].getIConst())); break; case EOpUintBitsToFloat: ASSERT(getType().getBasicType() == EbtUInt); resultArray[i].setFConst(gl::bitCast(operandArray[0].getUConst())); break; case EOpExp: foldFloatTypeUnary(operandArray[i], &expf, &resultArray[i]); break; case EOpLog: // For log(x), results are undefined if x <= 0, we are choosing to set result to 0. if (operandArray[i].getFConst() <= 0.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else foldFloatTypeUnary(operandArray[i], &logf, &resultArray[i]); break; case EOpExp2: foldFloatTypeUnary(operandArray[i], &exp2f, &resultArray[i]); break; case EOpLog2: // For log2(x), results are undefined if x <= 0, we are choosing to set result to 0. // And log2f is not available on some plarforms like old android, so just using // log(x)/log(2) here. if (operandArray[i].getFConst() <= 0.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else { foldFloatTypeUnary(operandArray[i], &logf, &resultArray[i]); resultArray[i].setFConst(resultArray[i].getFConst() / logf(2.0f)); } break; case EOpSqrt: // For sqrt(x), results are undefined if x < 0, we are choosing to set result to 0. if (operandArray[i].getFConst() < 0.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else foldFloatTypeUnary(operandArray[i], &sqrtf, &resultArray[i]); break; case EOpInversesqrt: // There is no stdlib built-in function equavalent for GLES built-in inversesqrt(), // so getting the square root first using builtin function sqrt() and then taking // its inverse. // Also, for inversesqrt(x), results are undefined if x <= 0, we are choosing to set // result to 0. if (operandArray[i].getFConst() <= 0.0f) UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); else { foldFloatTypeUnary(operandArray[i], &sqrtf, &resultArray[i]); resultArray[i].setFConst(1.0f / resultArray[i].getFConst()); } break; case EOpNotComponentWise: ASSERT(getType().getBasicType() == EbtBool); resultArray[i].setBConst(!operandArray[i].getBConst()); break; case EOpNormalize: { ASSERT(getType().getBasicType() == EbtFloat); float x = operandArray[i].getFConst(); float length = VectorLength(operandArray, objectSize); if (length != 0.0f) resultArray[i].setFConst(x / length); else UndefinedConstantFoldingError(getLine(), function, getType().getBasicType(), diagnostics, &resultArray[i]); break; } case EOpBitfieldReverse: { uint32_t value; if (getType().getBasicType() == EbtInt) { value = static_cast(operandArray[i].getIConst()); } else { ASSERT(getType().getBasicType() == EbtUInt); value = operandArray[i].getUConst(); } uint32_t result = gl::BitfieldReverse(value); if (getType().getBasicType() == EbtInt) { resultArray[i].setIConst(static_cast(result)); } else { resultArray[i].setUConst(result); } break; } case EOpBitCount: { uint32_t value; if (getType().getBasicType() == EbtInt) { value = static_cast(operandArray[i].getIConst()); } else { ASSERT(getType().getBasicType() == EbtUInt); value = operandArray[i].getUConst(); } int result = gl::BitCount(value); resultArray[i].setIConst(result); break; } case EOpFindLSB: { uint32_t value; if (getType().getBasicType() == EbtInt) { value = static_cast(operandArray[i].getIConst()); } else { ASSERT(getType().getBasicType() == EbtUInt); value = operandArray[i].getUConst(); } resultArray[i].setIConst(gl::FindLSB(value)); break; } case EOpFindMSB: { uint32_t value; if (getType().getBasicType() == EbtInt) { int intValue = operandArray[i].getIConst(); value = static_cast(intValue); if (intValue < 0) { // Look for zero instead of one in value. This also handles the intValue == // -1 special case, where the return value needs to be -1. value = ~value; } } else { ASSERT(getType().getBasicType() == EbtUInt); value = operandArray[i].getUConst(); } resultArray[i].setIConst(gl::FindMSB(value)); break; } default: return nullptr; } } return resultArray; } void TIntermConstantUnion::foldFloatTypeUnary(const TConstantUnion ¶meter, FloatTypeUnaryFunc builtinFunc, TConstantUnion *result) const { ASSERT(builtinFunc); ASSERT(getType().getBasicType() == EbtFloat); result->setFConst(builtinFunc(parameter.getFConst())); } void TIntermConstantUnion::propagatePrecision(TPrecision precision) { mType.setPrecision(precision); } // static TConstantUnion *TIntermConstantUnion::FoldAggregateBuiltIn(TIntermAggregate *aggregate, TDiagnostics *diagnostics) { const TOperator op = aggregate->getOp(); const TFunction *function = aggregate->getFunction(); TIntermSequence *arguments = aggregate->getSequence(); unsigned int argsCount = static_cast(arguments->size()); std::vector unionArrays(argsCount); std::vector objectSizes(argsCount); size_t maxObjectSize = 0; TBasicType basicType = EbtVoid; TSourceLoc loc; for (unsigned int i = 0; i < argsCount; i++) { TIntermConstantUnion *argConstant = (*arguments)[i]->getAsConstantUnion(); ASSERT(argConstant != nullptr); // Should be checked already. if (i == 0) { basicType = argConstant->getType().getBasicType(); loc = argConstant->getLine(); } unionArrays[i] = argConstant->getConstantValue(); objectSizes[i] = argConstant->getType().getObjectSize(); if (objectSizes[i] > maxObjectSize) maxObjectSize = objectSizes[i]; } if (!(*arguments)[0]->getAsTyped()->isMatrix() && aggregate->getOp() != EOpOuterProduct) { for (unsigned int i = 0; i < argsCount; i++) if (objectSizes[i] != maxObjectSize) unionArrays[i] = Vectorize(*unionArrays[i], maxObjectSize); } TConstantUnion *resultArray = nullptr; switch (op) { case EOpAtan: { ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { float y = unionArrays[0][i].getFConst(); float x = unionArrays[1][i].getFConst(); // Results are undefined if x and y are both 0. if (x == 0.0f && y == 0.0f) UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); else resultArray[i].setFConst(atan2f(y, x)); } break; } case EOpPow: { ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { float x = unionArrays[0][i].getFConst(); float y = unionArrays[1][i].getFConst(); // Results are undefined if x < 0. // Results are undefined if x = 0 and y <= 0. if (x < 0.0f) UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); else if (x == 0.0f && y <= 0.0f) UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); else resultArray[i].setFConst(powf(x, y)); } break; } case EOpMod: { ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { float x = unionArrays[0][i].getFConst(); float y = unionArrays[1][i].getFConst(); resultArray[i].setFConst(x - y * floorf(x / y)); } break; } case EOpMin: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setFConst( std::min(unionArrays[0][i].getFConst(), unionArrays[1][i].getFConst())); break; case EbtInt: resultArray[i].setIConst( std::min(unionArrays[0][i].getIConst(), unionArrays[1][i].getIConst())); break; case EbtUInt: resultArray[i].setUConst( std::min(unionArrays[0][i].getUConst(), unionArrays[1][i].getUConst())); break; default: UNREACHABLE(); break; } } break; } case EOpMax: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setFConst( std::max(unionArrays[0][i].getFConst(), unionArrays[1][i].getFConst())); break; case EbtInt: resultArray[i].setIConst( std::max(unionArrays[0][i].getIConst(), unionArrays[1][i].getIConst())); break; case EbtUInt: resultArray[i].setUConst( std::max(unionArrays[0][i].getUConst(), unionArrays[1][i].getUConst())); break; default: UNREACHABLE(); break; } } break; } case EOpStep: { ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) resultArray[i].setFConst( unionArrays[1][i].getFConst() < unionArrays[0][i].getFConst() ? 0.0f : 1.0f); break; } case EOpLessThanComponentWise: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setBConst(unionArrays[0][i].getFConst() < unionArrays[1][i].getFConst()); break; case EbtInt: resultArray[i].setBConst(unionArrays[0][i].getIConst() < unionArrays[1][i].getIConst()); break; case EbtUInt: resultArray[i].setBConst(unionArrays[0][i].getUConst() < unionArrays[1][i].getUConst()); break; default: UNREACHABLE(); break; } } break; } case EOpLessThanEqualComponentWise: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setBConst(unionArrays[0][i].getFConst() <= unionArrays[1][i].getFConst()); break; case EbtInt: resultArray[i].setBConst(unionArrays[0][i].getIConst() <= unionArrays[1][i].getIConst()); break; case EbtUInt: resultArray[i].setBConst(unionArrays[0][i].getUConst() <= unionArrays[1][i].getUConst()); break; default: UNREACHABLE(); break; } } break; } case EOpGreaterThanComponentWise: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setBConst(unionArrays[0][i].getFConst() > unionArrays[1][i].getFConst()); break; case EbtInt: resultArray[i].setBConst(unionArrays[0][i].getIConst() > unionArrays[1][i].getIConst()); break; case EbtUInt: resultArray[i].setBConst(unionArrays[0][i].getUConst() > unionArrays[1][i].getUConst()); break; default: UNREACHABLE(); break; } } break; } case EOpGreaterThanEqualComponentWise: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setBConst(unionArrays[0][i].getFConst() >= unionArrays[1][i].getFConst()); break; case EbtInt: resultArray[i].setBConst(unionArrays[0][i].getIConst() >= unionArrays[1][i].getIConst()); break; case EbtUInt: resultArray[i].setBConst(unionArrays[0][i].getUConst() >= unionArrays[1][i].getUConst()); break; default: UNREACHABLE(); break; } } } break; case EOpEqualComponentWise: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setBConst(unionArrays[0][i].getFConst() == unionArrays[1][i].getFConst()); break; case EbtInt: resultArray[i].setBConst(unionArrays[0][i].getIConst() == unionArrays[1][i].getIConst()); break; case EbtUInt: resultArray[i].setBConst(unionArrays[0][i].getUConst() == unionArrays[1][i].getUConst()); break; case EbtBool: resultArray[i].setBConst(unionArrays[0][i].getBConst() == unionArrays[1][i].getBConst()); break; default: UNREACHABLE(); break; } } break; } case EOpNotEqualComponentWise: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: resultArray[i].setBConst(unionArrays[0][i].getFConst() != unionArrays[1][i].getFConst()); break; case EbtInt: resultArray[i].setBConst(unionArrays[0][i].getIConst() != unionArrays[1][i].getIConst()); break; case EbtUInt: resultArray[i].setBConst(unionArrays[0][i].getUConst() != unionArrays[1][i].getUConst()); break; case EbtBool: resultArray[i].setBConst(unionArrays[0][i].getBConst() != unionArrays[1][i].getBConst()); break; default: UNREACHABLE(); break; } } break; } case EOpDistance: { ASSERT(basicType == EbtFloat); TConstantUnion *distanceArray = new TConstantUnion[maxObjectSize]; resultArray = new TConstantUnion(); for (size_t i = 0; i < maxObjectSize; i++) { float x = unionArrays[0][i].getFConst(); float y = unionArrays[1][i].getFConst(); distanceArray[i].setFConst(x - y); } resultArray->setFConst(VectorLength(distanceArray, maxObjectSize)); break; } case EOpDot: ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion(); resultArray->setFConst(VectorDotProduct(unionArrays[0], unionArrays[1], maxObjectSize)); break; case EOpCross: { ASSERT(basicType == EbtFloat && maxObjectSize == 3); resultArray = new TConstantUnion[maxObjectSize]; float x0 = unionArrays[0][0].getFConst(); float x1 = unionArrays[0][1].getFConst(); float x2 = unionArrays[0][2].getFConst(); float y0 = unionArrays[1][0].getFConst(); float y1 = unionArrays[1][1].getFConst(); float y2 = unionArrays[1][2].getFConst(); resultArray[0].setFConst(x1 * y2 - y1 * x2); resultArray[1].setFConst(x2 * y0 - y2 * x0); resultArray[2].setFConst(x0 * y1 - y0 * x1); break; } case EOpReflect: { ASSERT(basicType == EbtFloat); // genType reflect (genType I, genType N) : // For the incident vector I and surface orientation N, returns the reflection // direction: // I - 2 * dot(N, I) * N. resultArray = new TConstantUnion[maxObjectSize]; float dotProduct = VectorDotProduct(unionArrays[1], unionArrays[0], maxObjectSize); for (size_t i = 0; i < maxObjectSize; i++) { float result = unionArrays[0][i].getFConst() - 2.0f * dotProduct * unionArrays[1][i].getFConst(); resultArray[i].setFConst(result); } break; } case EOpMatrixCompMult: { ASSERT(basicType == EbtFloat && (*arguments)[0]->getAsTyped()->isMatrix() && (*arguments)[1]->getAsTyped()->isMatrix()); // Perform component-wise matrix multiplication. resultArray = new TConstantUnion[maxObjectSize]; const uint8_t rows = (*arguments)[0]->getAsTyped()->getRows(); const uint8_t cols = (*arguments)[0]->getAsTyped()->getCols(); angle::Matrix lhs = GetMatrix(unionArrays[0], rows, cols); angle::Matrix rhs = GetMatrix(unionArrays[1], rows, cols); angle::Matrix result = lhs.compMult(rhs); SetUnionArrayFromMatrix(result, resultArray); break; } case EOpOuterProduct: { ASSERT(basicType == EbtFloat); size_t numRows = (*arguments)[0]->getAsTyped()->getType().getObjectSize(); size_t numCols = (*arguments)[1]->getAsTyped()->getType().getObjectSize(); resultArray = new TConstantUnion[numRows * numCols]; angle::Matrix result = GetMatrix(unionArrays[0], static_cast(numRows), 1) .outerProduct(GetMatrix(unionArrays[1], 1, static_cast(numCols))); SetUnionArrayFromMatrix(result, resultArray); break; } case EOpClamp: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { switch (basicType) { case EbtFloat: { float x = unionArrays[0][i].getFConst(); float min = unionArrays[1][i].getFConst(); float max = unionArrays[2][i].getFConst(); // Results are undefined if min > max. if (min > max) UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); else resultArray[i].setFConst(gl::clamp(x, min, max)); break; } case EbtInt: { int x = unionArrays[0][i].getIConst(); int min = unionArrays[1][i].getIConst(); int max = unionArrays[2][i].getIConst(); // Results are undefined if min > max. if (min > max) UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); else resultArray[i].setIConst(gl::clamp(x, min, max)); break; } case EbtUInt: { unsigned int x = unionArrays[0][i].getUConst(); unsigned int min = unionArrays[1][i].getUConst(); unsigned int max = unionArrays[2][i].getUConst(); // Results are undefined if min > max. if (min > max) UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); else resultArray[i].setUConst(gl::clamp(x, min, max)); break; } default: UNREACHABLE(); break; } } break; } case EOpMix: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { TBasicType type = (*arguments)[2]->getAsTyped()->getType().getBasicType(); if (type == EbtFloat) { ASSERT(basicType == EbtFloat); float x = unionArrays[0][i].getFConst(); float y = unionArrays[1][i].getFConst(); // Returns the linear blend of x and y, i.e., x * (1 - a) + y * a. float a = unionArrays[2][i].getFConst(); resultArray[i].setFConst(x * (1.0f - a) + y * a); } else // 3rd parameter is EbtBool { ASSERT(type == EbtBool); // Selects which vector each returned component comes from. // For a component of a that is false, the corresponding component of x is // returned. // For a component of a that is true, the corresponding component of y is // returned. bool a = unionArrays[2][i].getBConst(); switch (basicType) { case EbtFloat: { float x = unionArrays[0][i].getFConst(); float y = unionArrays[1][i].getFConst(); resultArray[i].setFConst(a ? y : x); } break; case EbtInt: { int x = unionArrays[0][i].getIConst(); int y = unionArrays[1][i].getIConst(); resultArray[i].setIConst(a ? y : x); } break; case EbtUInt: { unsigned int x = unionArrays[0][i].getUConst(); unsigned int y = unionArrays[1][i].getUConst(); resultArray[i].setUConst(a ? y : x); } break; case EbtBool: { bool x = unionArrays[0][i].getBConst(); bool y = unionArrays[1][i].getBConst(); resultArray[i].setBConst(a ? y : x); } break; default: UNREACHABLE(); break; } } } break; } case EOpSmoothstep: { ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { float edge0 = unionArrays[0][i].getFConst(); float edge1 = unionArrays[1][i].getFConst(); float x = unionArrays[2][i].getFConst(); // Results are undefined if edge0 >= edge1. if (edge0 >= edge1) { UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); } else { // Returns 0.0 if x <= edge0 and 1.0 if x >= edge1 and performs smooth // Hermite interpolation between 0 and 1 when edge0 < x < edge1. float t = gl::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); resultArray[i].setFConst(t * t * (3.0f - 2.0f * t)); } } break; } case EOpFma: { ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { float a = unionArrays[0][i].getFConst(); float b = unionArrays[1][i].getFConst(); float c = unionArrays[2][i].getFConst(); // Returns a * b + c. resultArray[i].setFConst(a * b + c); } break; } case EOpLdexp: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { float x = unionArrays[0][i].getFConst(); int exp = unionArrays[1][i].getIConst(); if (exp > 128) { UndefinedConstantFoldingError(loc, function, basicType, diagnostics, &resultArray[i]); } else { resultArray[i].setFConst(gl::Ldexp(x, exp)); } } break; } case EOpFaceforward: { ASSERT(basicType == EbtFloat); // genType faceforward(genType N, genType I, genType Nref) : // If dot(Nref, I) < 0 return N, otherwise return -N. resultArray = new TConstantUnion[maxObjectSize]; float dotProduct = VectorDotProduct(unionArrays[2], unionArrays[1], maxObjectSize); for (size_t i = 0; i < maxObjectSize; i++) { if (dotProduct < 0) resultArray[i].setFConst(unionArrays[0][i].getFConst()); else resultArray[i].setFConst(-unionArrays[0][i].getFConst()); } break; } case EOpRefract: { ASSERT(basicType == EbtFloat); // genType refract(genType I, genType N, float eta) : // For the incident vector I and surface normal N, and the ratio of indices of // refraction eta, // return the refraction vector. The result is computed by // k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I)) // if (k < 0.0) // return genType(0.0) // else // return eta * I - (eta * dot(N, I) + sqrt(k)) * N resultArray = new TConstantUnion[maxObjectSize]; float dotProduct = VectorDotProduct(unionArrays[1], unionArrays[0], maxObjectSize); for (size_t i = 0; i < maxObjectSize; i++) { float eta = unionArrays[2][i].getFConst(); float k = 1.0f - eta * eta * (1.0f - dotProduct * dotProduct); if (k < 0.0f) resultArray[i].setFConst(0.0f); else resultArray[i].setFConst(eta * unionArrays[0][i].getFConst() - (eta * dotProduct + sqrtf(k)) * unionArrays[1][i].getFConst()); } break; } case EOpBitfieldExtract: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; ++i) { int offset = unionArrays[1][0].getIConst(); int bits = unionArrays[2][0].getIConst(); if (bits == 0) { if (aggregate->getBasicType() == EbtInt) { resultArray[i].setIConst(0); } else { ASSERT(aggregate->getBasicType() == EbtUInt); resultArray[i].setUConst(0); } } else if (offset < 0 || bits < 0 || offset >= 32 || bits > 32 || offset + bits > 32) { UndefinedConstantFoldingError(loc, function, aggregate->getBasicType(), diagnostics, &resultArray[i]); } else { // bits can be 32 here, so we need to avoid bit shift overflow. uint32_t maskMsb = 1u << (bits - 1); uint32_t mask = ((maskMsb - 1u) | maskMsb) << offset; if (aggregate->getBasicType() == EbtInt) { uint32_t value = static_cast(unionArrays[0][i].getIConst()); uint32_t resultUnsigned = (value & mask) >> offset; if ((resultUnsigned & maskMsb) != 0) { // The most significant bits (from bits+1 to the most significant bit) // should be set to 1. uint32_t higherBitsMask = ((1u << (32 - bits)) - 1u) << bits; resultUnsigned |= higherBitsMask; } resultArray[i].setIConst(static_cast(resultUnsigned)); } else { ASSERT(aggregate->getBasicType() == EbtUInt); uint32_t value = unionArrays[0][i].getUConst(); resultArray[i].setUConst((value & mask) >> offset); } } } break; } case EOpBitfieldInsert: { resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; ++i) { int offset = unionArrays[2][0].getIConst(); int bits = unionArrays[3][0].getIConst(); if (bits == 0) { if (aggregate->getBasicType() == EbtInt) { int32_t base = unionArrays[0][i].getIConst(); resultArray[i].setIConst(base); } else { ASSERT(aggregate->getBasicType() == EbtUInt); uint32_t base = unionArrays[0][i].getUConst(); resultArray[i].setUConst(base); } } else if (offset < 0 || bits < 0 || offset >= 32 || bits > 32 || offset + bits > 32) { UndefinedConstantFoldingError(loc, function, aggregate->getBasicType(), diagnostics, &resultArray[i]); } else { // bits can be 32 here, so we need to avoid bit shift overflow. uint32_t maskMsb = 1u << (bits - 1); uint32_t insertMask = ((maskMsb - 1u) | maskMsb) << offset; uint32_t baseMask = ~insertMask; if (aggregate->getBasicType() == EbtInt) { uint32_t base = static_cast(unionArrays[0][i].getIConst()); uint32_t insert = static_cast(unionArrays[1][i].getIConst()); uint32_t resultUnsigned = (base & baseMask) | ((insert << offset) & insertMask); resultArray[i].setIConst(static_cast(resultUnsigned)); } else { ASSERT(aggregate->getBasicType() == EbtUInt); uint32_t base = unionArrays[0][i].getUConst(); uint32_t insert = unionArrays[1][i].getUConst(); resultArray[i].setUConst((base & baseMask) | ((insert << offset) & insertMask)); } } } break; } case EOpDFdx: case EOpDFdy: case EOpFwidth: ASSERT(basicType == EbtFloat); resultArray = new TConstantUnion[maxObjectSize]; for (size_t i = 0; i < maxObjectSize; i++) { // Derivatives of constant arguments should be 0. resultArray[i].setFConst(0.0f); } break; default: UNREACHABLE(); return nullptr; } return resultArray; } bool TIntermConstantUnion::IsFloatDivision(TBasicType t1, TBasicType t2) { ImplicitTypeConversion conversion = GetConversion(t1, t2); ASSERT(conversion != ImplicitTypeConversion::Invalid); if (conversion == ImplicitTypeConversion::Same) { if (t1 == EbtFloat) return true; return false; } ASSERT(t1 == EbtFloat || t2 == EbtFloat); return true; } // TIntermPreprocessorDirective implementation. TIntermPreprocessorDirective::TIntermPreprocessorDirective(PreprocessorDirective directive, ImmutableString command) : mDirective(directive), mCommand(std::move(command)) {} TIntermPreprocessorDirective::TIntermPreprocessorDirective(const TIntermPreprocessorDirective &node) : TIntermPreprocessorDirective(node.mDirective, node.mCommand) {} TIntermPreprocessorDirective::~TIntermPreprocessorDirective() = default; size_t TIntermPreprocessorDirective::getChildCount() const { return 0; } TIntermNode *TIntermPreprocessorDirective::getChildNode(size_t index) const { UNREACHABLE(); return nullptr; } } // namespace sh