// // Copyright (C) 2002-2005 3Dlabs Inc. Ltd. // Copyright (C) 2012-2015 LunarG, Inc. // Copyright (C) 2015-2020 Google, Inc. // Copyright (C) 2017 ARM Limited. // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // // Neither the name of 3Dlabs Inc. Ltd. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // // Build the intermediate representation. // #include "localintermediate.h" #include "RemoveTree.h" #include "SymbolTable.h" #include "propagateNoContraction.h" #include #include #include namespace glslang { //////////////////////////////////////////////////////////////////////////// // // First set of functions are to help build the intermediate representation. // These functions are not member functions of the nodes. // They are called from parser productions. // ///////////////////////////////////////////////////////////////////////////// // // Add a terminal node for an identifier in an expression. // // Returns the added node. // TIntermSymbol* TIntermediate::addSymbol(long long id, const TString& name, const TType& type, const TConstUnionArray& constArray, TIntermTyped* constSubtree, const TSourceLoc& loc) { TIntermSymbol* node = new TIntermSymbol(id, name, type); node->setLoc(loc); node->setConstArray(constArray); node->setConstSubtree(constSubtree); return node; } TIntermSymbol* TIntermediate::addSymbol(const TIntermSymbol& intermSymbol) { return addSymbol(intermSymbol.getId(), intermSymbol.getName(), intermSymbol.getType(), intermSymbol.getConstArray(), intermSymbol.getConstSubtree(), intermSymbol.getLoc()); } TIntermSymbol* TIntermediate::addSymbol(const TVariable& variable) { glslang::TSourceLoc loc; // just a null location loc.init(); return addSymbol(variable, loc); } TIntermSymbol* TIntermediate::addSymbol(const TVariable& variable, const TSourceLoc& loc) { return addSymbol(variable.getUniqueId(), variable.getName(), variable.getType(), variable.getConstArray(), variable.getConstSubtree(), loc); } TIntermSymbol* TIntermediate::addSymbol(const TType& type, const TSourceLoc& loc) { TConstUnionArray unionArray; // just a null constant return addSymbol(0, "", type, unionArray, nullptr, loc); } // // Connect two nodes with a new parent that does a binary operation on the nodes. // // Returns the added node. // // Returns nullptr if the working conversions and promotions could not be found. // TIntermTyped* TIntermediate::addBinaryMath(TOperator op, TIntermTyped* left, TIntermTyped* right, const TSourceLoc& loc) { // No operations work on blocks if (left->getType().getBasicType() == EbtBlock || right->getType().getBasicType() == EbtBlock) return nullptr; // Convert "reference +/- int" and "reference - reference" to integer math if (op == EOpAdd || op == EOpSub) { // No addressing math on struct with unsized array. if ((left->isReference() && left->getType().getReferentType()->containsUnsizedArray()) || (right->isReference() && right->getType().getReferentType()->containsUnsizedArray())) { return nullptr; } if (left->isReference() && isTypeInt(right->getBasicType())) { const TType& referenceType = left->getType(); TIntermConstantUnion* size = addConstantUnion((unsigned long long)computeBufferReferenceTypeSize(left->getType()), loc, true); left = addBuiltInFunctionCall(loc, EOpConvPtrToUint64, true, left, TType(EbtUint64)); right = createConversion(EbtInt64, right); right = addBinaryMath(EOpMul, right, size, loc); TIntermTyped *node = addBinaryMath(op, left, right, loc); node = addBuiltInFunctionCall(loc, EOpConvUint64ToPtr, true, node, referenceType); return node; } } if (op == EOpAdd && right->isReference() && isTypeInt(left->getBasicType())) { const TType& referenceType = right->getType(); TIntermConstantUnion* size = addConstantUnion((unsigned long long)computeBufferReferenceTypeSize(right->getType()), loc, true); right = addBuiltInFunctionCall(loc, EOpConvPtrToUint64, true, right, TType(EbtUint64)); left = createConversion(EbtInt64, left); left = addBinaryMath(EOpMul, left, size, loc); TIntermTyped *node = addBinaryMath(op, left, right, loc); node = addBuiltInFunctionCall(loc, EOpConvUint64ToPtr, true, node, referenceType); return node; } if (op == EOpSub && left->isReference() && right->isReference()) { TIntermConstantUnion* size = addConstantUnion((long long)computeBufferReferenceTypeSize(left->getType()), loc, true); left = addBuiltInFunctionCall(loc, EOpConvPtrToUint64, true, left, TType(EbtUint64)); right = addBuiltInFunctionCall(loc, EOpConvPtrToUint64, true, right, TType(EbtUint64)); left = addBuiltInFunctionCall(loc, EOpConvUint64ToInt64, true, left, TType(EbtInt64)); right = addBuiltInFunctionCall(loc, EOpConvUint64ToInt64, true, right, TType(EbtInt64)); left = addBinaryMath(EOpSub, left, right, loc); TIntermTyped *node = addBinaryMath(EOpDiv, left, size, loc); return node; } // No other math operators supported on references if (left->isReference() || right->isReference()) return nullptr; // Try converting the children's base types to compatible types. auto children = addPairConversion(op, left, right); left = std::get<0>(children); right = std::get<1>(children); if (left == nullptr || right == nullptr) return nullptr; // Convert the children's type shape to be compatible. addBiShapeConversion(op, left, right); if (left == nullptr || right == nullptr) return nullptr; // // Need a new node holding things together. Make // one and promote it to the right type. // TIntermBinary* node = addBinaryNode(op, left, right, loc); if (! promote(node)) return nullptr; node->updatePrecision(); // // If they are both (non-specialization) constants, they must be folded. // (Unless it's the sequence (comma) operator, but that's handled in addComma().) // TIntermConstantUnion *leftTempConstant = node->getLeft()->getAsConstantUnion(); TIntermConstantUnion *rightTempConstant = node->getRight()->getAsConstantUnion(); if (leftTempConstant && rightTempConstant) { TIntermTyped* folded = leftTempConstant->fold(node->getOp(), rightTempConstant); if (folded) return folded; } // If can propagate spec-constantness and if the operation is an allowed // specialization-constant operation, make a spec-constant. if (specConstantPropagates(*node->getLeft(), *node->getRight()) && isSpecializationOperation(*node)) node->getWritableType().getQualifier().makeSpecConstant(); // If must propagate nonuniform, make a nonuniform. if ((node->getLeft()->getQualifier().isNonUniform() || node->getRight()->getQualifier().isNonUniform()) && isNonuniformPropagating(node->getOp())) node->getWritableType().getQualifier().nonUniform = true; return node; } // // Low level: add binary node (no promotions or other argument modifications) // TIntermBinary* TIntermediate::addBinaryNode(TOperator op, TIntermTyped* left, TIntermTyped* right, const TSourceLoc& loc) const { // build the node TIntermBinary* node = new TIntermBinary(op); node->setLoc(loc.line != 0 ? loc : left->getLoc()); node->setLeft(left); node->setRight(right); return node; } // // like non-type form, but sets node's type. // TIntermBinary* TIntermediate::addBinaryNode(TOperator op, TIntermTyped* left, TIntermTyped* right, const TSourceLoc& loc, const TType& type) const { TIntermBinary* node = addBinaryNode(op, left, right, loc); node->setType(type); return node; } // // Low level: add unary node (no promotions or other argument modifications) // TIntermUnary* TIntermediate::addUnaryNode(TOperator op, TIntermTyped* child, const TSourceLoc& loc) const { TIntermUnary* node = new TIntermUnary(op); node->setLoc(loc.line != 0 ? loc : child->getLoc()); node->setOperand(child); return node; } // // like non-type form, but sets node's type. // TIntermUnary* TIntermediate::addUnaryNode(TOperator op, TIntermTyped* child, const TSourceLoc& loc, const TType& type) const { TIntermUnary* node = addUnaryNode(op, child, loc); node->setType(type); return node; } // // Connect two nodes through an assignment. // // Returns the added node. // // Returns nullptr if the 'right' type could not be converted to match the 'left' type, // or the resulting operation cannot be properly promoted. // TIntermTyped* TIntermediate::addAssign(TOperator op, TIntermTyped* left, TIntermTyped* right, const TSourceLoc& loc) { // No block assignment if (left->getType().getBasicType() == EbtBlock || right->getType().getBasicType() == EbtBlock) return nullptr; // Convert "reference += int" to "reference = reference + int". We need this because the // "reference + int" calculation involves a cast back to the original type, which makes it // not an lvalue. if ((op == EOpAddAssign || op == EOpSubAssign) && left->isReference()) { if (!(right->getType().isScalar() && right->getType().isIntegerDomain())) return nullptr; TIntermTyped* node = addBinaryMath(op == EOpAddAssign ? EOpAdd : EOpSub, left, right, loc); if (!node) return nullptr; TIntermSymbol* symbol = left->getAsSymbolNode(); left = addSymbol(*symbol); node = addAssign(EOpAssign, left, node, loc); return node; } // // Like adding binary math, except the conversion can only go // from right to left. // // convert base types, nullptr return means not possible right = addConversion(op, left->getType(), right); if (right == nullptr) return nullptr; // convert shape right = addUniShapeConversion(op, left->getType(), right); // build the node TIntermBinary* node = addBinaryNode(op, left, right, loc); if (! promote(node)) return nullptr; node->updatePrecision(); return node; } // // Connect two nodes through an index operator, where the left node is the base // of an array or struct, and the right node is a direct or indirect offset. // // Returns the added node. // The caller should set the type of the returned node. // TIntermTyped* TIntermediate::addIndex(TOperator op, TIntermTyped* base, TIntermTyped* index, const TSourceLoc& loc) { // caller should set the type return addBinaryNode(op, base, index, loc); } // // Add one node as the parent of another that it operates on. // // Returns the added node. // TIntermTyped* TIntermediate::addUnaryMath(TOperator op, TIntermTyped* child, const TSourceLoc& loc) { if (child == 0) return nullptr; if (child->getType().getBasicType() == EbtBlock) return nullptr; switch (op) { case EOpLogicalNot: if (getSource() == EShSourceHlsl) { break; // HLSL can promote logical not } if (child->getType().getBasicType() != EbtBool || child->getType().isMatrix() || child->getType().isArray() || child->getType().isVector()) { return nullptr; } break; case EOpPostIncrement: case EOpPreIncrement: case EOpPostDecrement: case EOpPreDecrement: case EOpNegative: if (child->getType().getBasicType() == EbtStruct || child->getType().isArray()) return nullptr; default: break; // some compilers want this } // // Do we need to promote the operand? // TBasicType newType = EbtVoid; switch (op) { case EOpConstructBool: newType = EbtBool; break; case EOpConstructFloat: newType = EbtFloat; break; case EOpConstructInt: newType = EbtInt; break; case EOpConstructUint: newType = EbtUint; break; #ifndef GLSLANG_WEB case EOpConstructInt8: newType = EbtInt8; break; case EOpConstructUint8: newType = EbtUint8; break; case EOpConstructInt16: newType = EbtInt16; break; case EOpConstructUint16: newType = EbtUint16; break; case EOpConstructInt64: newType = EbtInt64; break; case EOpConstructUint64: newType = EbtUint64; break; case EOpConstructDouble: newType = EbtDouble; break; case EOpConstructFloat16: newType = EbtFloat16; break; #endif default: break; // some compilers want this } if (newType != EbtVoid) { child = addConversion(op, TType(newType, EvqTemporary, child->getVectorSize(), child->getMatrixCols(), child->getMatrixRows(), child->isVector()), child); if (child == nullptr) return nullptr; } // // For constructors, we are now done, it was all in the conversion. // TODO: but, did this bypass constant folding? // switch (op) { case EOpConstructInt8: case EOpConstructUint8: case EOpConstructInt16: case EOpConstructUint16: case EOpConstructInt: case EOpConstructUint: case EOpConstructInt64: case EOpConstructUint64: case EOpConstructBool: case EOpConstructFloat: case EOpConstructDouble: case EOpConstructFloat16: return child; default: break; // some compilers want this } // // Make a new node for the operator. // TIntermUnary* node = addUnaryNode(op, child, loc); if (! promote(node)) return nullptr; node->updatePrecision(); // If it's a (non-specialization) constant, it must be folded. if (node->getOperand()->getAsConstantUnion()) return node->getOperand()->getAsConstantUnion()->fold(op, node->getType()); // If it's a specialization constant, the result is too, // if the operation is allowed for specialization constants. if (node->getOperand()->getType().getQualifier().isSpecConstant() && isSpecializationOperation(*node)) node->getWritableType().getQualifier().makeSpecConstant(); // If must propagate nonuniform, make a nonuniform. if (node->getOperand()->getQualifier().isNonUniform() && isNonuniformPropagating(node->getOp())) node->getWritableType().getQualifier().nonUniform = true; return node; } TIntermTyped* TIntermediate::addBuiltInFunctionCall(const TSourceLoc& loc, TOperator op, bool unary, TIntermNode* childNode, const TType& returnType) { if (unary) { // // Treat it like a unary operator. // addUnaryMath() should get the type correct on its own; // including constness (which would differ from the prototype). // TIntermTyped* child = childNode->getAsTyped(); if (child == nullptr) return nullptr; if (child->getAsConstantUnion()) { TIntermTyped* folded = child->getAsConstantUnion()->fold(op, returnType); if (folded) return folded; } return addUnaryNode(op, child, child->getLoc(), returnType); } else { // setAggregateOperater() calls fold() for constant folding TIntermTyped* node = setAggregateOperator(childNode, op, returnType, loc); return node; } } // // This is the safe way to change the operator on an aggregate, as it // does lots of error checking and fixing. Especially for establishing // a function call's operation on its set of parameters. Sequences // of instructions are also aggregates, but they just directly set // their operator to EOpSequence. // // Returns an aggregate node, which could be the one passed in if // it was already an aggregate. // TIntermTyped* TIntermediate::setAggregateOperator(TIntermNode* node, TOperator op, const TType& type, const TSourceLoc& loc) { TIntermAggregate* aggNode; // // Make sure we have an aggregate. If not turn it into one. // if (node != nullptr) { aggNode = node->getAsAggregate(); if (aggNode == nullptr || aggNode->getOp() != EOpNull) { // // Make an aggregate containing this node. // aggNode = new TIntermAggregate(); aggNode->getSequence().push_back(node); } } else aggNode = new TIntermAggregate(); // // Set the operator. // aggNode->setOperator(op); if (loc.line != 0 || node != nullptr) aggNode->setLoc(loc.line != 0 ? loc : node->getLoc()); aggNode->setType(type); return fold(aggNode); } bool TIntermediate::isConversionAllowed(TOperator op, TIntermTyped* node) const { // // Does the base type even allow the operation? // switch (node->getBasicType()) { case EbtVoid: return false; case EbtAtomicUint: case EbtSampler: case EbtAccStruct: // opaque types can be passed to functions if (op == EOpFunction) break; // HLSL can assign samplers directly (no constructor) if (getSource() == EShSourceHlsl && node->getBasicType() == EbtSampler) break; // samplers can get assigned via a sampler constructor // (well, not yet, but code in the rest of this function is ready for it) if (node->getBasicType() == EbtSampler && op == EOpAssign && node->getAsOperator() != nullptr && node->getAsOperator()->getOp() == EOpConstructTextureSampler) break; // otherwise, opaque types can't even be operated on, let alone converted return false; default: break; } return true; } bool TIntermediate::buildConvertOp(TBasicType dst, TBasicType src, TOperator& newOp) const { switch (dst) { #ifndef GLSLANG_WEB case EbtDouble: switch (src) { case EbtUint: newOp = EOpConvUintToDouble; break; case EbtBool: newOp = EOpConvBoolToDouble; break; case EbtFloat: newOp = EOpConvFloatToDouble; break; case EbtInt: newOp = EOpConvIntToDouble; break; case EbtInt8: newOp = EOpConvInt8ToDouble; break; case EbtUint8: newOp = EOpConvUint8ToDouble; break; case EbtInt16: newOp = EOpConvInt16ToDouble; break; case EbtUint16: newOp = EOpConvUint16ToDouble; break; case EbtFloat16: newOp = EOpConvFloat16ToDouble; break; case EbtInt64: newOp = EOpConvInt64ToDouble; break; case EbtUint64: newOp = EOpConvUint64ToDouble; break; default: return false; } break; #endif case EbtFloat: switch (src) { case EbtInt: newOp = EOpConvIntToFloat; break; case EbtUint: newOp = EOpConvUintToFloat; break; case EbtBool: newOp = EOpConvBoolToFloat; break; #ifndef GLSLANG_WEB case EbtDouble: newOp = EOpConvDoubleToFloat; break; case EbtInt8: newOp = EOpConvInt8ToFloat; break; case EbtUint8: newOp = EOpConvUint8ToFloat; break; case EbtInt16: newOp = EOpConvInt16ToFloat; break; case EbtUint16: newOp = EOpConvUint16ToFloat; break; case EbtFloat16: newOp = EOpConvFloat16ToFloat; break; case EbtInt64: newOp = EOpConvInt64ToFloat; break; case EbtUint64: newOp = EOpConvUint64ToFloat; break; #endif default: return false; } break; #ifndef GLSLANG_WEB case EbtFloat16: switch (src) { case EbtInt8: newOp = EOpConvInt8ToFloat16; break; case EbtUint8: newOp = EOpConvUint8ToFloat16; break; case EbtInt16: newOp = EOpConvInt16ToFloat16; break; case EbtUint16: newOp = EOpConvUint16ToFloat16; break; case EbtInt: newOp = EOpConvIntToFloat16; break; case EbtUint: newOp = EOpConvUintToFloat16; break; case EbtBool: newOp = EOpConvBoolToFloat16; break; case EbtFloat: newOp = EOpConvFloatToFloat16; break; case EbtDouble: newOp = EOpConvDoubleToFloat16; break; case EbtInt64: newOp = EOpConvInt64ToFloat16; break; case EbtUint64: newOp = EOpConvUint64ToFloat16; break; default: return false; } break; #endif case EbtBool: switch (src) { case EbtInt: newOp = EOpConvIntToBool; break; case EbtUint: newOp = EOpConvUintToBool; break; case EbtFloat: newOp = EOpConvFloatToBool; break; #ifndef GLSLANG_WEB case EbtDouble: newOp = EOpConvDoubleToBool; break; case EbtInt8: newOp = EOpConvInt8ToBool; break; case EbtUint8: newOp = EOpConvUint8ToBool; break; case EbtInt16: newOp = EOpConvInt16ToBool; break; case EbtUint16: newOp = EOpConvUint16ToBool; break; case EbtFloat16: newOp = EOpConvFloat16ToBool; break; case EbtInt64: newOp = EOpConvInt64ToBool; break; case EbtUint64: newOp = EOpConvUint64ToBool; break; #endif default: return false; } break; #ifndef GLSLANG_WEB case EbtInt8: switch (src) { case EbtUint8: newOp = EOpConvUint8ToInt8; break; case EbtInt16: newOp = EOpConvInt16ToInt8; break; case EbtUint16: newOp = EOpConvUint16ToInt8; break; case EbtInt: newOp = EOpConvIntToInt8; break; case EbtUint: newOp = EOpConvUintToInt8; break; case EbtInt64: newOp = EOpConvInt64ToInt8; break; case EbtUint64: newOp = EOpConvUint64ToInt8; break; case EbtBool: newOp = EOpConvBoolToInt8; break; case EbtFloat: newOp = EOpConvFloatToInt8; break; case EbtDouble: newOp = EOpConvDoubleToInt8; break; case EbtFloat16: newOp = EOpConvFloat16ToInt8; break; default: return false; } break; case EbtUint8: switch (src) { case EbtInt8: newOp = EOpConvInt8ToUint8; break; case EbtInt16: newOp = EOpConvInt16ToUint8; break; case EbtUint16: newOp = EOpConvUint16ToUint8; break; case EbtInt: newOp = EOpConvIntToUint8; break; case EbtUint: newOp = EOpConvUintToUint8; break; case EbtInt64: newOp = EOpConvInt64ToUint8; break; case EbtUint64: newOp = EOpConvUint64ToUint8; break; case EbtBool: newOp = EOpConvBoolToUint8; break; case EbtFloat: newOp = EOpConvFloatToUint8; break; case EbtDouble: newOp = EOpConvDoubleToUint8; break; case EbtFloat16: newOp = EOpConvFloat16ToUint8; break; default: return false; } break; case EbtInt16: switch (src) { case EbtUint8: newOp = EOpConvUint8ToInt16; break; case EbtInt8: newOp = EOpConvInt8ToInt16; break; case EbtUint16: newOp = EOpConvUint16ToInt16; break; case EbtInt: newOp = EOpConvIntToInt16; break; case EbtUint: newOp = EOpConvUintToInt16; break; case EbtInt64: newOp = EOpConvInt64ToInt16; break; case EbtUint64: newOp = EOpConvUint64ToInt16; break; case EbtBool: newOp = EOpConvBoolToInt16; break; case EbtFloat: newOp = EOpConvFloatToInt16; break; case EbtDouble: newOp = EOpConvDoubleToInt16; break; case EbtFloat16: newOp = EOpConvFloat16ToInt16; break; default: return false; } break; case EbtUint16: switch (src) { case EbtInt8: newOp = EOpConvInt8ToUint16; break; case EbtUint8: newOp = EOpConvUint8ToUint16; break; case EbtInt16: newOp = EOpConvInt16ToUint16; break; case EbtInt: newOp = EOpConvIntToUint16; break; case EbtUint: newOp = EOpConvUintToUint16; break; case EbtInt64: newOp = EOpConvInt64ToUint16; break; case EbtUint64: newOp = EOpConvUint64ToUint16; break; case EbtBool: newOp = EOpConvBoolToUint16; break; case EbtFloat: newOp = EOpConvFloatToUint16; break; case EbtDouble: newOp = EOpConvDoubleToUint16; break; case EbtFloat16: newOp = EOpConvFloat16ToUint16; break; default: return false; } break; #endif case EbtInt: switch (src) { case EbtUint: newOp = EOpConvUintToInt; break; case EbtBool: newOp = EOpConvBoolToInt; break; case EbtFloat: newOp = EOpConvFloatToInt; break; #ifndef GLSLANG_WEB case EbtInt8: newOp = EOpConvInt8ToInt; break; case EbtUint8: newOp = EOpConvUint8ToInt; break; case EbtInt16: newOp = EOpConvInt16ToInt; break; case EbtUint16: newOp = EOpConvUint16ToInt; break; case EbtDouble: newOp = EOpConvDoubleToInt; break; case EbtFloat16: newOp = EOpConvFloat16ToInt; break; case EbtInt64: newOp = EOpConvInt64ToInt; break; case EbtUint64: newOp = EOpConvUint64ToInt; break; #endif default: return false; } break; case EbtUint: switch (src) { case EbtInt: newOp = EOpConvIntToUint; break; case EbtBool: newOp = EOpConvBoolToUint; break; case EbtFloat: newOp = EOpConvFloatToUint; break; #ifndef GLSLANG_WEB case EbtInt8: newOp = EOpConvInt8ToUint; break; case EbtUint8: newOp = EOpConvUint8ToUint; break; case EbtInt16: newOp = EOpConvInt16ToUint; break; case EbtUint16: newOp = EOpConvUint16ToUint; break; case EbtDouble: newOp = EOpConvDoubleToUint; break; case EbtFloat16: newOp = EOpConvFloat16ToUint; break; case EbtInt64: newOp = EOpConvInt64ToUint; break; case EbtUint64: newOp = EOpConvUint64ToUint; break; #endif default: return false; } break; #ifndef GLSLANG_WEB case EbtInt64: switch (src) { case EbtInt8: newOp = EOpConvInt8ToInt64; break; case EbtUint8: newOp = EOpConvUint8ToInt64; break; case EbtInt16: newOp = EOpConvInt16ToInt64; break; case EbtUint16: newOp = EOpConvUint16ToInt64; break; case EbtInt: newOp = EOpConvIntToInt64; break; case EbtUint: newOp = EOpConvUintToInt64; break; case EbtBool: newOp = EOpConvBoolToInt64; break; case EbtFloat: newOp = EOpConvFloatToInt64; break; case EbtDouble: newOp = EOpConvDoubleToInt64; break; case EbtFloat16: newOp = EOpConvFloat16ToInt64; break; case EbtUint64: newOp = EOpConvUint64ToInt64; break; default: return false; } break; case EbtUint64: switch (src) { case EbtInt8: newOp = EOpConvInt8ToUint64; break; case EbtUint8: newOp = EOpConvUint8ToUint64; break; case EbtInt16: newOp = EOpConvInt16ToUint64; break; case EbtUint16: newOp = EOpConvUint16ToUint64; break; case EbtInt: newOp = EOpConvIntToUint64; break; case EbtUint: newOp = EOpConvUintToUint64; break; case EbtBool: newOp = EOpConvBoolToUint64; break; case EbtFloat: newOp = EOpConvFloatToUint64; break; case EbtDouble: newOp = EOpConvDoubleToUint64; break; case EbtFloat16: newOp = EOpConvFloat16ToUint64; break; case EbtInt64: newOp = EOpConvInt64ToUint64; break; default: return false; } break; #endif default: return false; } return true; } // This is 'mechanism' here, it does any conversion told. // It is about basic type, not about shape. // The policy comes from the shader or the calling code. TIntermTyped* TIntermediate::createConversion(TBasicType convertTo, TIntermTyped* node) const { // // Add a new newNode for the conversion. // #ifndef GLSLANG_WEB bool convertToIntTypes = (convertTo == EbtInt8 || convertTo == EbtUint8 || convertTo == EbtInt16 || convertTo == EbtUint16 || convertTo == EbtInt || convertTo == EbtUint || convertTo == EbtInt64 || convertTo == EbtUint64); bool convertFromIntTypes = (node->getBasicType() == EbtInt8 || node->getBasicType() == EbtUint8 || node->getBasicType() == EbtInt16 || node->getBasicType() == EbtUint16 || node->getBasicType() == EbtInt || node->getBasicType() == EbtUint || node->getBasicType() == EbtInt64 || node->getBasicType() == EbtUint64); bool convertToFloatTypes = (convertTo == EbtFloat16 || convertTo == EbtFloat || convertTo == EbtDouble); bool convertFromFloatTypes = (node->getBasicType() == EbtFloat16 || node->getBasicType() == EbtFloat || node->getBasicType() == EbtDouble); if (((convertTo == EbtInt8 || convertTo == EbtUint8) && ! convertFromIntTypes) || ((node->getBasicType() == EbtInt8 || node->getBasicType() == EbtUint8) && ! convertToIntTypes)) { if (! getArithemeticInt8Enabled()) { return nullptr; } } if (((convertTo == EbtInt16 || convertTo == EbtUint16) && ! convertFromIntTypes) || ((node->getBasicType() == EbtInt16 || node->getBasicType() == EbtUint16) && ! convertToIntTypes)) { if (! getArithemeticInt16Enabled()) { return nullptr; } } if ((convertTo == EbtFloat16 && ! convertFromFloatTypes) || (node->getBasicType() == EbtFloat16 && ! convertToFloatTypes)) { if (! getArithemeticFloat16Enabled()) { return nullptr; } } #endif TIntermUnary* newNode = nullptr; TOperator newOp = EOpNull; if (!buildConvertOp(convertTo, node->getBasicType(), newOp)) { return nullptr; } TType newType(convertTo, EvqTemporary, node->getVectorSize(), node->getMatrixCols(), node->getMatrixRows()); newNode = addUnaryNode(newOp, node, node->getLoc(), newType); if (node->getAsConstantUnion()) { #ifndef GLSLANG_WEB // 8/16-bit storage extensions don't support 8/16-bit constants, so don't fold conversions // to those types if ((getArithemeticInt8Enabled() || !(convertTo == EbtInt8 || convertTo == EbtUint8)) && (getArithemeticInt16Enabled() || !(convertTo == EbtInt16 || convertTo == EbtUint16)) && (getArithemeticFloat16Enabled() || !(convertTo == EbtFloat16))) #endif { TIntermTyped* folded = node->getAsConstantUnion()->fold(newOp, newType); if (folded) return folded; } } // Propagate specialization-constant-ness, if allowed if (node->getType().getQualifier().isSpecConstant() && isSpecializationOperation(*newNode)) newNode->getWritableType().getQualifier().makeSpecConstant(); return newNode; } TIntermTyped* TIntermediate::addConversion(TBasicType convertTo, TIntermTyped* node) const { return createConversion(convertTo, node); } // For converting a pair of operands to a binary operation to compatible // types with each other, relative to the operation in 'op'. // This does not cover assignment operations, which is asymmetric in that the // left type is not changeable. // See addConversion(op, type, node) for assignments and unary operation // conversions. // // Generally, this is focused on basic type conversion, not shape conversion. // See addShapeConversion() for shape conversions. // // Returns the converted pair of nodes. // Returns when there is no conversion. std::tuple TIntermediate::addPairConversion(TOperator op, TIntermTyped* node0, TIntermTyped* node1) { if (!isConversionAllowed(op, node0) || !isConversionAllowed(op, node1)) return std::make_tuple(nullptr, nullptr); if (node0->getType() != node1->getType()) { // If differing structure, then no conversions. if (node0->isStruct() || node1->isStruct()) return std::make_tuple(nullptr, nullptr); // If differing arrays, then no conversions. if (node0->getType().isArray() || node1->getType().isArray()) return std::make_tuple(nullptr, nullptr); // No implicit conversions for operations involving cooperative matrices if (node0->getType().isCoopMat() || node1->getType().isCoopMat()) return std::make_tuple(node0, node1); } auto promoteTo = std::make_tuple(EbtNumTypes, EbtNumTypes); switch (op) { // // List all the binary ops that can implicitly convert one operand to the other's type; // This implements the 'policy' for implicit type conversion. // case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: case EOpEqual: case EOpNotEqual: case EOpAdd: case EOpSub: case EOpMul: case EOpDiv: case EOpMod: case EOpVectorTimesScalar: case EOpVectorTimesMatrix: case EOpMatrixTimesVector: case EOpMatrixTimesScalar: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpSequence: // used by ?: if (node0->getBasicType() == node1->getBasicType()) return std::make_tuple(node0, node1); promoteTo = getConversionDestinationType(node0->getBasicType(), node1->getBasicType(), op); if (std::get<0>(promoteTo) == EbtNumTypes || std::get<1>(promoteTo) == EbtNumTypes) return std::make_tuple(nullptr, nullptr); break; case EOpLogicalAnd: case EOpLogicalOr: case EOpLogicalXor: if (getSource() == EShSourceHlsl) promoteTo = std::make_tuple(EbtBool, EbtBool); else return std::make_tuple(node0, node1); break; // There are no conversions needed for GLSL; the shift amount just needs to be an // integer type, as does the base. // HLSL can promote bools to ints to make this work. case EOpLeftShift: case EOpRightShift: if (getSource() == EShSourceHlsl) { TBasicType node0BasicType = node0->getBasicType(); if (node0BasicType == EbtBool) node0BasicType = EbtInt; if (node1->getBasicType() == EbtBool) promoteTo = std::make_tuple(node0BasicType, EbtInt); else promoteTo = std::make_tuple(node0BasicType, node1->getBasicType()); } else { if (isTypeInt(node0->getBasicType()) && isTypeInt(node1->getBasicType())) return std::make_tuple(node0, node1); else return std::make_tuple(nullptr, nullptr); } break; default: if (node0->getType() == node1->getType()) return std::make_tuple(node0, node1); return std::make_tuple(nullptr, nullptr); } TIntermTyped* newNode0; TIntermTyped* newNode1; if (std::get<0>(promoteTo) != node0->getType().getBasicType()) { if (node0->getAsConstantUnion()) newNode0 = promoteConstantUnion(std::get<0>(promoteTo), node0->getAsConstantUnion()); else newNode0 = createConversion(std::get<0>(promoteTo), node0); } else newNode0 = node0; if (std::get<1>(promoteTo) != node1->getType().getBasicType()) { if (node1->getAsConstantUnion()) newNode1 = promoteConstantUnion(std::get<1>(promoteTo), node1->getAsConstantUnion()); else newNode1 = createConversion(std::get<1>(promoteTo), node1); } else newNode1 = node1; return std::make_tuple(newNode0, newNode1); } // // Convert the node's type to the given type, as allowed by the operation involved: 'op'. // For implicit conversions, 'op' is not the requested conversion, it is the explicit // operation requiring the implicit conversion. // // Binary operation conversions should be handled by addConversion(op, node, node), not here. // // Returns a node representing the conversion, which could be the same // node passed in if no conversion was needed. // // Generally, this is focused on basic type conversion, not shape conversion. // See addShapeConversion() for shape conversions. // // Return nullptr if a conversion can't be done. // TIntermTyped* TIntermediate::addConversion(TOperator op, const TType& type, TIntermTyped* node) { if (!isConversionAllowed(op, node)) return nullptr; // Otherwise, if types are identical, no problem if (type == node->getType()) return node; // If one's a structure, then no conversions. if (type.isStruct() || node->isStruct()) return nullptr; // If one's an array, then no conversions. if (type.isArray() || node->getType().isArray()) return nullptr; // Note: callers are responsible for other aspects of shape, // like vector and matrix sizes. switch (op) { // // Explicit conversions (unary operations) // case EOpConstructBool: case EOpConstructFloat: case EOpConstructInt: case EOpConstructUint: #ifndef GLSLANG_WEB case EOpConstructDouble: case EOpConstructFloat16: case EOpConstructInt8: case EOpConstructUint8: case EOpConstructInt16: case EOpConstructUint16: case EOpConstructInt64: case EOpConstructUint64: break; #endif // // Implicit conversions // case EOpLogicalNot: case EOpFunctionCall: case EOpReturn: case EOpAssign: case EOpAddAssign: case EOpSubAssign: case EOpMulAssign: case EOpVectorTimesScalarAssign: case EOpMatrixTimesScalarAssign: case EOpDivAssign: case EOpModAssign: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: case EOpAtan: case EOpClamp: case EOpCross: case EOpDistance: case EOpDot: case EOpDst: case EOpFaceForward: case EOpFma: case EOpFrexp: case EOpLdexp: case EOpMix: case EOpLit: case EOpMax: case EOpMin: case EOpMod: case EOpModf: case EOpPow: case EOpReflect: case EOpRefract: case EOpSmoothStep: case EOpStep: case EOpSequence: case EOpConstructStruct: case EOpConstructCooperativeMatrix: if (type.isReference() || node->getType().isReference()) { // types must match to assign a reference if (type == node->getType()) return node; else return nullptr; } if (type.getBasicType() == node->getType().getBasicType()) return node; if (! canImplicitlyPromote(node->getBasicType(), type.getBasicType(), op)) return nullptr; break; // For GLSL, there are no conversions needed; the shift amount just needs to be an // integer type, as do the base/result. // HLSL can convert the shift from a bool to an int. case EOpLeftShiftAssign: case EOpRightShiftAssign: { if (!(getSource() == EShSourceHlsl && node->getType().getBasicType() == EbtBool)) { if (isTypeInt(type.getBasicType()) && isTypeInt(node->getBasicType())) return node; else return nullptr; } break; } default: // default is to require a match; all exceptions should have case statements above if (type.getBasicType() == node->getType().getBasicType()) return node; else return nullptr; } bool canPromoteConstant = true; #ifndef GLSLANG_WEB // GL_EXT_shader_16bit_storage can't do OpConstantComposite with // 16-bit types, so disable promotion for those types. // Many issues with this, from JohnK: // - this isn't really right to discuss SPIR-V here // - this could easily be entirely about scalars, so is overstepping // - we should be looking at what the shader asked for, and saying whether or // not it can be done, in the parser, by calling requireExtensions(), not // changing language sementics on the fly by asking what extensions are in use // - at the time of this writing (14-Aug-2020), no test results are changed by this. switch (op) { case EOpConstructFloat16: canPromoteConstant = numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_float16); break; case EOpConstructInt8: case EOpConstructUint8: canPromoteConstant = numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_int8); break; case EOpConstructInt16: case EOpConstructUint16: canPromoteConstant = numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_int16); break; default: break; } #endif if (canPromoteConstant && node->getAsConstantUnion()) return promoteConstantUnion(type.getBasicType(), node->getAsConstantUnion()); // // Add a new newNode for the conversion. // TIntermTyped* newNode = createConversion(type.getBasicType(), node); return newNode; } // Convert the node's shape of type for the given type, as allowed by the // operation involved: 'op'. This is for situations where there is only one // direction to consider doing the shape conversion. // // This implements policy, it call addShapeConversion() for the mechanism. // // Generally, the AST represents allowed GLSL shapes, so this isn't needed // for GLSL. Bad shapes are caught in conversion or promotion. // // Return 'node' if no conversion was done. Promotion handles final shape // checking. // TIntermTyped* TIntermediate::addUniShapeConversion(TOperator op, const TType& type, TIntermTyped* node) { // some source languages don't do this switch (getSource()) { case EShSourceHlsl: break; case EShSourceGlsl: default: return node; } // some operations don't do this switch (op) { case EOpFunctionCall: case EOpReturn: break; case EOpMulAssign: // want to support vector *= scalar native ops in AST and lower, not smear, similarly for // matrix *= scalar, etc. case EOpAddAssign: case EOpSubAssign: case EOpDivAssign: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: case EOpRightShiftAssign: case EOpLeftShiftAssign: if (node->getVectorSize() == 1) return node; break; case EOpAssign: break; case EOpMix: break; default: return node; } return addShapeConversion(type, node); } // Convert the nodes' shapes to be compatible for the operation 'op'. // // This implements policy, it call addShapeConversion() for the mechanism. // // Generally, the AST represents allowed GLSL shapes, so this isn't needed // for GLSL. Bad shapes are caught in conversion or promotion. // void TIntermediate::addBiShapeConversion(TOperator op, TIntermTyped*& lhsNode, TIntermTyped*& rhsNode) { // some source languages don't do this switch (getSource()) { case EShSourceHlsl: break; case EShSourceGlsl: default: return; } // some operations don't do this // 'break' will mean attempt bidirectional conversion switch (op) { case EOpMulAssign: case EOpAssign: case EOpAddAssign: case EOpSubAssign: case EOpDivAssign: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: case EOpRightShiftAssign: case EOpLeftShiftAssign: // switch to unidirectional conversion (the lhs can't change) rhsNode = addUniShapeConversion(op, lhsNode->getType(), rhsNode); return; case EOpMul: // matrix multiply does not change shapes if (lhsNode->isMatrix() && rhsNode->isMatrix()) return; case EOpAdd: case EOpSub: case EOpDiv: // want to support vector * scalar native ops in AST and lower, not smear, similarly for // matrix * vector, etc. if (lhsNode->getVectorSize() == 1 || rhsNode->getVectorSize() == 1) return; break; case EOpRightShift: case EOpLeftShift: // can natively support the right operand being a scalar and the left a vector, // but not the reverse if (rhsNode->getVectorSize() == 1) return; break; case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: case EOpEqual: case EOpNotEqual: case EOpLogicalAnd: case EOpLogicalOr: case EOpLogicalXor: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpMix: break; default: return; } // Do bidirectional conversions if (lhsNode->getType().isScalarOrVec1() || rhsNode->getType().isScalarOrVec1()) { if (lhsNode->getType().isScalarOrVec1()) lhsNode = addShapeConversion(rhsNode->getType(), lhsNode); else rhsNode = addShapeConversion(lhsNode->getType(), rhsNode); } lhsNode = addShapeConversion(rhsNode->getType(), lhsNode); rhsNode = addShapeConversion(lhsNode->getType(), rhsNode); } // Convert the node's shape of type for the given type, as allowed by the // operation involved: 'op'. // // Generally, the AST represents allowed GLSL shapes, so this isn't needed // for GLSL. Bad shapes are caught in conversion or promotion. // // Return 'node' if no conversion was done. Promotion handles final shape // checking. // TIntermTyped* TIntermediate::addShapeConversion(const TType& type, TIntermTyped* node) { // no conversion needed if (node->getType() == type) return node; // structures and arrays don't change shape, either to or from if (node->getType().isStruct() || node->getType().isArray() || type.isStruct() || type.isArray()) return node; // The new node that handles the conversion TOperator constructorOp = mapTypeToConstructorOp(type); if (getSource() == EShSourceHlsl) { // HLSL rules for scalar, vector and matrix conversions: // 1) scalar can become anything, initializing every component with its value // 2) vector and matrix can become scalar, first element is used (warning: truncation) // 3) matrix can become matrix with less rows and/or columns (warning: truncation) // 4) vector can become vector with less rows size (warning: truncation) // 5a) vector 4 can become 2x2 matrix (special case) (same packing layout, its a reinterpret) // 5b) 2x2 matrix can become vector 4 (special case) (same packing layout, its a reinterpret) const TType &sourceType = node->getType(); // rule 1 for scalar to matrix is special if (sourceType.isScalarOrVec1() && type.isMatrix()) { // HLSL semantics: the scalar (or vec1) is replicated to every component of the matrix. Left to its // own devices, the constructor from a scalar would populate the diagonal. This forces replication // to every matrix element. // Note that if the node is complex (e.g, a function call), we don't want to duplicate it here // repeatedly, so we copy it to a temp, then use the temp. const int matSize = type.computeNumComponents(); TIntermAggregate* rhsAggregate = new TIntermAggregate(); const bool isSimple = (node->getAsSymbolNode() != nullptr) || (node->getAsConstantUnion() != nullptr); if (!isSimple) { assert(0); // TODO: use node replicator service when available. } for (int x = 0; x < matSize; ++x) rhsAggregate->getSequence().push_back(node); return setAggregateOperator(rhsAggregate, constructorOp, type, node->getLoc()); } // rule 1 and 2 if ((sourceType.isScalar() && !type.isScalar()) || (!sourceType.isScalar() && type.isScalar())) return setAggregateOperator(makeAggregate(node), constructorOp, type, node->getLoc()); // rule 3 and 5b if (sourceType.isMatrix()) { // rule 3 if (type.isMatrix()) { if ((sourceType.getMatrixCols() != type.getMatrixCols() || sourceType.getMatrixRows() != type.getMatrixRows()) && sourceType.getMatrixCols() >= type.getMatrixCols() && sourceType.getMatrixRows() >= type.getMatrixRows()) return setAggregateOperator(makeAggregate(node), constructorOp, type, node->getLoc()); // rule 5b } else if (type.isVector()) { if (type.getVectorSize() == 4 && sourceType.getMatrixCols() == 2 && sourceType.getMatrixRows() == 2) return setAggregateOperator(makeAggregate(node), constructorOp, type, node->getLoc()); } } // rule 4 and 5a if (sourceType.isVector()) { // rule 4 if (type.isVector()) { if (sourceType.getVectorSize() > type.getVectorSize()) return setAggregateOperator(makeAggregate(node), constructorOp, type, node->getLoc()); // rule 5a } else if (type.isMatrix()) { if (sourceType.getVectorSize() == 4 && type.getMatrixCols() == 2 && type.getMatrixRows() == 2) return setAggregateOperator(makeAggregate(node), constructorOp, type, node->getLoc()); } } } // scalar -> vector or vec1 -> vector or // vector -> scalar or // bigger vector -> smaller vector if ((node->getType().isScalarOrVec1() && type.isVector()) || (node->getType().isVector() && type.isScalar()) || (node->isVector() && type.isVector() && node->getVectorSize() > type.getVectorSize())) return setAggregateOperator(makeAggregate(node), constructorOp, type, node->getLoc()); return node; } bool TIntermediate::isIntegralPromotion(TBasicType from, TBasicType to) const { // integral promotions if (to == EbtInt) { switch(from) { case EbtInt8: case EbtInt16: case EbtUint8: case EbtUint16: return true; default: break; } } return false; } bool TIntermediate::isFPPromotion(TBasicType from, TBasicType to) const { // floating-point promotions if (to == EbtDouble) { switch(from) { case EbtFloat16: case EbtFloat: return true; default: break; } } return false; } bool TIntermediate::isIntegralConversion(TBasicType from, TBasicType to) const { #ifdef GLSLANG_WEB return false; #endif switch (from) { case EbtInt: switch(to) { case EbtUint: return version >= 400 || getSource() == EShSourceHlsl; case EbtInt64: case EbtUint64: return true; default: break; } break; case EbtUint: switch(to) { case EbtInt64: case EbtUint64: return true; default: break; } break; case EbtInt8: switch (to) { case EbtUint8: case EbtInt16: case EbtUint16: case EbtUint: case EbtInt64: case EbtUint64: return true; default: break; } break; case EbtUint8: switch (to) { case EbtInt16: case EbtUint16: case EbtUint: case EbtInt64: case EbtUint64: return true; default: break; } break; case EbtInt16: switch(to) { case EbtUint16: case EbtUint: case EbtInt64: case EbtUint64: return true; default: break; } break; case EbtUint16: switch(to) { case EbtUint: case EbtInt64: case EbtUint64: return true; default: break; } break; case EbtInt64: if (to == EbtUint64) { return true; } break; default: break; } return false; } bool TIntermediate::isFPConversion(TBasicType from, TBasicType to) const { #ifdef GLSLANG_WEB return false; #endif if (to == EbtFloat && from == EbtFloat16) { return true; } else { return false; } } bool TIntermediate::isFPIntegralConversion(TBasicType from, TBasicType to) const { switch (from) { case EbtInt: case EbtUint: switch(to) { case EbtFloat: case EbtDouble: return true; default: break; } break; #ifndef GLSLANG_WEB case EbtInt8: case EbtUint8: case EbtInt16: case EbtUint16: switch (to) { case EbtFloat16: case EbtFloat: case EbtDouble: return true; default: break; } break; case EbtInt64: case EbtUint64: if (to == EbtDouble) { return true; } break; #endif default: break; } return false; } // // See if the 'from' type is allowed to be implicitly converted to the // 'to' type. This is not about vector/array/struct, only about basic type. // bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperator op) const { if ((isEsProfile() && version < 310 ) || version == 110) return false; if (from == to) return true; // TODO: Move more policies into language-specific handlers. // Some languages allow more general (or potentially, more specific) conversions under some conditions. if (getSource() == EShSourceHlsl) { const bool fromConvertable = (from == EbtFloat || from == EbtDouble || from == EbtInt || from == EbtUint || from == EbtBool); const bool toConvertable = (to == EbtFloat || to == EbtDouble || to == EbtInt || to == EbtUint || to == EbtBool); if (fromConvertable && toConvertable) { switch (op) { case EOpAndAssign: // assignments can perform arbitrary conversions case EOpInclusiveOrAssign: // ... case EOpExclusiveOrAssign: // ... case EOpAssign: // ... case EOpAddAssign: // ... case EOpSubAssign: // ... case EOpMulAssign: // ... case EOpVectorTimesScalarAssign: // ... case EOpMatrixTimesScalarAssign: // ... case EOpDivAssign: // ... case EOpModAssign: // ... case EOpReturn: // function returns can also perform arbitrary conversions case EOpFunctionCall: // conversion of a calling parameter case EOpLogicalNot: case EOpLogicalAnd: case EOpLogicalOr: case EOpLogicalXor: case EOpConstructStruct: return true; default: break; } } } if (getSource() == EShSourceHlsl) { // HLSL if (from == EbtBool && (to == EbtInt || to == EbtUint || to == EbtFloat)) return true; } else { // GLSL if (isIntegralPromotion(from, to) || isFPPromotion(from, to) || isIntegralConversion(from, to) || isFPConversion(from, to) || isFPIntegralConversion(from, to)) { if (numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_int8) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_int16) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_int32) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_int64) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_float16) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_float32) || numericFeatures.contains(TNumericFeatures::shader_explicit_arithmetic_types_float64)) { return true; } } } if (isEsProfile()) { switch (to) { case EbtFloat: switch (from) { case EbtInt: case EbtUint: return numericFeatures.contains(TNumericFeatures::shader_implicit_conversions); default: return false; } case EbtUint: switch (from) { case EbtInt: return numericFeatures.contains(TNumericFeatures::shader_implicit_conversions); default: return false; } default: return false; } } else { switch (to) { case EbtDouble: switch (from) { case EbtInt: case EbtUint: case EbtInt64: case EbtUint64: case EbtFloat: return version >= 400 || numericFeatures.contains(TNumericFeatures::gpu_shader_fp64); case EbtInt16: case EbtUint16: return (version >= 400 || numericFeatures.contains(TNumericFeatures::gpu_shader_fp64)) && numericFeatures.contains(TNumericFeatures::gpu_shader_int16); case EbtFloat16: return (version >= 400 || numericFeatures.contains(TNumericFeatures::gpu_shader_fp64)) && numericFeatures.contains(TNumericFeatures::gpu_shader_half_float); default: return false; } case EbtFloat: switch (from) { case EbtInt: case EbtUint: return true; case EbtBool: return getSource() == EShSourceHlsl; case EbtInt16: case EbtUint16: return numericFeatures.contains(TNumericFeatures::gpu_shader_int16); case EbtFloat16: return numericFeatures.contains(TNumericFeatures::gpu_shader_half_float) || getSource() == EShSourceHlsl; default: return false; } case EbtUint: switch (from) { case EbtInt: return version >= 400 || getSource() == EShSourceHlsl || IsRequestedExtension(E_GL_ARB_gpu_shader5); case EbtBool: return getSource() == EShSourceHlsl; case EbtInt16: case EbtUint16: return numericFeatures.contains(TNumericFeatures::gpu_shader_int16); default: return false; } case EbtInt: switch (from) { case EbtBool: return getSource() == EShSourceHlsl; case EbtInt16: return numericFeatures.contains(TNumericFeatures::gpu_shader_int16); default: return false; } case EbtUint64: switch (from) { case EbtInt: case EbtUint: case EbtInt64: return true; case EbtInt16: case EbtUint16: return numericFeatures.contains(TNumericFeatures::gpu_shader_int16); default: return false; } case EbtInt64: switch (from) { case EbtInt: return true; case EbtInt16: return numericFeatures.contains(TNumericFeatures::gpu_shader_int16); default: return false; } case EbtFloat16: switch (from) { case EbtInt16: case EbtUint16: return numericFeatures.contains(TNumericFeatures::gpu_shader_int16); default: break; } return false; case EbtUint16: switch (from) { case EbtInt16: return numericFeatures.contains(TNumericFeatures::gpu_shader_int16); default: break; } return false; default: return false; } } return false; } static bool canSignedIntTypeRepresentAllUnsignedValues(TBasicType sintType, TBasicType uintType) { #ifdef GLSLANG_WEB return false; #endif switch(sintType) { case EbtInt8: switch(uintType) { case EbtUint8: case EbtUint16: case EbtUint: case EbtUint64: return false; default: assert(false); return false; } break; case EbtInt16: switch(uintType) { case EbtUint8: return true; case EbtUint16: case EbtUint: case EbtUint64: return false; default: assert(false); return false; } break; case EbtInt: switch(uintType) { case EbtUint8: case EbtUint16: return true; case EbtUint: return false; default: assert(false); return false; } break; case EbtInt64: switch(uintType) { case EbtUint8: case EbtUint16: case EbtUint: return true; case EbtUint64: return false; default: assert(false); return false; } break; default: assert(false); return false; } } static TBasicType getCorrespondingUnsignedType(TBasicType type) { #ifdef GLSLANG_WEB assert(type == EbtInt); return EbtUint; #endif switch(type) { case EbtInt8: return EbtUint8; case EbtInt16: return EbtUint16; case EbtInt: return EbtUint; case EbtInt64: return EbtUint64; default: assert(false); return EbtNumTypes; } } // Implements the following rules // - If either operand has type float64_t or derived from float64_t, // the other shall be converted to float64_t or derived type. // - Otherwise, if either operand has type float32_t or derived from // float32_t, the other shall be converted to float32_t or derived type. // - Otherwise, if either operand has type float16_t or derived from // float16_t, the other shall be converted to float16_t or derived type. // - Otherwise, if both operands have integer types the following rules // shall be applied to the operands: // - If both operands have the same type, no further conversion // is needed. // - Otherwise, if both operands have signed integer types or both // have unsigned integer types, the operand with the type of lesser // integer conversion rank shall be converted to the type of the // operand with greater rank. // - Otherwise, if the operand that has unsigned integer type has rank // greater than or equal to the rank of the type of the other // operand, the operand with signed integer type shall be converted // to the type of the operand with unsigned integer type. // - Otherwise, if the type of the operand with signed integer type can // represent all of the values of the type of the operand with // unsigned integer type, the operand with unsigned integer type // shall be converted to the type of the operand with signed // integer type. // - Otherwise, both operands shall be converted to the unsigned // integer type corresponding to the type of the operand with signed // integer type. std::tuple TIntermediate::getConversionDestinationType(TBasicType type0, TBasicType type1, TOperator op) const { TBasicType res0 = EbtNumTypes; TBasicType res1 = EbtNumTypes; if ((isEsProfile() && (version < 310 || !numericFeatures.contains(TNumericFeatures::shader_implicit_conversions))) || version == 110) return std::make_tuple(res0, res1); if (getSource() == EShSourceHlsl) { if (canImplicitlyPromote(type1, type0, op)) { res0 = type0; res1 = type0; } else if (canImplicitlyPromote(type0, type1, op)) { res0 = type1; res1 = type1; } return std::make_tuple(res0, res1); } if ((type0 == EbtDouble && canImplicitlyPromote(type1, EbtDouble, op)) || (type1 == EbtDouble && canImplicitlyPromote(type0, EbtDouble, op)) ) { res0 = EbtDouble; res1 = EbtDouble; } else if ((type0 == EbtFloat && canImplicitlyPromote(type1, EbtFloat, op)) || (type1 == EbtFloat && canImplicitlyPromote(type0, EbtFloat, op)) ) { res0 = EbtFloat; res1 = EbtFloat; } else if ((type0 == EbtFloat16 && canImplicitlyPromote(type1, EbtFloat16, op)) || (type1 == EbtFloat16 && canImplicitlyPromote(type0, EbtFloat16, op)) ) { res0 = EbtFloat16; res1 = EbtFloat16; } else if (isTypeInt(type0) && isTypeInt(type1) && (canImplicitlyPromote(type0, type1, op) || canImplicitlyPromote(type1, type0, op))) { if ((isTypeSignedInt(type0) && isTypeSignedInt(type1)) || (isTypeUnsignedInt(type0) && isTypeUnsignedInt(type1))) { if (getTypeRank(type0) < getTypeRank(type1)) { res0 = type1; res1 = type1; } else { res0 = type0; res1 = type0; } } else if (isTypeUnsignedInt(type0) && (getTypeRank(type0) > getTypeRank(type1))) { res0 = type0; res1 = type0; } else if (isTypeUnsignedInt(type1) && (getTypeRank(type1) > getTypeRank(type0))) { res0 = type1; res1 = type1; } else if (isTypeSignedInt(type0)) { if (canSignedIntTypeRepresentAllUnsignedValues(type0, type1)) { res0 = type0; res1 = type0; } else { res0 = getCorrespondingUnsignedType(type0); res1 = getCorrespondingUnsignedType(type0); } } else if (isTypeSignedInt(type1)) { if (canSignedIntTypeRepresentAllUnsignedValues(type1, type0)) { res0 = type1; res1 = type1; } else { res0 = getCorrespondingUnsignedType(type1); res1 = getCorrespondingUnsignedType(type1); } } } return std::make_tuple(res0, res1); } // // Given a type, find what operation would fully construct it. // TOperator TIntermediate::mapTypeToConstructorOp(const TType& type) const { TOperator op = EOpNull; if (type.getQualifier().isNonUniform()) return EOpConstructNonuniform; if (type.isCoopMat()) return EOpConstructCooperativeMatrix; switch (type.getBasicType()) { case EbtStruct: op = EOpConstructStruct; break; case EbtSampler: if (type.getSampler().isCombined()) op = EOpConstructTextureSampler; break; case EbtFloat: if (type.isMatrix()) { switch (type.getMatrixCols()) { case 2: switch (type.getMatrixRows()) { case 2: op = EOpConstructMat2x2; break; case 3: op = EOpConstructMat2x3; break; case 4: op = EOpConstructMat2x4; break; default: break; // some compilers want this } break; case 3: switch (type.getMatrixRows()) { case 2: op = EOpConstructMat3x2; break; case 3: op = EOpConstructMat3x3; break; case 4: op = EOpConstructMat3x4; break; default: break; // some compilers want this } break; case 4: switch (type.getMatrixRows()) { case 2: op = EOpConstructMat4x2; break; case 3: op = EOpConstructMat4x3; break; case 4: op = EOpConstructMat4x4; break; default: break; // some compilers want this } break; default: break; // some compilers want this } } else { switch(type.getVectorSize()) { case 1: op = EOpConstructFloat; break; case 2: op = EOpConstructVec2; break; case 3: op = EOpConstructVec3; break; case 4: op = EOpConstructVec4; break; default: break; // some compilers want this } } break; case EbtInt: if (type.getMatrixCols()) { switch (type.getMatrixCols()) { case 2: switch (type.getMatrixRows()) { case 2: op = EOpConstructIMat2x2; break; case 3: op = EOpConstructIMat2x3; break; case 4: op = EOpConstructIMat2x4; break; default: break; // some compilers want this } break; case 3: switch (type.getMatrixRows()) { case 2: op = EOpConstructIMat3x2; break; case 3: op = EOpConstructIMat3x3; break; case 4: op = EOpConstructIMat3x4; break; default: break; // some compilers want this } break; case 4: switch (type.getMatrixRows()) { case 2: op = EOpConstructIMat4x2; break; case 3: op = EOpConstructIMat4x3; break; case 4: op = EOpConstructIMat4x4; break; default: break; // some compilers want this } break; } } else { switch(type.getVectorSize()) { case 1: op = EOpConstructInt; break; case 2: op = EOpConstructIVec2; break; case 3: op = EOpConstructIVec3; break; case 4: op = EOpConstructIVec4; break; default: break; // some compilers want this } } break; case EbtUint: if (type.getMatrixCols()) { switch (type.getMatrixCols()) { case 2: switch (type.getMatrixRows()) { case 2: op = EOpConstructUMat2x2; break; case 3: op = EOpConstructUMat2x3; break; case 4: op = EOpConstructUMat2x4; break; default: break; // some compilers want this } break; case 3: switch (type.getMatrixRows()) { case 2: op = EOpConstructUMat3x2; break; case 3: op = EOpConstructUMat3x3; break; case 4: op = EOpConstructUMat3x4; break; default: break; // some compilers want this } break; case 4: switch (type.getMatrixRows()) { case 2: op = EOpConstructUMat4x2; break; case 3: op = EOpConstructUMat4x3; break; case 4: op = EOpConstructUMat4x4; break; default: break; // some compilers want this } break; } } else { switch(type.getVectorSize()) { case 1: op = EOpConstructUint; break; case 2: op = EOpConstructUVec2; break; case 3: op = EOpConstructUVec3; break; case 4: op = EOpConstructUVec4; break; default: break; // some compilers want this } } break; case EbtBool: if (type.getMatrixCols()) { switch (type.getMatrixCols()) { case 2: switch (type.getMatrixRows()) { case 2: op = EOpConstructBMat2x2; break; case 3: op = EOpConstructBMat2x3; break; case 4: op = EOpConstructBMat2x4; break; default: break; // some compilers want this } break; case 3: switch (type.getMatrixRows()) { case 2: op = EOpConstructBMat3x2; break; case 3: op = EOpConstructBMat3x3; break; case 4: op = EOpConstructBMat3x4; break; default: break; // some compilers want this } break; case 4: switch (type.getMatrixRows()) { case 2: op = EOpConstructBMat4x2; break; case 3: op = EOpConstructBMat4x3; break; case 4: op = EOpConstructBMat4x4; break; default: break; // some compilers want this } break; } } else { switch(type.getVectorSize()) { case 1: op = EOpConstructBool; break; case 2: op = EOpConstructBVec2; break; case 3: op = EOpConstructBVec3; break; case 4: op = EOpConstructBVec4; break; default: break; // some compilers want this } } break; #ifndef GLSLANG_WEB case EbtDouble: if (type.getMatrixCols()) { switch (type.getMatrixCols()) { case 2: switch (type.getMatrixRows()) { case 2: op = EOpConstructDMat2x2; break; case 3: op = EOpConstructDMat2x3; break; case 4: op = EOpConstructDMat2x4; break; default: break; // some compilers want this } break; case 3: switch (type.getMatrixRows()) { case 2: op = EOpConstructDMat3x2; break; case 3: op = EOpConstructDMat3x3; break; case 4: op = EOpConstructDMat3x4; break; default: break; // some compilers want this } break; case 4: switch (type.getMatrixRows()) { case 2: op = EOpConstructDMat4x2; break; case 3: op = EOpConstructDMat4x3; break; case 4: op = EOpConstructDMat4x4; break; default: break; // some compilers want this } break; } } else { switch(type.getVectorSize()) { case 1: op = EOpConstructDouble; break; case 2: op = EOpConstructDVec2; break; case 3: op = EOpConstructDVec3; break; case 4: op = EOpConstructDVec4; break; default: break; // some compilers want this } } break; case EbtFloat16: if (type.getMatrixCols()) { switch (type.getMatrixCols()) { case 2: switch (type.getMatrixRows()) { case 2: op = EOpConstructF16Mat2x2; break; case 3: op = EOpConstructF16Mat2x3; break; case 4: op = EOpConstructF16Mat2x4; break; default: break; // some compilers want this } break; case 3: switch (type.getMatrixRows()) { case 2: op = EOpConstructF16Mat3x2; break; case 3: op = EOpConstructF16Mat3x3; break; case 4: op = EOpConstructF16Mat3x4; break; default: break; // some compilers want this } break; case 4: switch (type.getMatrixRows()) { case 2: op = EOpConstructF16Mat4x2; break; case 3: op = EOpConstructF16Mat4x3; break; case 4: op = EOpConstructF16Mat4x4; break; default: break; // some compilers want this } break; } } else { switch (type.getVectorSize()) { case 1: op = EOpConstructFloat16; break; case 2: op = EOpConstructF16Vec2; break; case 3: op = EOpConstructF16Vec3; break; case 4: op = EOpConstructF16Vec4; break; default: break; // some compilers want this } } break; case EbtInt8: switch(type.getVectorSize()) { case 1: op = EOpConstructInt8; break; case 2: op = EOpConstructI8Vec2; break; case 3: op = EOpConstructI8Vec3; break; case 4: op = EOpConstructI8Vec4; break; default: break; // some compilers want this } break; case EbtUint8: switch(type.getVectorSize()) { case 1: op = EOpConstructUint8; break; case 2: op = EOpConstructU8Vec2; break; case 3: op = EOpConstructU8Vec3; break; case 4: op = EOpConstructU8Vec4; break; default: break; // some compilers want this } break; case EbtInt16: switch(type.getVectorSize()) { case 1: op = EOpConstructInt16; break; case 2: op = EOpConstructI16Vec2; break; case 3: op = EOpConstructI16Vec3; break; case 4: op = EOpConstructI16Vec4; break; default: break; // some compilers want this } break; case EbtUint16: switch(type.getVectorSize()) { case 1: op = EOpConstructUint16; break; case 2: op = EOpConstructU16Vec2; break; case 3: op = EOpConstructU16Vec3; break; case 4: op = EOpConstructU16Vec4; break; default: break; // some compilers want this } break; case EbtInt64: switch(type.getVectorSize()) { case 1: op = EOpConstructInt64; break; case 2: op = EOpConstructI64Vec2; break; case 3: op = EOpConstructI64Vec3; break; case 4: op = EOpConstructI64Vec4; break; default: break; // some compilers want this } break; case EbtUint64: switch(type.getVectorSize()) { case 1: op = EOpConstructUint64; break; case 2: op = EOpConstructU64Vec2; break; case 3: op = EOpConstructU64Vec3; break; case 4: op = EOpConstructU64Vec4; break; default: break; // some compilers want this } break; case EbtReference: op = EOpConstructReference; break; case EbtAccStruct: op = EOpConstructAccStruct; break; #endif default: break; } return op; } // // Safe way to combine two nodes into an aggregate. Works with null pointers, // a node that's not a aggregate yet, etc. // // Returns the resulting aggregate, unless nullptr was passed in for // both existing nodes. // TIntermAggregate* TIntermediate::growAggregate(TIntermNode* left, TIntermNode* right) { if (left == nullptr && right == nullptr) return nullptr; TIntermAggregate* aggNode = nullptr; if (left != nullptr) aggNode = left->getAsAggregate(); if (aggNode == nullptr || aggNode->getOp() != EOpNull) { aggNode = new TIntermAggregate; if (left != nullptr) aggNode->getSequence().push_back(left); } if (right != nullptr) aggNode->getSequence().push_back(right); return aggNode; } TIntermAggregate* TIntermediate::growAggregate(TIntermNode* left, TIntermNode* right, const TSourceLoc& loc) { TIntermAggregate* aggNode = growAggregate(left, right); if (aggNode) aggNode->setLoc(loc); return aggNode; } // // Turn an existing node into an aggregate. // // Returns an aggregate, unless nullptr was passed in for the existing node. // TIntermAggregate* TIntermediate::makeAggregate(TIntermNode* node) { if (node == nullptr) return nullptr; TIntermAggregate* aggNode = new TIntermAggregate; aggNode->getSequence().push_back(node); aggNode->setLoc(node->getLoc()); return aggNode; } TIntermAggregate* TIntermediate::makeAggregate(TIntermNode* node, const TSourceLoc& loc) { if (node == nullptr) return nullptr; TIntermAggregate* aggNode = new TIntermAggregate; aggNode->getSequence().push_back(node); aggNode->setLoc(loc); return aggNode; } // // Make an aggregate with an empty sequence. // TIntermAggregate* TIntermediate::makeAggregate(const TSourceLoc& loc) { TIntermAggregate* aggNode = new TIntermAggregate; aggNode->setLoc(loc); return aggNode; } // // For "if" test nodes. There are three children; a condition, // a true path, and a false path. The two paths are in the // nodePair. // // Returns the selection node created. // TIntermSelection* TIntermediate::addSelection(TIntermTyped* cond, TIntermNodePair nodePair, const TSourceLoc& loc) { // // Don't prune the false path for compile-time constants; it's needed // for static access analysis. // TIntermSelection* node = new TIntermSelection(cond, nodePair.node1, nodePair.node2); node->setLoc(loc); return node; } TIntermTyped* TIntermediate::addComma(TIntermTyped* left, TIntermTyped* right, const TSourceLoc& loc) { // However, the lowest precedence operators of the sequence operator ( , ) and the assignment operators // ... are not included in the operators that can create a constant expression. // // if (left->getType().getQualifier().storage == EvqConst && // right->getType().getQualifier().storage == EvqConst) { // return right; //} TIntermTyped *commaAggregate = growAggregate(left, right, loc); commaAggregate->getAsAggregate()->setOperator(EOpComma); commaAggregate->setType(right->getType()); commaAggregate->getWritableType().getQualifier().makeTemporary(); return commaAggregate; } TIntermTyped* TIntermediate::addMethod(TIntermTyped* object, const TType& type, const TString* name, const TSourceLoc& loc) { TIntermMethod* method = new TIntermMethod(object, type, *name); method->setLoc(loc); return method; } // // For "?:" test nodes. There are three children; a condition, // a true path, and a false path. The two paths are specified // as separate parameters. For vector 'cond', the true and false // are not paths, but vectors to mix. // // Specialization constant operations include // - The ternary operator ( ? : ) // // Returns the selection node created, or nullptr if one could not be. // TIntermTyped* TIntermediate::addSelection(TIntermTyped* cond, TIntermTyped* trueBlock, TIntermTyped* falseBlock, const TSourceLoc& loc) { // If it's void, go to the if-then-else selection() if (trueBlock->getBasicType() == EbtVoid && falseBlock->getBasicType() == EbtVoid) { TIntermNodePair pair = { trueBlock, falseBlock }; TIntermSelection* selection = addSelection(cond, pair, loc); if (getSource() == EShSourceHlsl) selection->setNoShortCircuit(); return selection; } // // Get compatible types. // auto children = addPairConversion(EOpSequence, trueBlock, falseBlock); trueBlock = std::get<0>(children); falseBlock = std::get<1>(children); if (trueBlock == nullptr || falseBlock == nullptr) return nullptr; // Handle a vector condition as a mix if (!cond->getType().isScalarOrVec1()) { TType targetVectorType(trueBlock->getType().getBasicType(), EvqTemporary, cond->getType().getVectorSize()); // smear true/false operands as needed trueBlock = addUniShapeConversion(EOpMix, targetVectorType, trueBlock); falseBlock = addUniShapeConversion(EOpMix, targetVectorType, falseBlock); // After conversion, types have to match. if (falseBlock->getType() != trueBlock->getType()) return nullptr; // make the mix operation TIntermAggregate* mix = makeAggregate(loc); mix = growAggregate(mix, falseBlock); mix = growAggregate(mix, trueBlock); mix = growAggregate(mix, cond); mix->setType(targetVectorType); mix->setOp(EOpMix); return mix; } // Now have a scalar condition... // Convert true and false expressions to matching types addBiShapeConversion(EOpMix, trueBlock, falseBlock); // After conversion, types have to match. if (falseBlock->getType() != trueBlock->getType()) return nullptr; // Eliminate the selection when the condition is a scalar and all operands are constant. if (cond->getAsConstantUnion() && trueBlock->getAsConstantUnion() && falseBlock->getAsConstantUnion()) { if (cond->getAsConstantUnion()->getConstArray()[0].getBConst()) return trueBlock; else return falseBlock; } // // Make a selection node. // TIntermSelection* node = new TIntermSelection(cond, trueBlock, falseBlock, trueBlock->getType()); node->setLoc(loc); node->getQualifier().precision = std::max(trueBlock->getQualifier().precision, falseBlock->getQualifier().precision); if ((cond->getQualifier().isConstant() && specConstantPropagates(*trueBlock, *falseBlock)) || (cond->getQualifier().isSpecConstant() && trueBlock->getQualifier().isConstant() && falseBlock->getQualifier().isConstant())) node->getQualifier().makeSpecConstant(); else node->getQualifier().makeTemporary(); if (getSource() == EShSourceHlsl) node->setNoShortCircuit(); return node; } // // Constant terminal nodes. Has a union that contains bool, float or int constants // // Returns the constant union node created. // TIntermConstantUnion* TIntermediate::addConstantUnion(const TConstUnionArray& unionArray, const TType& t, const TSourceLoc& loc, bool literal) const { TIntermConstantUnion* node = new TIntermConstantUnion(unionArray, t); node->getQualifier().storage = EvqConst; node->setLoc(loc); if (literal) node->setLiteral(); return node; } TIntermConstantUnion* TIntermediate::addConstantUnion(signed char i8, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setI8Const(i8); return addConstantUnion(unionArray, TType(EbtInt8, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(unsigned char u8, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setUConst(u8); return addConstantUnion(unionArray, TType(EbtUint8, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(signed short i16, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setI16Const(i16); return addConstantUnion(unionArray, TType(EbtInt16, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(unsigned short u16, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setU16Const(u16); return addConstantUnion(unionArray, TType(EbtUint16, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(int i, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setIConst(i); return addConstantUnion(unionArray, TType(EbtInt, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(unsigned int u, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setUConst(u); return addConstantUnion(unionArray, TType(EbtUint, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(long long i64, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setI64Const(i64); return addConstantUnion(unionArray, TType(EbtInt64, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(unsigned long long u64, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setU64Const(u64); return addConstantUnion(unionArray, TType(EbtUint64, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(bool b, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setBConst(b); return addConstantUnion(unionArray, TType(EbtBool, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(double d, TBasicType baseType, const TSourceLoc& loc, bool literal) const { assert(baseType == EbtFloat || baseType == EbtDouble || baseType == EbtFloat16); TConstUnionArray unionArray(1); unionArray[0].setDConst(d); return addConstantUnion(unionArray, TType(baseType, EvqConst), loc, literal); } TIntermConstantUnion* TIntermediate::addConstantUnion(const TString* s, const TSourceLoc& loc, bool literal) const { TConstUnionArray unionArray(1); unionArray[0].setSConst(s); return addConstantUnion(unionArray, TType(EbtString, EvqConst), loc, literal); } // Put vector swizzle selectors onto the given sequence void TIntermediate::pushSelector(TIntermSequence& sequence, const TVectorSelector& selector, const TSourceLoc& loc) { TIntermConstantUnion* constIntNode = addConstantUnion(selector, loc); sequence.push_back(constIntNode); } // Put matrix swizzle selectors onto the given sequence void TIntermediate::pushSelector(TIntermSequence& sequence, const TMatrixSelector& selector, const TSourceLoc& loc) { TIntermConstantUnion* constIntNode = addConstantUnion(selector.coord1, loc); sequence.push_back(constIntNode); constIntNode = addConstantUnion(selector.coord2, loc); sequence.push_back(constIntNode); } // Make an aggregate node that has a sequence of all selectors. template TIntermTyped* TIntermediate::addSwizzle(TSwizzleSelectors& selector, const TSourceLoc& loc); template TIntermTyped* TIntermediate::addSwizzle(TSwizzleSelectors& selector, const TSourceLoc& loc); template TIntermTyped* TIntermediate::addSwizzle(TSwizzleSelectors& selector, const TSourceLoc& loc) { TIntermAggregate* node = new TIntermAggregate(EOpSequence); node->setLoc(loc); TIntermSequence &sequenceVector = node->getSequence(); for (int i = 0; i < selector.size(); i++) pushSelector(sequenceVector, selector[i], loc); return node; } // // Follow the left branches down to the root of an l-value // expression (just "." and []). // // Return the base of the l-value (where following indexing quits working). // Return nullptr if a chain following dereferences cannot be followed. // // 'swizzleOkay' says whether or not it is okay to consider a swizzle // a valid part of the dereference chain. // // 'BufferReferenceOk' says if type is buffer_reference, the routine stop to find the most left node. // // const TIntermTyped* TIntermediate::findLValueBase(const TIntermTyped* node, bool swizzleOkay , bool bufferReferenceOk) { do { const TIntermBinary* binary = node->getAsBinaryNode(); if (binary == nullptr) return node; TOperator op = binary->getOp(); if (op != EOpIndexDirect && op != EOpIndexIndirect && op != EOpIndexDirectStruct && op != EOpVectorSwizzle && op != EOpMatrixSwizzle) return nullptr; if (! swizzleOkay) { if (op == EOpVectorSwizzle || op == EOpMatrixSwizzle) return nullptr; if ((op == EOpIndexDirect || op == EOpIndexIndirect) && (binary->getLeft()->getType().isVector() || binary->getLeft()->getType().isScalar()) && ! binary->getLeft()->getType().isArray()) return nullptr; } node = node->getAsBinaryNode()->getLeft(); if (bufferReferenceOk && node->isReference()) return node; } while (true); } // // Create while and do-while loop nodes. // TIntermLoop* TIntermediate::addLoop(TIntermNode* body, TIntermTyped* test, TIntermTyped* terminal, bool testFirst, const TSourceLoc& loc) { TIntermLoop* node = new TIntermLoop(body, test, terminal, testFirst); node->setLoc(loc); return node; } // // Create a for-loop sequence. // TIntermAggregate* TIntermediate::addForLoop(TIntermNode* body, TIntermNode* initializer, TIntermTyped* test, TIntermTyped* terminal, bool testFirst, const TSourceLoc& loc, TIntermLoop*& node) { node = new TIntermLoop(body, test, terminal, testFirst); node->setLoc(loc); // make a sequence of the initializer and statement, but try to reuse the // aggregate already created for whatever is in the initializer, if there is one TIntermAggregate* loopSequence = (initializer == nullptr || initializer->getAsAggregate() == nullptr) ? makeAggregate(initializer, loc) : initializer->getAsAggregate(); if (loopSequence != nullptr && loopSequence->getOp() == EOpSequence) loopSequence->setOp(EOpNull); loopSequence = growAggregate(loopSequence, node); loopSequence->setOperator(EOpSequence); return loopSequence; } // // Add branches. // TIntermBranch* TIntermediate::addBranch(TOperator branchOp, const TSourceLoc& loc) { return addBranch(branchOp, nullptr, loc); } TIntermBranch* TIntermediate::addBranch(TOperator branchOp, TIntermTyped* expression, const TSourceLoc& loc) { TIntermBranch* node = new TIntermBranch(branchOp, expression); node->setLoc(loc); return node; } // Propagate precision from formal function return type to actual return type, // and on to its subtree. void TIntermBranch::updatePrecision(TPrecisionQualifier parentPrecision) { TIntermTyped* exp = getExpression(); if (exp == nullptr) return; if (exp->getBasicType() == EbtInt || exp->getBasicType() == EbtUint || exp->getBasicType() == EbtFloat || exp->getBasicType() == EbtFloat16) { if (parentPrecision != EpqNone && exp->getQualifier().precision == EpqNone) { exp->propagatePrecision(parentPrecision); } } } // // This is to be executed after the final root is put on top by the parsing // process. // bool TIntermediate::postProcess(TIntermNode* root, EShLanguage /*language*/) { if (root == nullptr) return true; // Finish off the top-level sequence TIntermAggregate* aggRoot = root->getAsAggregate(); if (aggRoot && aggRoot->getOp() == EOpNull) aggRoot->setOperator(EOpSequence); #ifndef GLSLANG_WEB // Propagate 'noContraction' label in backward from 'precise' variables. glslang::PropagateNoContraction(*this); switch (textureSamplerTransformMode) { case EShTexSampTransKeep: break; case EShTexSampTransUpgradeTextureRemoveSampler: performTextureUpgradeAndSamplerRemovalTransformation(root); break; case EShTexSampTransCount: assert(0); break; } #endif return true; } void TIntermediate::addSymbolLinkageNodes(TIntermAggregate*& linkage, EShLanguage language, TSymbolTable& symbolTable) { // Add top-level nodes for declarations that must be checked cross // compilation unit by a linker, yet might not have been referenced // by the AST. // // Almost entirely, translation of symbols is driven by what's present // in the AST traversal, not by translating the symbol table. // // However, there are some special cases: // - From the specification: "Special built-in inputs gl_VertexID and // gl_InstanceID are also considered active vertex attributes." // - Linker-based type mismatch error reporting needs to see all // uniforms/ins/outs variables and blocks. // - ftransform() can make gl_Vertex and gl_ModelViewProjectionMatrix active. // // if (ftransformUsed) { // TODO: 1.1 lowering functionality: track ftransform() usage // addSymbolLinkageNode(root, symbolTable, "gl_Vertex"); // addSymbolLinkageNode(root, symbolTable, "gl_ModelViewProjectionMatrix"); //} if (language == EShLangVertex) { // the names won't be found in the symbol table unless the versions are right, // so version logic does not need to be repeated here addSymbolLinkageNode(linkage, symbolTable, "gl_VertexID"); addSymbolLinkageNode(linkage, symbolTable, "gl_InstanceID"); } // Add a child to the root node for the linker objects linkage->setOperator(EOpLinkerObjects); treeRoot = growAggregate(treeRoot, linkage); } // // Add the given name or symbol to the list of nodes at the end of the tree used // for link-time checking and external linkage. // void TIntermediate::addSymbolLinkageNode(TIntermAggregate*& linkage, TSymbolTable& symbolTable, const TString& name) { TSymbol* symbol = symbolTable.find(name); if (symbol) addSymbolLinkageNode(linkage, *symbol->getAsVariable()); } void TIntermediate::addSymbolLinkageNode(TIntermAggregate*& linkage, const TSymbol& symbol) { const TVariable* variable = symbol.getAsVariable(); if (! variable) { // This must be a member of an anonymous block, and we need to add the whole block const TAnonMember* anon = symbol.getAsAnonMember(); variable = &anon->getAnonContainer(); } TIntermSymbol* node = addSymbol(*variable); linkage = growAggregate(linkage, node); } // // Add a caller->callee relationship to the call graph. // Assumes the strings are unique per signature. // void TIntermediate::addToCallGraph(TInfoSink& /*infoSink*/, const TString& caller, const TString& callee) { // Duplicates are okay, but faster to not keep them, and they come grouped by caller, // as long as new ones are push on the same end we check on for duplicates for (TGraph::const_iterator call = callGraph.begin(); call != callGraph.end(); ++call) { if (call->caller != caller) break; if (call->callee == callee) return; } callGraph.emplace_front(caller, callee); } // // This deletes the tree. // void TIntermediate::removeTree() { if (treeRoot) RemoveAllTreeNodes(treeRoot); } // // Implement the part of KHR_vulkan_glsl that lists the set of operations // that can result in a specialization constant operation. // // "5.x Specialization Constant Operations" // // Only some operations discussed in this section may be applied to a // specialization constant and still yield a result that is as // specialization constant. The operations allowed are listed below. // When a specialization constant is operated on with one of these // operators and with another constant or specialization constant, the // result is implicitly a specialization constant. // // - int(), uint(), and bool() constructors for type conversions // from any of the following types to any of the following types: // * int // * uint // * bool // - vector versions of the above conversion constructors // - allowed implicit conversions of the above // - swizzles (e.g., foo.yx) // - The following when applied to integer or unsigned integer types: // * unary negative ( - ) // * binary operations ( + , - , * , / , % ) // * shift ( <<, >> ) // * bitwise operations ( & , | , ^ ) // - The following when applied to integer or unsigned integer scalar types: // * comparison ( == , != , > , >= , < , <= ) // - The following when applied to the Boolean scalar type: // * not ( ! ) // * logical operations ( && , || , ^^ ) // * comparison ( == , != )" // // This function just handles binary and unary nodes. Construction // rules are handled in construction paths that are not covered by the unary // and binary paths, while required conversions will still show up here // as unary converters in the from a construction operator. // bool TIntermediate::isSpecializationOperation(const TIntermOperator& node) const { // The operations resulting in floating point are quite limited // (However, some floating-point operations result in bool, like ">", // so are handled later.) if (node.getType().isFloatingDomain()) { switch (node.getOp()) { case EOpIndexDirect: case EOpIndexIndirect: case EOpIndexDirectStruct: case EOpVectorSwizzle: case EOpConvFloatToDouble: case EOpConvDoubleToFloat: case EOpConvFloat16ToFloat: case EOpConvFloatToFloat16: case EOpConvFloat16ToDouble: case EOpConvDoubleToFloat16: return true; default: return false; } } // Check for floating-point arguments if (const TIntermBinary* bin = node.getAsBinaryNode()) if (bin->getLeft() ->getType().isFloatingDomain() || bin->getRight()->getType().isFloatingDomain()) return false; // So, for now, we can assume everything left is non-floating-point... // Now check for integer/bool-based operations switch (node.getOp()) { // dereference/swizzle case EOpIndexDirect: case EOpIndexIndirect: case EOpIndexDirectStruct: case EOpVectorSwizzle: // (u)int* -> bool case EOpConvInt8ToBool: case EOpConvInt16ToBool: case EOpConvIntToBool: case EOpConvInt64ToBool: case EOpConvUint8ToBool: case EOpConvUint16ToBool: case EOpConvUintToBool: case EOpConvUint64ToBool: // bool -> (u)int* case EOpConvBoolToInt8: case EOpConvBoolToInt16: case EOpConvBoolToInt: case EOpConvBoolToInt64: case EOpConvBoolToUint8: case EOpConvBoolToUint16: case EOpConvBoolToUint: case EOpConvBoolToUint64: // int8_t -> (u)int* case EOpConvInt8ToInt16: case EOpConvInt8ToInt: case EOpConvInt8ToInt64: case EOpConvInt8ToUint8: case EOpConvInt8ToUint16: case EOpConvInt8ToUint: case EOpConvInt8ToUint64: // int16_t -> (u)int* case EOpConvInt16ToInt8: case EOpConvInt16ToInt: case EOpConvInt16ToInt64: case EOpConvInt16ToUint8: case EOpConvInt16ToUint16: case EOpConvInt16ToUint: case EOpConvInt16ToUint64: // int32_t -> (u)int* case EOpConvIntToInt8: case EOpConvIntToInt16: case EOpConvIntToInt64: case EOpConvIntToUint8: case EOpConvIntToUint16: case EOpConvIntToUint: case EOpConvIntToUint64: // int64_t -> (u)int* case EOpConvInt64ToInt8: case EOpConvInt64ToInt16: case EOpConvInt64ToInt: case EOpConvInt64ToUint8: case EOpConvInt64ToUint16: case EOpConvInt64ToUint: case EOpConvInt64ToUint64: // uint8_t -> (u)int* case EOpConvUint8ToInt8: case EOpConvUint8ToInt16: case EOpConvUint8ToInt: case EOpConvUint8ToInt64: case EOpConvUint8ToUint16: case EOpConvUint8ToUint: case EOpConvUint8ToUint64: // uint16_t -> (u)int* case EOpConvUint16ToInt8: case EOpConvUint16ToInt16: case EOpConvUint16ToInt: case EOpConvUint16ToInt64: case EOpConvUint16ToUint8: case EOpConvUint16ToUint: case EOpConvUint16ToUint64: // uint32_t -> (u)int* case EOpConvUintToInt8: case EOpConvUintToInt16: case EOpConvUintToInt: case EOpConvUintToInt64: case EOpConvUintToUint8: case EOpConvUintToUint16: case EOpConvUintToUint64: // uint64_t -> (u)int* case EOpConvUint64ToInt8: case EOpConvUint64ToInt16: case EOpConvUint64ToInt: case EOpConvUint64ToInt64: case EOpConvUint64ToUint8: case EOpConvUint64ToUint16: case EOpConvUint64ToUint: // unary operations case EOpNegative: case EOpLogicalNot: case EOpBitwiseNot: // binary operations case EOpAdd: case EOpSub: case EOpMul: case EOpVectorTimesScalar: case EOpDiv: case EOpMod: case EOpRightShift: case EOpLeftShift: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpLogicalOr: case EOpLogicalXor: case EOpLogicalAnd: case EOpEqual: case EOpNotEqual: case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: return true; default: return false; } } // Is the operation one that must propagate nonuniform? bool TIntermediate::isNonuniformPropagating(TOperator op) const { // "* All Operators in Section 5.1 (Operators), except for assignment, // arithmetic assignment, and sequence // * Component selection in Section 5.5 // * Matrix components in Section 5.6 // * Structure and Array Operations in Section 5.7, except for the length // method." switch (op) { case EOpPostIncrement: case EOpPostDecrement: case EOpPreIncrement: case EOpPreDecrement: case EOpNegative: case EOpLogicalNot: case EOpVectorLogicalNot: case EOpBitwiseNot: case EOpAdd: case EOpSub: case EOpMul: case EOpDiv: case EOpMod: case EOpRightShift: case EOpLeftShift: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpEqual: case EOpNotEqual: case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: case EOpVectorTimesScalar: case EOpVectorTimesMatrix: case EOpMatrixTimesVector: case EOpMatrixTimesScalar: case EOpLogicalOr: case EOpLogicalXor: case EOpLogicalAnd: case EOpIndexDirect: case EOpIndexIndirect: case EOpIndexDirectStruct: case EOpVectorSwizzle: return true; default: break; } return false; } //////////////////////////////////////////////////////////////// // // Member functions of the nodes used for building the tree. // //////////////////////////////////////////////////////////////// // // Say whether or not an operation node changes the value of a variable. // // Returns true if state is modified. // bool TIntermOperator::modifiesState() const { switch (op) { case EOpPostIncrement: case EOpPostDecrement: case EOpPreIncrement: case EOpPreDecrement: case EOpAssign: case EOpAddAssign: case EOpSubAssign: case EOpMulAssign: case EOpVectorTimesMatrixAssign: case EOpVectorTimesScalarAssign: case EOpMatrixTimesScalarAssign: case EOpMatrixTimesMatrixAssign: case EOpDivAssign: case EOpModAssign: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: case EOpLeftShiftAssign: case EOpRightShiftAssign: return true; default: return false; } } // // returns true if the operator is for one of the constructors // bool TIntermOperator::isConstructor() const { return op > EOpConstructGuardStart && op < EOpConstructGuardEnd; } // // Make sure the type of an operator is appropriate for its // combination of operation and operand type. This will invoke // promoteUnary, promoteBinary, etc as needed. // // Returns false if nothing makes sense. // bool TIntermediate::promote(TIntermOperator* node) { if (node == nullptr) return false; if (node->getAsUnaryNode()) return promoteUnary(*node->getAsUnaryNode()); if (node->getAsBinaryNode()) return promoteBinary(*node->getAsBinaryNode()); if (node->getAsAggregate()) return promoteAggregate(*node->getAsAggregate()); return false; } // // See TIntermediate::promote // bool TIntermediate::promoteUnary(TIntermUnary& node) { const TOperator op = node.getOp(); TIntermTyped* operand = node.getOperand(); switch (op) { case EOpLogicalNot: // Convert operand to a boolean type if (operand->getBasicType() != EbtBool) { // Add constructor to boolean type. If that fails, we can't do it, so return false. TIntermTyped* converted = addConversion(op, TType(EbtBool), operand); if (converted == nullptr) return false; // Use the result of converting the node to a bool. node.setOperand(operand = converted); // also updates stack variable } break; case EOpBitwiseNot: if (!isTypeInt(operand->getBasicType())) return false; break; case EOpNegative: case EOpPostIncrement: case EOpPostDecrement: case EOpPreIncrement: case EOpPreDecrement: if (!isTypeInt(operand->getBasicType()) && operand->getBasicType() != EbtFloat && operand->getBasicType() != EbtFloat16 && operand->getBasicType() != EbtDouble) return false; break; default: // HLSL uses this path for initial function signature finding for built-ins // taking a single argument, which generally don't participate in // operator-based type promotion (type conversion will occur later). // For now, scalar argument cases are relying on the setType() call below. if (getSource() == EShSourceHlsl) break; // GLSL only allows integer arguments for the cases identified above in the // case statements. if (operand->getBasicType() != EbtFloat) return false; } node.setType(operand->getType()); node.getWritableType().getQualifier().makeTemporary(); return true; } // Propagate precision qualifiers *up* from children to parent. void TIntermUnary::updatePrecision() { if (getBasicType() == EbtInt || getBasicType() == EbtUint || getBasicType() == EbtFloat || getBasicType() == EbtFloat16) { if (operand->getQualifier().precision > getQualifier().precision) getQualifier().precision = operand->getQualifier().precision; } } // // See TIntermediate::promote // bool TIntermediate::promoteBinary(TIntermBinary& node) { TOperator op = node.getOp(); TIntermTyped* left = node.getLeft(); TIntermTyped* right = node.getRight(); // Arrays and structures have to be exact matches. if ((left->isArray() || right->isArray() || left->getBasicType() == EbtStruct || right->getBasicType() == EbtStruct) && left->getType() != right->getType()) return false; // Base assumption: just make the type the same as the left // operand. Only deviations from this will be coded. node.setType(left->getType()); node.getWritableType().getQualifier().clear(); // Composite and opaque types don't having pending operator changes, e.g., // array, structure, and samplers. Just establish final type and correctness. if (left->isArray() || left->getBasicType() == EbtStruct || left->getBasicType() == EbtSampler) { switch (op) { case EOpEqual: case EOpNotEqual: if (left->getBasicType() == EbtSampler) { // can't compare samplers return false; } else { // Promote to conditional node.setType(TType(EbtBool)); } return true; case EOpAssign: // Keep type from above return true; default: return false; } } // // We now have only scalars, vectors, and matrices to worry about. // // HLSL implicitly promotes bool -> int for numeric operations. // (Implicit conversions to make the operands match each other's types were already done.) if (getSource() == EShSourceHlsl && (left->getBasicType() == EbtBool || right->getBasicType() == EbtBool)) { switch (op) { case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: case EOpRightShift: case EOpLeftShift: case EOpMod: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpAdd: case EOpSub: case EOpDiv: case EOpMul: if (left->getBasicType() == EbtBool) left = createConversion(EbtInt, left); if (right->getBasicType() == EbtBool) right = createConversion(EbtInt, right); if (left == nullptr || right == nullptr) return false; node.setLeft(left); node.setRight(right); // Update the original base assumption on result type.. node.setType(left->getType()); node.getWritableType().getQualifier().clear(); break; default: break; } } // Do general type checks against individual operands (comparing left and right is coming up, checking mixed shapes after that) switch (op) { case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: // Relational comparisons need numeric types and will promote to scalar Boolean. if (left->getBasicType() == EbtBool) return false; node.setType(TType(EbtBool, EvqTemporary, left->getVectorSize())); break; case EOpEqual: case EOpNotEqual: if (getSource() == EShSourceHlsl) { const int resultWidth = std::max(left->getVectorSize(), right->getVectorSize()); // In HLSL, == or != on vectors means component-wise comparison. if (resultWidth > 1) { op = (op == EOpEqual) ? EOpVectorEqual : EOpVectorNotEqual; node.setOp(op); } node.setType(TType(EbtBool, EvqTemporary, resultWidth)); } else { // All the above comparisons result in a bool (but not the vector compares) node.setType(TType(EbtBool)); } break; case EOpLogicalAnd: case EOpLogicalOr: case EOpLogicalXor: // logical ops operate only on Booleans or vectors of Booleans. if (left->getBasicType() != EbtBool || left->isMatrix()) return false; if (getSource() == EShSourceGlsl) { // logical ops operate only on scalar Booleans and will promote to scalar Boolean. if (left->isVector()) return false; } node.setType(TType(EbtBool, EvqTemporary, left->getVectorSize())); break; case EOpRightShift: case EOpLeftShift: case EOpRightShiftAssign: case EOpLeftShiftAssign: case EOpMod: case EOpModAssign: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: if (getSource() == EShSourceHlsl) break; // Check for integer-only operands. if (!isTypeInt(left->getBasicType()) && !isTypeInt(right->getBasicType())) return false; if (left->isMatrix() || right->isMatrix()) return false; break; case EOpAdd: case EOpSub: case EOpDiv: case EOpMul: case EOpAddAssign: case EOpSubAssign: case EOpMulAssign: case EOpDivAssign: // check for non-Boolean operands if (left->getBasicType() == EbtBool || right->getBasicType() == EbtBool) return false; default: break; } // Compare left and right, and finish with the cases where the operand types must match switch (op) { case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: case EOpEqual: case EOpNotEqual: case EOpVectorEqual: case EOpVectorNotEqual: case EOpLogicalAnd: case EOpLogicalOr: case EOpLogicalXor: return left->getType() == right->getType(); case EOpMod: case EOpModAssign: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: case EOpAdd: case EOpSub: case EOpDiv: case EOpAddAssign: case EOpSubAssign: case EOpDivAssign: // Quick out in case the types do match if (left->getType() == right->getType()) return true; // Fall through case EOpMul: case EOpMulAssign: // At least the basic type has to match if (left->getBasicType() != right->getBasicType()) return false; default: break; } if (left->getType().isCoopMat() || right->getType().isCoopMat()) { if (left->getType().isCoopMat() && right->getType().isCoopMat() && *left->getType().getTypeParameters() != *right->getType().getTypeParameters()) { return false; } switch (op) { case EOpMul: case EOpMulAssign: if (left->getType().isCoopMat() && right->getType().isCoopMat()) { return false; } if (op == EOpMulAssign && right->getType().isCoopMat()) { return false; } node.setOp(op == EOpMulAssign ? EOpMatrixTimesScalarAssign : EOpMatrixTimesScalar); if (right->getType().isCoopMat()) { node.setType(right->getType()); } return true; case EOpAdd: case EOpSub: case EOpDiv: case EOpAssign: // These require both to be cooperative matrices if (!left->getType().isCoopMat() || !right->getType().isCoopMat()) { return false; } return true; default: break; } return false; } // Finish handling the case, for all ops, where both operands are scalars. if (left->isScalar() && right->isScalar()) return true; // Finish handling the case, for all ops, where there are two vectors of different sizes if (left->isVector() && right->isVector() && left->getVectorSize() != right->getVectorSize() && right->getVectorSize() > 1) return false; // // We now have a mix of scalars, vectors, or matrices, for non-relational operations. // // Can these two operands be combined, what is the resulting type? TBasicType basicType = left->getBasicType(); switch (op) { case EOpMul: if (!left->isMatrix() && right->isMatrix()) { if (left->isVector()) { if (left->getVectorSize() != right->getMatrixRows()) return false; node.setOp(op = EOpVectorTimesMatrix); node.setType(TType(basicType, EvqTemporary, right->getMatrixCols())); } else { node.setOp(op = EOpMatrixTimesScalar); node.setType(TType(basicType, EvqTemporary, 0, right->getMatrixCols(), right->getMatrixRows())); } } else if (left->isMatrix() && !right->isMatrix()) { if (right->isVector()) { if (left->getMatrixCols() != right->getVectorSize()) return false; node.setOp(op = EOpMatrixTimesVector); node.setType(TType(basicType, EvqTemporary, left->getMatrixRows())); } else { node.setOp(op = EOpMatrixTimesScalar); } } else if (left->isMatrix() && right->isMatrix()) { if (left->getMatrixCols() != right->getMatrixRows()) return false; node.setOp(op = EOpMatrixTimesMatrix); node.setType(TType(basicType, EvqTemporary, 0, right->getMatrixCols(), left->getMatrixRows())); } else if (! left->isMatrix() && ! right->isMatrix()) { if (left->isVector() && right->isVector()) { ; // leave as component product } else if (left->isVector() || right->isVector()) { node.setOp(op = EOpVectorTimesScalar); if (right->isVector()) node.setType(TType(basicType, EvqTemporary, right->getVectorSize())); } } else { return false; } break; case EOpMulAssign: if (! left->isMatrix() && right->isMatrix()) { if (left->isVector()) { if (left->getVectorSize() != right->getMatrixRows() || left->getVectorSize() != right->getMatrixCols()) return false; node.setOp(op = EOpVectorTimesMatrixAssign); } else { return false; } } else if (left->isMatrix() && !right->isMatrix()) { if (right->isVector()) { return false; } else { node.setOp(op = EOpMatrixTimesScalarAssign); } } else if (left->isMatrix() && right->isMatrix()) { if (left->getMatrixCols() != right->getMatrixCols() || left->getMatrixCols() != right->getMatrixRows()) return false; node.setOp(op = EOpMatrixTimesMatrixAssign); } else if (!left->isMatrix() && !right->isMatrix()) { if (left->isVector() && right->isVector()) { // leave as component product } else if (left->isVector() || right->isVector()) { if (! left->isVector()) return false; node.setOp(op = EOpVectorTimesScalarAssign); } } else { return false; } break; case EOpRightShift: case EOpLeftShift: case EOpRightShiftAssign: case EOpLeftShiftAssign: if (right->isVector() && (! left->isVector() || right->getVectorSize() != left->getVectorSize())) return false; break; case EOpAssign: if (left->getVectorSize() != right->getVectorSize() || left->getMatrixCols() != right->getMatrixCols() || left->getMatrixRows() != right->getMatrixRows()) return false; // fall through case EOpAdd: case EOpSub: case EOpDiv: case EOpMod: case EOpAnd: case EOpInclusiveOr: case EOpExclusiveOr: case EOpAddAssign: case EOpSubAssign: case EOpDivAssign: case EOpModAssign: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: if ((left->isMatrix() && right->isVector()) || (left->isVector() && right->isMatrix()) || left->getBasicType() != right->getBasicType()) return false; if (left->isMatrix() && right->isMatrix() && (left->getMatrixCols() != right->getMatrixCols() || left->getMatrixRows() != right->getMatrixRows())) return false; if (left->isVector() && right->isVector() && left->getVectorSize() != right->getVectorSize()) return false; if (right->isVector() || right->isMatrix()) { node.getWritableType().shallowCopy(right->getType()); node.getWritableType().getQualifier().makeTemporary(); } break; default: return false; } // // One more check for assignment. // switch (op) { // The resulting type has to match the left operand. case EOpAssign: case EOpAddAssign: case EOpSubAssign: case EOpMulAssign: case EOpDivAssign: case EOpModAssign: case EOpAndAssign: case EOpInclusiveOrAssign: case EOpExclusiveOrAssign: case EOpLeftShiftAssign: case EOpRightShiftAssign: if (node.getType() != left->getType()) return false; break; default: break; } return true; } // // See TIntermediate::promote // bool TIntermediate::promoteAggregate(TIntermAggregate& node) { TOperator op = node.getOp(); TIntermSequence& args = node.getSequence(); const int numArgs = static_cast(args.size()); // Presently, only hlsl does intrinsic promotions. if (getSource() != EShSourceHlsl) return true; // set of opcodes that can be promoted in this manner. switch (op) { case EOpAtan: case EOpClamp: case EOpCross: case EOpDistance: case EOpDot: case EOpDst: case EOpFaceForward: // case EOpFindMSB: TODO: // case EOpFindLSB: TODO: case EOpFma: case EOpMod: case EOpFrexp: case EOpLdexp: case EOpMix: case EOpLit: case EOpMax: case EOpMin: case EOpModf: // case EOpGenMul: TODO: case EOpPow: case EOpReflect: case EOpRefract: // case EOpSinCos: TODO: case EOpSmoothStep: case EOpStep: break; default: return true; } // TODO: array and struct behavior // Try converting all nodes to the given node's type TIntermSequence convertedArgs(numArgs, nullptr); // Try to convert all types to the nonConvArg type. for (int nonConvArg = 0; nonConvArg < numArgs; ++nonConvArg) { // Try converting all args to this arg's type for (int convArg = 0; convArg < numArgs; ++convArg) { convertedArgs[convArg] = addConversion(op, args[nonConvArg]->getAsTyped()->getType(), args[convArg]->getAsTyped()); } // If we successfully converted all the args, use the result. if (std::all_of(convertedArgs.begin(), convertedArgs.end(), [](const TIntermNode* node) { return node != nullptr; })) { std::swap(args, convertedArgs); return true; } } return false; } // Propagate precision qualifiers *up* from children to parent, and then // back *down* again to the children's subtrees. void TIntermBinary::updatePrecision() { if (getBasicType() == EbtInt || getBasicType() == EbtUint || getBasicType() == EbtFloat || getBasicType() == EbtFloat16) { if (op == EOpRightShift || op == EOpLeftShift) { // For shifts get precision from left side only and thus no need to propagate getQualifier().precision = left->getQualifier().precision; } else { getQualifier().precision = std::max(right->getQualifier().precision, left->getQualifier().precision); if (getQualifier().precision != EpqNone) { left->propagatePrecision(getQualifier().precision); right->propagatePrecision(getQualifier().precision); } } } } // Recursively propagate precision qualifiers *down* the subtree of the current node, // until reaching a node that already has a precision qualifier or otherwise does // not participate in precision propagation. void TIntermTyped::propagatePrecision(TPrecisionQualifier newPrecision) { if (getQualifier().precision != EpqNone || (getBasicType() != EbtInt && getBasicType() != EbtUint && getBasicType() != EbtFloat && getBasicType() != EbtFloat16)) return; getQualifier().precision = newPrecision; TIntermBinary* binaryNode = getAsBinaryNode(); if (binaryNode) { binaryNode->getLeft()->propagatePrecision(newPrecision); binaryNode->getRight()->propagatePrecision(newPrecision); return; } TIntermUnary* unaryNode = getAsUnaryNode(); if (unaryNode) { unaryNode->getOperand()->propagatePrecision(newPrecision); return; } TIntermAggregate* aggregateNode = getAsAggregate(); if (aggregateNode) { TIntermSequence operands = aggregateNode->getSequence(); for (unsigned int i = 0; i < operands.size(); ++i) { TIntermTyped* typedNode = operands[i]->getAsTyped(); if (! typedNode) break; typedNode->propagatePrecision(newPrecision); } return; } TIntermSelection* selectionNode = getAsSelectionNode(); if (selectionNode) { TIntermTyped* typedNode = selectionNode->getTrueBlock()->getAsTyped(); if (typedNode) { typedNode->propagatePrecision(newPrecision); typedNode = selectionNode->getFalseBlock()->getAsTyped(); if (typedNode) typedNode->propagatePrecision(newPrecision); } return; } } TIntermTyped* TIntermediate::promoteConstantUnion(TBasicType promoteTo, TIntermConstantUnion* node) const { const TConstUnionArray& rightUnionArray = node->getConstArray(); int size = node->getType().computeNumComponents(); TConstUnionArray leftUnionArray(size); for (int i=0; i < size; i++) { #define PROMOTE(Set, CType, Get) leftUnionArray[i].Set(static_cast(rightUnionArray[i].Get())) #define PROMOTE_TO_BOOL(Get) leftUnionArray[i].setBConst(rightUnionArray[i].Get() != 0) #ifdef GLSLANG_WEB #define TO_ALL(Get) \ switch (promoteTo) { \ case EbtFloat: PROMOTE(setDConst, double, Get); break; \ case EbtInt: PROMOTE(setIConst, int, Get); break; \ case EbtUint: PROMOTE(setUConst, unsigned int, Get); break; \ case EbtBool: PROMOTE_TO_BOOL(Get); break; \ default: return node; \ } #else #define TO_ALL(Get) \ switch (promoteTo) { \ case EbtFloat16: PROMOTE(setDConst, double, Get); break; \ case EbtFloat: PROMOTE(setDConst, double, Get); break; \ case EbtDouble: PROMOTE(setDConst, double, Get); break; \ case EbtInt8: PROMOTE(setI8Const, char, Get); break; \ case EbtInt16: PROMOTE(setI16Const, short, Get); break; \ case EbtInt: PROMOTE(setIConst, int, Get); break; \ case EbtInt64: PROMOTE(setI64Const, long long, Get); break; \ case EbtUint8: PROMOTE(setU8Const, unsigned char, Get); break; \ case EbtUint16: PROMOTE(setU16Const, unsigned short, Get); break; \ case EbtUint: PROMOTE(setUConst, unsigned int, Get); break; \ case EbtUint64: PROMOTE(setU64Const, unsigned long long, Get); break; \ case EbtBool: PROMOTE_TO_BOOL(Get); break; \ default: return node; \ } #endif switch (node->getType().getBasicType()) { case EbtFloat: TO_ALL(getDConst); break; case EbtInt: TO_ALL(getIConst); break; case EbtUint: TO_ALL(getUConst); break; case EbtBool: TO_ALL(getBConst); break; #ifndef GLSLANG_WEB case EbtFloat16: TO_ALL(getDConst); break; case EbtDouble: TO_ALL(getDConst); break; case EbtInt8: TO_ALL(getI8Const); break; case EbtInt16: TO_ALL(getI16Const); break; case EbtInt64: TO_ALL(getI64Const); break; case EbtUint8: TO_ALL(getU8Const); break; case EbtUint16: TO_ALL(getU16Const); break; case EbtUint64: TO_ALL(getU64Const); break; #endif default: return node; } } const TType& t = node->getType(); return addConstantUnion(leftUnionArray, TType(promoteTo, t.getQualifier().storage, t.getVectorSize(), t.getMatrixCols(), t.getMatrixRows()), node->getLoc()); } void TIntermAggregate::setPragmaTable(const TPragmaTable& pTable) { assert(pragmaTable == nullptr); pragmaTable = new TPragmaTable; *pragmaTable = pTable; } // If either node is a specialization constant, while the other is // a constant (or specialization constant), the result is still // a specialization constant. bool TIntermediate::specConstantPropagates(const TIntermTyped& node1, const TIntermTyped& node2) { return (node1.getType().getQualifier().isSpecConstant() && node2.getType().getQualifier().isConstant()) || (node2.getType().getQualifier().isSpecConstant() && node1.getType().getQualifier().isConstant()); } struct TextureUpgradeAndSamplerRemovalTransform : public TIntermTraverser { void visitSymbol(TIntermSymbol* symbol) override { if (symbol->getBasicType() == EbtSampler && symbol->getType().getSampler().isTexture()) { symbol->getWritableType().getSampler().setCombined(true); } } bool visitAggregate(TVisit, TIntermAggregate* ag) override { using namespace std; TIntermSequence& seq = ag->getSequence(); TQualifierList& qual = ag->getQualifierList(); // qual and seq are indexed using the same indices, so we have to modify both in lock-step assert(seq.size() == qual.size() || qual.empty()); size_t write = 0; for (size_t i = 0; i < seq.size(); ++i) { TIntermSymbol* symbol = seq[i]->getAsSymbolNode(); if (symbol && symbol->getBasicType() == EbtSampler && symbol->getType().getSampler().isPureSampler()) { // remove pure sampler variables continue; } TIntermNode* result = seq[i]; // replace constructors with sampler/textures TIntermAggregate *constructor = seq[i]->getAsAggregate(); if (constructor && constructor->getOp() == EOpConstructTextureSampler) { if (!constructor->getSequence().empty()) result = constructor->getSequence()[0]; } // write new node & qualifier seq[write] = result; if (!qual.empty()) qual[write] = qual[i]; write++; } seq.resize(write); if (!qual.empty()) qual.resize(write); return true; } }; void TIntermediate::performTextureUpgradeAndSamplerRemovalTransformation(TIntermNode* root) { TextureUpgradeAndSamplerRemovalTransform transform; root->traverse(&transform); } const char* TIntermediate::getResourceName(TResourceType res) { switch (res) { case EResSampler: return "shift-sampler-binding"; case EResTexture: return "shift-texture-binding"; case EResImage: return "shift-image-binding"; case EResUbo: return "shift-UBO-binding"; case EResSsbo: return "shift-ssbo-binding"; case EResUav: return "shift-uav-binding"; default: assert(0); // internal error: should only be called with valid resource types. return nullptr; } } } // end namespace glslang