John Kessenich deec1933e9 Web: Turn off includes, independent preprocessing path, fine tune all.
Saved about 21K, size down to 380K of MSVC x86 code.
Fixed one bug that needs to be looked at on the master branch:
The test for needing a Vulkan binding has a bug in it, "!layoutAttachment"
which does not mean "no layoutAttachment", because that is non-zero.
This is why some test and test results changed.
2019-08-20 23:21:56 -06:00

3945 lines
131 KiB
C++

//
// Copyright (C) 2002-2005 3Dlabs Inc. Ltd.
// Copyright (C) 2012-2015 LunarG, Inc.
// Copyright (C) 2015-2018 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 <cfloat>
#include <utility>
#include <tuple>
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(int 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, 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) && extensionRequested(E_GL_EXT_buffer_reference2)) {
// 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 = addConversion(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, TSourceLoc loc) const
{
// build the node
TIntermBinary* node = new TIntermBinary(op);
if (loc.line == 0)
loc = left->getLoc();
node->setLoc(loc);
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, 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, TSourceLoc loc) const
{
TIntermUnary* node = new TIntermUnary(op);
if (loc.line == 0)
loc = child->getLoc();
node->setLoc(loc);
node->setOperand(child);
return node;
}
//
// like non-type form, but sets node's type.
//
TIntermUnary* TIntermediate::addUnaryNode(TOperator op, TIntermTyped* child, 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, 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() &&
extensionRequested(E_GL_EXT_buffer_reference2)) {
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, 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, 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, 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);
if (loc.line == 0)
loc = node->getLoc();
}
} else
aggNode = new TIntermAggregate();
//
// Set the operator.
//
aggNode->setOperator(op);
if (loc.line != 0)
aggNode->setLoc(loc);
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 EbtAccStructNV:
// 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;
}
// 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.
//
TIntermUnary* newNode = nullptr;
TOperator newOp = EOpNull;
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 (! getArithemeticInt8Enabled()) {
if (((convertTo == EbtInt8 || convertTo == EbtUint8) && ! convertFromIntTypes) ||
((node->getBasicType() == EbtInt8 || node->getBasicType() == EbtUint8) && ! convertToIntTypes))
return nullptr;
}
if (! getArithemeticInt16Enabled()) {
if (((convertTo == EbtInt16 || convertTo == EbtUint16) && ! convertFromIntTypes) ||
((node->getBasicType() == EbtInt16 || node->getBasicType() == EbtUint16) && ! convertToIntTypes))
return nullptr;
}
if (! getArithemeticFloat16Enabled()) {
if ((convertTo == EbtFloat16 && ! convertFromFloatTypes) ||
(node->getBasicType() == EbtFloat16 && ! convertToFloatTypes))
return nullptr;
}
switch (convertTo) {
#ifndef GLSLANG_WEB
case EbtDouble:
switch (node->getBasicType()) {
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 nullptr;
}
break;
#endif
case EbtFloat:
switch (node->getBasicType()) {
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 nullptr;
}
break;
#ifndef GLSLANG_WEB
case EbtFloat16:
switch (node->getBasicType()) {
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 nullptr;
}
break;
#endif
case EbtBool:
switch (node->getBasicType()) {
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 nullptr;
}
break;
#ifndef GLSLANG_WEB
case EbtInt8:
switch (node->getBasicType()) {
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 nullptr;
}
break;
case EbtUint8:
switch (node->getBasicType()) {
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 nullptr;
}
break;
case EbtInt16:
switch (node->getBasicType()) {
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 nullptr;
}
break;
case EbtUint16:
switch (node->getBasicType()) {
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 nullptr;
}
break;
#endif
case EbtInt:
switch (node->getBasicType()) {
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 nullptr;
}
break;
case EbtUint:
switch (node->getBasicType()) {
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 nullptr;
}
break;
#ifndef GLSLANG_WEB
case EbtInt64:
switch (node->getBasicType()) {
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 nullptr;
}
break;
case EbtUint64:
switch (node->getBasicType()) {
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 nullptr;
}
break;
#endif
default:
return nullptr;
}
TType newType(convertTo, EvqTemporary, node->getVectorSize(), node->getMatrixCols(), node->getMatrixRows());
newNode = addUnaryNode(newOp, node, node->getLoc(), newType);
if (node->getAsConstantUnion()) {
// 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))) {
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 <nullptr, nullptr> when there is no conversion.
std::tuple<TIntermTyped*, TIntermTyped*>
TIntermediate::addConversion(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 = getConversionDestinatonType(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.
TBasicType promoteTo;
// GL_EXT_shader_16bit_storage can't do OpConstantComposite with
// 16-bit types, so disable promotion for those types.
bool canPromoteConstant = true;
switch (op) {
//
// Explicit conversions (unary operations)
//
case EOpConstructBool:
promoteTo = EbtBool;
break;
case EOpConstructFloat:
promoteTo = EbtFloat;
break;
case EOpConstructInt:
promoteTo = EbtInt;
break;
case EOpConstructUint:
promoteTo = EbtUint;
break;
#ifndef GLSLANG_WEB
case EOpConstructDouble:
promoteTo = EbtDouble;
break;
case EOpConstructFloat16:
promoteTo = EbtFloat16;
canPromoteConstant = extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_float16);
break;
case EOpConstructInt8:
promoteTo = EbtInt8;
canPromoteConstant = extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int8);
break;
case EOpConstructUint8:
promoteTo = EbtUint8;
canPromoteConstant = extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int8);
break;
case EOpConstructInt16:
promoteTo = EbtInt16;
canPromoteConstant = extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int16);
break;
case EOpConstructUint16:
promoteTo = EbtUint16;
canPromoteConstant = extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int16);
break;
case EOpConstructInt64:
promoteTo = EbtInt64;
break;
case EOpConstructUint64:
promoteTo = EbtUint64;
break;
#endif
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 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))
promoteTo = type.getBasicType();
else
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)
promoteTo = type.getBasicType();
else {
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;
}
if (canPromoteConstant && node->getAsConstantUnion())
return promoteConstantUnion(promoteTo, node->getAsConstantUnion());
//
// Add a new newNode for the conversion.
//
TIntermTyped* newNode = createConversion(promoteTo, 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 == 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;
}
}
}
bool explicitTypesEnabled = extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int8) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int16) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int32) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_int64) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_float16) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_float32) ||
extensionRequested(E_GL_EXT_shader_explicit_arithmetic_types_float64);
if (explicitTypesEnabled) {
// integral promotions
if (isIntegralPromotion(from, to)) {
return true;
}
// floating-point promotions
if (isFPPromotion(from, to)) {
return true;
}
// integral conversions
if (isIntegralConversion(from, to)) {
return true;
}
// floating-point conversions
if (isFPConversion(from, to)) {
return true;
}
// floating-integral conversions
if (isFPIntegralConversion(from, to)) {
return true;
}
// hlsl supported conversions
if (getSource() == EShSourceHlsl) {
if (from == EbtBool && (to == EbtInt || to == EbtUint || to == EbtFloat))
return true;
}
} else {
switch (to) {
case EbtDouble:
switch (from) {
case EbtInt:
case EbtUint:
case EbtInt64:
case EbtUint64:
case EbtFloat:
case EbtDouble:
return true;
case EbtInt16:
case EbtUint16:
return extensionRequested(E_GL_AMD_gpu_shader_int16);
case EbtFloat16:
return extensionRequested(E_GL_AMD_gpu_shader_half_float);
default:
return false;
}
case EbtFloat:
switch (from) {
case EbtInt:
case EbtUint:
case EbtFloat:
return true;
case EbtBool:
return getSource() == EShSourceHlsl;
case EbtInt16:
case EbtUint16:
return extensionRequested(E_GL_AMD_gpu_shader_int16);
case EbtFloat16:
return
extensionRequested(E_GL_AMD_gpu_shader_half_float) ||
getSource() == EShSourceHlsl;
default:
return false;
}
case EbtUint:
switch (from) {
case EbtInt:
return version >= 400 || getSource() == EShSourceHlsl;
case EbtUint:
return true;
case EbtBool:
return getSource() == EShSourceHlsl;
case EbtInt16:
case EbtUint16:
return extensionRequested(E_GL_AMD_gpu_shader_int16);
default:
return false;
}
case EbtInt:
switch (from) {
case EbtInt:
return true;
case EbtBool:
return getSource() == EShSourceHlsl;
case EbtInt16:
return extensionRequested(E_GL_AMD_gpu_shader_int16);
default:
return false;
}
case EbtUint64:
switch (from) {
case EbtInt:
case EbtUint:
case EbtInt64:
case EbtUint64:
return true;
case EbtInt16:
case EbtUint16:
return extensionRequested(E_GL_AMD_gpu_shader_int16);
default:
return false;
}
case EbtInt64:
switch (from) {
case EbtInt:
case EbtInt64:
return true;
case EbtInt16:
return extensionRequested(E_GL_AMD_gpu_shader_int16);
default:
return false;
}
case EbtFloat16:
switch (from) {
case EbtInt16:
case EbtUint16:
return extensionRequested(E_GL_AMD_gpu_shader_int16);
case EbtFloat16:
return extensionRequested(E_GL_AMD_gpu_shader_half_float);
default:
break;
}
return false;
case EbtUint16:
switch (from) {
case EbtInt16:
case EbtUint16:
return extensionRequested(E_GL_AMD_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<TBasicType, TBasicType> TIntermediate::getConversionDestinatonType(TBasicType type0, TBasicType type1, TOperator op) const
{
TBasicType res0 = EbtNumTypes;
TBasicType res1 = EbtNumTypes;
if (isEsProfile() || 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;
#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 = addConversion(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<TVectorSelector>(TSwizzleSelectors<TVectorSelector>& selector, const TSourceLoc& loc);
template TIntermTyped* TIntermediate::addSwizzle<TMatrixSelector>(TSwizzleSelectors<TMatrixSelector>& selector, const TSourceLoc& loc);
template<typename selectorType>
TIntermTyped* TIntermediate::addSwizzle(TSwizzleSelectors<selectorType>& 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.
//
const TIntermTyped* TIntermediate::findLValueBase(const TIntermTyped* node, bool swizzleOkay)
{
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();
} 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;
}
//
// 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;
}
#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.push_front(TCall(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:
if (operand->getBasicType() != EbtFloat)
return false;
}
node.setType(operand->getType());
node.getWritableType().getQualifier().makeTemporary();
return true;
}
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<int>(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;
}
void TIntermBinary::updatePrecision()
{
if (getBasicType() == EbtInt || getBasicType() == EbtUint || getBasicType() == EbtFloat || getBasicType() == EbtFloat16) {
getQualifier().precision = std::max(right->getQualifier().precision, left->getQualifier().precision);
if (getQualifier().precision != EpqNone) {
left->propagatePrecision(getQualifier().precision);
right->propagatePrecision(getQualifier().precision);
}
}
}
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<CType>(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