blob: adef17611b2a32b3aeb0db799bd1da4002ce2639 [file] [log] [blame]
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include <cctype>
#include <map>
#include "common/system_utils.h"
#include "compiler/translator/BaseTypes.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/Name.h"
#include "compiler/translator/OutputTree.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/msl/AstHelpers.h"
#include "compiler/translator/msl/DebugSink.h"
#include "compiler/translator/msl/EmitMetal.h"
#include "compiler/translator/msl/Layout.h"
#include "compiler/translator/msl/ProgramPrelude.h"
#include "compiler/translator/msl/RewritePipelines.h"
#include "compiler/translator/msl/TranslatorMSL.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
using namespace sh;
////////////////////////////////////////////////////////////////////////////////
#if defined(ANGLE_ENABLE_ASSERTS)
using Sink = DebugSink;
#else
using Sink = TInfoSinkBase;
#endif
////////////////////////////////////////////////////////////////////////////////
namespace
{
struct VarDecl
{
explicit VarDecl(const TVariable &var) : mVariable(&var), mIsField(false) {}
explicit VarDecl(const TField &field) : mField(&field), mIsField(true) {}
ANGLE_INLINE const TVariable &variable() const
{
ASSERT(isVariable());
return *mVariable;
}
ANGLE_INLINE const TField &field() const
{
ASSERT(isField());
return *mField;
}
ANGLE_INLINE bool isVariable() const { return !mIsField; }
ANGLE_INLINE bool isField() const { return mIsField; }
const TType &type() const { return isField() ? *field().type() : variable().getType(); }
SymbolType symbolType() const
{
return isField() ? field().symbolType() : variable().symbolType();
}
private:
union
{
const TVariable *mVariable;
const TField *mField;
};
bool mIsField;
};
class GenMetalTraverser : public TIntermTraverser
{
public:
~GenMetalTraverser() override;
GenMetalTraverser(const TCompiler &compiler,
Sink &out,
IdGen &idGen,
const PipelineStructs &pipelineStructs,
SymbolEnv &symbolEnv,
const ShCompileOptions &compileOptions);
void visitSymbol(TIntermSymbol *) override;
void visitConstantUnion(TIntermConstantUnion *) override;
bool visitSwizzle(Visit, TIntermSwizzle *) override;
bool visitBinary(Visit, TIntermBinary *) override;
bool visitUnary(Visit, TIntermUnary *) override;
bool visitTernary(Visit, TIntermTernary *) override;
bool visitIfElse(Visit, TIntermIfElse *) override;
bool visitSwitch(Visit, TIntermSwitch *) override;
bool visitCase(Visit, TIntermCase *) override;
void visitFunctionPrototype(TIntermFunctionPrototype *) override;
bool visitFunctionDefinition(Visit, TIntermFunctionDefinition *) override;
bool visitAggregate(Visit, TIntermAggregate *) override;
bool visitBlock(Visit, TIntermBlock *) override;
bool visitGlobalQualifierDeclaration(Visit, TIntermGlobalQualifierDeclaration *) override;
bool visitDeclaration(Visit, TIntermDeclaration *) override;
bool visitLoop(Visit, TIntermLoop *) override;
bool visitForLoop(TIntermLoop *);
bool visitWhileLoop(TIntermLoop *);
bool visitDoWhileLoop(TIntermLoop *);
bool visitBranch(Visit, TIntermBranch *) override;
private:
using FuncToName = std::map<ImmutableString, Name>;
static FuncToName BuildFuncToName();
struct EmitVariableDeclarationConfig
{
bool isParameter = false;
bool isMainParameter = false;
bool emitPostQualifier = false;
bool isPacked = false;
bool disableStructSpecifier = false;
bool isUBO = false;
const AddressSpace *isPointer = nullptr;
const AddressSpace *isReference = nullptr;
};
struct EmitTypeConfig
{
const EmitVariableDeclarationConfig *evdConfig = nullptr;
};
void emitIndentation();
void emitOpeningPointerParen();
void emitClosingPointerParen();
void emitFunctionSignature(const TFunction &func);
void emitFunctionReturn(const TFunction &func);
void emitFunctionParameter(const TFunction &func, const TVariable &param);
void emitNameOf(const TField &object);
void emitNameOf(const TSymbol &object);
void emitNameOf(const VarDecl &object);
void emitBareTypeName(const TType &type, const EmitTypeConfig &etConfig);
void emitType(const TType &type, const EmitTypeConfig &etConfig);
void emitPostQualifier(const EmitVariableDeclarationConfig &evdConfig,
const VarDecl &decl,
const TQualifier qualifier);
void emitLoopBody(TIntermBlock *bodyNode);
struct FieldAnnotationIndices
{
size_t attribute = 0;
size_t color = 0;
};
void emitFieldDeclaration(const TField &field,
const TStructure &parent,
FieldAnnotationIndices &annotationIndices);
void emitAttributeDeclaration(const TField &field, FieldAnnotationIndices &annotationIndices);
void emitUniformBufferDeclaration(const TField &field,
FieldAnnotationIndices &annotationIndices);
void emitStructDeclaration(const TType &type);
void emitOrdinaryVariableDeclaration(const VarDecl &decl,
const EmitVariableDeclarationConfig &evdConfig);
void emitVariableDeclaration(const VarDecl &decl,
const EmitVariableDeclarationConfig &evdConfig);
void emitOpenBrace();
void emitCloseBrace();
void groupedTraverse(TIntermNode &node);
const TField &getDirectField(const TFieldListCollection &fieldsNode,
const TConstantUnion &index);
const TField &getDirectField(const TIntermTyped &fieldsNode, TIntermTyped &indexNode);
const TConstantUnion *emitConstantUnionArray(const TConstantUnion *const constUnion,
const size_t size);
const TConstantUnion *emitConstantUnion(const TType &type, const TConstantUnion *constUnion);
void emitSingleConstant(const TConstantUnion *const constUnion);
private:
Sink &mOut;
const TCompiler &mCompiler;
const PipelineStructs &mPipelineStructs;
SymbolEnv &mSymbolEnv;
IdGen &mIdGen;
int mIndentLevel = -1;
int mLastIndentationPos = -1;
int mOpenPointerParenCount = 0;
bool mParentIsSwitch = false;
bool isTraversingVertexMain = false;
bool mTemporarilyDisableSemicolon = false;
std::unordered_map<const TSymbol *, Name> mRenamedSymbols;
const FuncToName mFuncToName = BuildFuncToName();
size_t mMainTextureIndex = 0;
size_t mMainSamplerIndex = 0;
size_t mMainUniformBufferIndex = 0;
size_t mDriverUniformsBindingIndex = 0;
size_t mUBOArgumentBufferBindingIndex = 0;
bool mRasterOrderGroupsSupported = false;
bool mInjectAsmStatementIntoLoopBodies = false;
};
} // anonymous namespace
GenMetalTraverser::~GenMetalTraverser()
{
ASSERT(mIndentLevel == -1);
ASSERT(!mParentIsSwitch);
ASSERT(mOpenPointerParenCount == 0);
}
GenMetalTraverser::GenMetalTraverser(const TCompiler &compiler,
Sink &out,
IdGen &idGen,
const PipelineStructs &pipelineStructs,
SymbolEnv &symbolEnv,
const ShCompileOptions &compileOptions)
: TIntermTraverser(true, false, false),
mOut(out),
mCompiler(compiler),
mPipelineStructs(pipelineStructs),
mSymbolEnv(symbolEnv),
mIdGen(idGen),
mMainUniformBufferIndex(compileOptions.metal.defaultUniformsBindingIndex),
mDriverUniformsBindingIndex(compileOptions.metal.driverUniformsBindingIndex),
mUBOArgumentBufferBindingIndex(compileOptions.metal.UBOArgumentBufferBindingIndex),
mRasterOrderGroupsSupported(compileOptions.pls.fragmentSyncType ==
ShFragmentSynchronizationType::RasterOrderGroups_Metal),
mInjectAsmStatementIntoLoopBodies(compileOptions.metal.injectAsmStatementIntoLoopBodies)
{}
void GenMetalTraverser::emitIndentation()
{
ASSERT(mIndentLevel >= 0);
if (mLastIndentationPos == mOut.size())
{
return; // Line is already indented.
}
for (int i = 0; i < mIndentLevel; ++i)
{
mOut << " ";
}
mLastIndentationPos = mOut.size();
}
void GenMetalTraverser::emitOpeningPointerParen()
{
mOut << "(*";
mOpenPointerParenCount++;
}
void GenMetalTraverser::emitClosingPointerParen()
{
if (mOpenPointerParenCount > 0)
{
mOut << ")";
mOpenPointerParenCount--;
}
}
static const char *GetOperatorString(TOperator op,
const TType &resultType,
const TType *argType0,
const TType *argType1,
const TType *argType2)
{
switch (op)
{
case TOperator::EOpComma:
return ",";
case TOperator::EOpAssign:
return "=";
case TOperator::EOpInitialize:
return "=";
case TOperator::EOpAddAssign:
return "+=";
case TOperator::EOpSubAssign:
return "-=";
case TOperator::EOpMulAssign:
return "*=";
case TOperator::EOpDivAssign:
return "/=";
case TOperator::EOpIModAssign:
return "%=";
case TOperator::EOpBitShiftLeftAssign:
return "<<="; // TODO: Check logical vs arithmetic shifting.
case TOperator::EOpBitShiftRightAssign:
return ">>="; // TODO: Check logical vs arithmetic shifting.
case TOperator::EOpBitwiseAndAssign:
return "&=";
case TOperator::EOpBitwiseXorAssign:
return "^=";
case TOperator::EOpBitwiseOrAssign:
return "|=";
case TOperator::EOpAdd:
return "+";
case TOperator::EOpSub:
return "-";
case TOperator::EOpMul:
return "*";
case TOperator::EOpDiv:
return "/";
case TOperator::EOpIMod:
return "%";
case TOperator::EOpBitShiftLeft:
return "<<"; // TODO: Check logical vs arithmetic shifting.
case TOperator::EOpBitShiftRight:
return ">>"; // TODO: Check logical vs arithmetic shifting.
case TOperator::EOpBitwiseAnd:
return "&";
case TOperator::EOpBitwiseXor:
return "^";
case TOperator::EOpBitwiseOr:
return "|";
case TOperator::EOpLessThan:
return "<";
case TOperator::EOpGreaterThan:
return ">";
case TOperator::EOpLessThanEqual:
return "<=";
case TOperator::EOpGreaterThanEqual:
return ">=";
case TOperator::EOpLessThanComponentWise:
return "<";
case TOperator::EOpLessThanEqualComponentWise:
return "<=";
case TOperator::EOpGreaterThanEqualComponentWise:
return ">=";
case TOperator::EOpGreaterThanComponentWise:
return ">";
case TOperator::EOpLogicalOr:
return "||";
case TOperator::EOpLogicalXor:
return "!=/*xor*/"; // XXX: This might need to be handled differently for some obtuse
// use case.
case TOperator::EOpLogicalAnd:
return "&&";
case TOperator::EOpNegative:
return "-";
case TOperator::EOpPositive:
if (argType0->isMatrix())
{
return "";
}
return "+";
case TOperator::EOpLogicalNot:
return "!";
case TOperator::EOpNotComponentWise:
return "!";
case TOperator::EOpBitwiseNot:
return "~";
case TOperator::EOpPostIncrement:
return "++";
case TOperator::EOpPostDecrement:
return "--";
case TOperator::EOpPreIncrement:
return "++";
case TOperator::EOpPreDecrement:
return "--";
case TOperator::EOpVectorTimesScalarAssign:
return "*=";
case TOperator::EOpVectorTimesMatrixAssign:
return "*=";
case TOperator::EOpMatrixTimesScalarAssign:
return "*=";
case TOperator::EOpMatrixTimesMatrixAssign:
return "*=";
case TOperator::EOpVectorTimesScalar:
return "*";
case TOperator::EOpVectorTimesMatrix:
return "*";
case TOperator::EOpMatrixTimesVector:
return "*";
case TOperator::EOpMatrixTimesScalar:
return "*";
case TOperator::EOpMatrixTimesMatrix:
return "*";
case TOperator::EOpEqualComponentWise:
return "==";
case TOperator::EOpNotEqualComponentWise:
return "!=";
case TOperator::EOpEqual:
if ((argType0->getStruct() && argType1->getStruct()) &&
(argType0->isArray() && argType1->isArray()))
{
return "ANGLE_equalStructArray";
}
if ((argType0->isVector() && argType1->isVector()) ||
(argType0->getStruct() && argType1->getStruct()) ||
(argType0->isArray() && argType1->isArray()) ||
(argType0->isMatrix() && argType1->isMatrix()))
{
return "ANGLE_equal";
}
return "==";
case TOperator::EOpNotEqual:
if ((argType0->getStruct() && argType1->getStruct()) &&
(argType0->isArray() && argType1->isArray()))
{
return "ANGLE_notEqualStructArray";
}
if ((argType0->isVector() && argType1->isVector()) ||
(argType0->isArray() && argType1->isArray()) ||
(argType0->isMatrix() && argType1->isMatrix()))
{
return "ANGLE_notEqual";
}
else if (argType0->getStruct() && argType1->getStruct())
{
return "ANGLE_notEqualStruct";
}
return "!=";
case TOperator::EOpKill:
UNIMPLEMENTED();
return "kill";
case TOperator::EOpReturn:
return "return";
case TOperator::EOpBreak:
return "break";
case TOperator::EOpContinue:
return "continue";
case TOperator::EOpRadians:
return "ANGLE_radians";
case TOperator::EOpDegrees:
return "ANGLE_degrees";
case TOperator::EOpAtan:
return argType1 == nullptr ? "metal::atan" : "metal::atan2";
case TOperator::EOpMod:
return "ANGLE_mod"; // differs from metal::mod
case TOperator::EOpRefract:
return argType0->isVector() ? "metal::refract" : "ANGLE_refract_scalar";
case TOperator::EOpDistance:
return argType0->isVector() ? "metal::distance" : "ANGLE_distance_scalar";
case TOperator::EOpLength:
return argType0->isVector() ? "metal::length" : "metal::abs";
case TOperator::EOpDot:
return argType0->isVector() ? "metal::dot" : "*";
case TOperator::EOpNormalize:
return argType0->isVector() ? "metal::fast::normalize" : "metal::sign";
case TOperator::EOpFaceforward:
return argType0->isVector() ? "metal::faceforward" : "ANGLE_faceforward_scalar";
case TOperator::EOpReflect:
return argType0->isVector() ? "metal::reflect" : "ANGLE_reflect_scalar";
case TOperator::EOpMatrixCompMult:
return "ANGLE_componentWiseMultiply";
case TOperator::EOpOuterProduct:
return "ANGLE_outerProduct";
case TOperator::EOpSign:
return argType0->getBasicType() == EbtFloat ? "metal::sign" : "ANGLE_sign_int";
case TOperator::EOpAbs:
return "metal::abs";
case TOperator::EOpAll:
return "metal::all";
case TOperator::EOpAny:
return "metal::any";
case TOperator::EOpSin:
return "metal::sin";
case TOperator::EOpCos:
return "metal::cos";
case TOperator::EOpTan:
return "metal::tan";
case TOperator::EOpAsin:
return "metal::asin";
case TOperator::EOpAcos:
return "metal::acos";
case TOperator::EOpSinh:
return "metal::sinh";
case TOperator::EOpCosh:
return "metal::cosh";
case TOperator::EOpTanh:
return resultType.getPrecision() == TPrecision::EbpHigh ? "metal::precise::tanh"
: "metal::tanh";
case TOperator::EOpAsinh:
return "metal::asinh";
case TOperator::EOpAcosh:
return "metal::acosh";
case TOperator::EOpAtanh:
return "metal::atanh";
case TOperator::EOpFma:
return "metal::fma";
case TOperator::EOpPow:
return "metal::powr"; // GLSL's pow excludes negative x
case TOperator::EOpExp:
return "metal::exp";
case TOperator::EOpExp2:
return "metal::exp2";
case TOperator::EOpLog:
return "metal::log";
case TOperator::EOpLog2:
return "metal::log2";
case TOperator::EOpSqrt:
return "metal::sqrt";
case TOperator::EOpFloor:
return "metal::floor";
case TOperator::EOpTrunc:
return "metal::trunc";
case TOperator::EOpCeil:
return "metal::ceil";
case TOperator::EOpFract:
return "metal::fract";
case TOperator::EOpMin:
return "metal::min";
case TOperator::EOpMax:
return "metal::max";
case TOperator::EOpRound:
return "metal::round";
case TOperator::EOpRoundEven:
return "metal::rint";
case TOperator::EOpClamp:
return "metal::clamp"; // TODO fast vs precise namespace
case TOperator::EOpSaturate:
return "metal::saturate"; // TODO fast vs precise namespace
case TOperator::EOpMix:
if (!argType1->isScalar() && argType2 && argType2->getBasicType() == EbtBool)
{
return "ANGLE_mix_bool";
}
return "metal::mix";
case TOperator::EOpStep:
return "metal::step";
case TOperator::EOpSmoothstep:
return "metal::smoothstep";
case TOperator::EOpModf:
return "metal::modf";
case TOperator::EOpIsnan:
return "metal::isnan";
case TOperator::EOpIsinf:
return "metal::isinf";
case TOperator::EOpLdexp:
return "metal::ldexp";
case TOperator::EOpFrexp:
return "metal::frexp";
case TOperator::EOpInversesqrt:
return "metal::rsqrt";
case TOperator::EOpCross:
return "metal::cross";
case TOperator::EOpDFdx:
return "metal::dfdx";
case TOperator::EOpDFdy:
return "metal::dfdy";
case TOperator::EOpFwidth:
return "metal::fwidth";
case TOperator::EOpTranspose:
return "metal::transpose";
case TOperator::EOpDeterminant:
return "metal::determinant";
case TOperator::EOpInverse:
return "ANGLE_inverse";
case TOperator::EOpInterpolateAtCentroid:
return "ANGLE_interpolateAtCentroid";
case TOperator::EOpInterpolateAtSample:
return "ANGLE_interpolateAtSample";
case TOperator::EOpInterpolateAtOffset:
return "ANGLE_interpolateAtOffset";
case TOperator::EOpInterpolateAtCenter:
return "ANGLE_interpolateAtCenter";
case TOperator::EOpFloatBitsToInt:
case TOperator::EOpFloatBitsToUint:
case TOperator::EOpIntBitsToFloat:
case TOperator::EOpUintBitsToFloat:
{
#define RETURN_AS_TYPE_SCALAR() \
do \
switch (resultType.getBasicType()) \
{ \
case TBasicType::EbtInt: \
return "as_type<int>"; \
case TBasicType::EbtUInt: \
return "as_type<uint32_t>"; \
case TBasicType::EbtFloat: \
return "as_type<float>"; \
default: \
UNIMPLEMENTED(); \
return "TOperator_TODO"; \
} \
while (false)
#define RETURN_AS_TYPE(post) \
do \
switch (resultType.getBasicType()) \
{ \
case TBasicType::EbtInt: \
return "as_type<int" post ">"; \
case TBasicType::EbtUInt: \
return "as_type<uint" post ">"; \
case TBasicType::EbtFloat: \
return "as_type<float" post ">"; \
default: \
UNIMPLEMENTED(); \
return "TOperator_TODO"; \
} \
while (false)
if (resultType.isScalar())
{
RETURN_AS_TYPE_SCALAR();
}
else if (resultType.isVector())
{
switch (resultType.getNominalSize())
{
case 2:
RETURN_AS_TYPE("2");
case 3:
RETURN_AS_TYPE("3");
case 4:
RETURN_AS_TYPE("4");
default:
UNREACHABLE();
return nullptr;
}
}
else
{
UNIMPLEMENTED();
return "TOperator_TODO";
}
#undef RETURN_AS_TYPE
#undef RETURN_AS_TYPE_SCALAR
}
case TOperator::EOpPackUnorm2x16:
return "metal::pack_float_to_unorm2x16";
case TOperator::EOpPackSnorm2x16:
return "metal::pack_float_to_snorm2x16";
case TOperator::EOpPackUnorm4x8:
return "metal::pack_float_to_unorm4x8";
case TOperator::EOpPackSnorm4x8:
return "metal::pack_float_to_snorm4x8";
case TOperator::EOpUnpackUnorm2x16:
return "metal::unpack_unorm2x16_to_float";
case TOperator::EOpUnpackSnorm2x16:
return "metal::unpack_snorm2x16_to_float";
case TOperator::EOpUnpackUnorm4x8:
return "metal::unpack_unorm4x8_to_float";
case TOperator::EOpUnpackSnorm4x8:
return "metal::unpack_snorm4x8_to_float";
case TOperator::EOpPackHalf2x16:
return "ANGLE_pack_half_2x16";
case TOperator::EOpUnpackHalf2x16:
return "ANGLE_unpack_half_2x16";
case TOperator::EOpNumSamples:
return "metal::get_num_samples";
case TOperator::EOpSamplePosition:
return "metal::get_sample_position";
case TOperator::EOpBitfieldExtract:
case TOperator::EOpBitfieldInsert:
case TOperator::EOpBitfieldReverse:
case TOperator::EOpBitCount:
case TOperator::EOpFindLSB:
case TOperator::EOpFindMSB:
case TOperator::EOpUaddCarry:
case TOperator::EOpUsubBorrow:
case TOperator::EOpUmulExtended:
case TOperator::EOpImulExtended:
case TOperator::EOpBarrier:
case TOperator::EOpMemoryBarrier:
case TOperator::EOpMemoryBarrierAtomicCounter:
case TOperator::EOpMemoryBarrierBuffer:
case TOperator::EOpMemoryBarrierShared:
case TOperator::EOpGroupMemoryBarrier:
case TOperator::EOpAtomicAdd:
case TOperator::EOpAtomicMin:
case TOperator::EOpAtomicMax:
case TOperator::EOpAtomicAnd:
case TOperator::EOpAtomicOr:
case TOperator::EOpAtomicXor:
case TOperator::EOpAtomicExchange:
case TOperator::EOpAtomicCompSwap:
case TOperator::EOpEmitVertex:
case TOperator::EOpEndPrimitive:
case TOperator::EOpArrayLength:
UNIMPLEMENTED();
return "TOperator_TODO";
case TOperator::EOpNull:
case TOperator::EOpConstruct:
case TOperator::EOpCallFunctionInAST:
case TOperator::EOpCallInternalRawFunction:
case TOperator::EOpIndexDirect:
case TOperator::EOpIndexIndirect:
case TOperator::EOpIndexDirectStruct:
case TOperator::EOpIndexDirectInterfaceBlock:
UNREACHABLE();
return nullptr;
default:
// Any other built-in function.
return nullptr;
}
}
static bool IsSymbolicOperator(TOperator op,
const TType &resultType,
const TType *argType0,
const TType *argType1)
{
const char *operatorString = GetOperatorString(op, resultType, argType0, argType1, nullptr);
if (operatorString == nullptr)
{
return false;
}
return !std::isalnum(operatorString[0]);
}
static TIntermBinary *AsSpecificBinaryNode(TIntermNode &node, TOperator op)
{
TIntermBinary *binaryNode = node.getAsBinaryNode();
if (binaryNode)
{
return binaryNode->getOp() == op ? binaryNode : nullptr;
}
return nullptr;
}
static bool Parenthesize(TIntermNode &node)
{
if (node.getAsSymbolNode())
{
return false;
}
if (node.getAsConstantUnion())
{
return false;
}
if (node.getAsAggregate())
{
return false;
}
if (node.getAsSwizzleNode())
{
return false;
}
if (TIntermUnary *unaryNode = node.getAsUnaryNode())
{
// TODO: Use a precedence and associativity rules instead of this ad-hoc impl.
const TType &resultType = unaryNode->getType();
const TType &argType = unaryNode->getOperand()->getType();
return IsSymbolicOperator(unaryNode->getOp(), resultType, &argType, nullptr);
}
if (TIntermBinary *binaryNode = node.getAsBinaryNode())
{
// TODO: Use a precedence and associativity rules instead of this ad-hoc impl.
const TOperator op = binaryNode->getOp();
switch (op)
{
case TOperator::EOpIndexDirectStruct:
case TOperator::EOpIndexDirectInterfaceBlock:
case TOperator::EOpIndexDirect:
case TOperator::EOpIndexIndirect:
return Parenthesize(*binaryNode->getLeft());
case TOperator::EOpAssign:
case TOperator::EOpInitialize:
return AsSpecificBinaryNode(*binaryNode->getRight(), TOperator::EOpComma);
default:
{
const TType &resultType = binaryNode->getType();
const TType &leftType = binaryNode->getLeft()->getType();
const TType &rightType = binaryNode->getRight()->getType();
return IsSymbolicOperator(binaryNode->getOp(), resultType, &leftType, &rightType);
}
}
}
return true;
}
void GenMetalTraverser::groupedTraverse(TIntermNode &node)
{
const bool emitParens = Parenthesize(node);
if (emitParens)
{
mOut << "(";
}
node.traverse(this);
if (emitParens)
{
mOut << ")";
}
}
void GenMetalTraverser::emitPostQualifier(const EmitVariableDeclarationConfig &evdConfig,
const VarDecl &decl,
const TQualifier qualifier)
{
bool isInvariant = false;
switch (qualifier)
{
case TQualifier::EvqPosition:
isInvariant = decl.type().isInvariant();
[[fallthrough]];
case TQualifier::EvqFragCoord:
mOut << " [[position]]";
break;
case TQualifier::EvqClipDistance:
mOut << " [[clip_distance]] [" << decl.type().getOutermostArraySize() << "]";
break;
case TQualifier::EvqPointSize:
mOut << " [[point_size]]";
break;
case TQualifier::EvqVertexID:
if (evdConfig.isMainParameter)
{
mOut << " [[vertex_id]]";
}
break;
case TQualifier::EvqPointCoord:
if (evdConfig.isMainParameter)
{
mOut << " [[point_coord]]";
}
break;
case TQualifier::EvqFrontFacing:
if (evdConfig.isMainParameter)
{
mOut << " [[front_facing]]";
}
break;
case TQualifier::EvqSampleID:
if (evdConfig.isMainParameter)
{
mOut << " [[sample_id]]";
}
break;
case TQualifier::EvqSampleMaskIn:
if (evdConfig.isMainParameter)
{
mOut << " [[sample_mask]]";
}
break;
default:
break;
}
if (isInvariant)
{
mOut << " [[invariant]]";
TranslatorMetalReflection *reflection = mtl::getTranslatorMetalReflection(&mCompiler);
reflection->hasInvariance = true;
}
}
void GenMetalTraverser::emitLoopBody(TIntermBlock *bodyNode)
{
if (mInjectAsmStatementIntoLoopBodies)
{
emitOpenBrace();
emitIndentation();
mOut << "__asm__(\"\");\n";
}
bodyNode->traverse(this);
if (mInjectAsmStatementIntoLoopBodies)
{
emitCloseBrace();
}
}
static void EmitName(Sink &out, const Name &name)
{
#if defined(ANGLE_ENABLE_ASSERTS)
DebugSink::EscapedSink escapedOut(out.escape());
#else
TInfoSinkBase &escapedOut = out;
#endif
name.emit(escapedOut);
}
void GenMetalTraverser::emitNameOf(const TField &object)
{
EmitName(mOut, Name(object));
}
void GenMetalTraverser::emitNameOf(const TSymbol &object)
{
auto it = mRenamedSymbols.find(&object);
if (it == mRenamedSymbols.end())
{
EmitName(mOut, Name(object));
}
else
{
EmitName(mOut, it->second);
}
}
void GenMetalTraverser::emitNameOf(const VarDecl &object)
{
if (object.isField())
{
emitNameOf(object.field());
}
else
{
emitNameOf(object.variable());
}
}
void GenMetalTraverser::emitBareTypeName(const TType &type, const EmitTypeConfig &etConfig)
{
const TBasicType basicType = type.getBasicType();
switch (basicType)
{
case TBasicType::EbtVoid:
case TBasicType::EbtBool:
case TBasicType::EbtFloat:
case TBasicType::EbtInt:
{
mOut << type.getBasicString();
break;
}
case TBasicType::EbtUInt:
{
if (type.isScalar())
{
mOut << "uint32_t";
}
else
{
mOut << type.getBasicString();
}
}
break;
case TBasicType::EbtStruct:
{
const TStructure &structure = *type.getStruct();
emitNameOf(structure);
}
break;
case TBasicType::EbtInterfaceBlock:
{
const TInterfaceBlock &interfaceBlock = *type.getInterfaceBlock();
emitNameOf(interfaceBlock);
}
break;
default:
{
if (IsSampler(basicType))
{
if (etConfig.evdConfig && etConfig.evdConfig->isMainParameter)
{
EmitName(mOut, GetTextureTypeName(basicType));
}
else
{
const TStructure &env = mSymbolEnv.getTextureEnv(basicType);
emitNameOf(env);
}
}
else if (IsImage(basicType))
{
mOut << "metal::texture2d<";
switch (type.getBasicType())
{
case EbtImage2D:
mOut << "float";
break;
case EbtIImage2D:
mOut << "int";
break;
case EbtUImage2D:
mOut << "uint";
break;
default:
UNIMPLEMENTED();
break;
}
if (type.getMemoryQualifier().readonly || type.getMemoryQualifier().writeonly)
{
UNIMPLEMENTED();
}
mOut << ", metal::access::read_write>";
}
else
{
UNIMPLEMENTED();
}
}
}
}
void GenMetalTraverser::emitType(const TType &type, const EmitTypeConfig &etConfig)
{
const bool isUBO = etConfig.evdConfig ? etConfig.evdConfig->isUBO : false;
if (etConfig.evdConfig)
{
const auto &evdConfig = *etConfig.evdConfig;
if (isUBO)
{
if (type.isArray())
{
mOut << "metal::array<";
}
}
if (evdConfig.isPointer)
{
mOut << toString(*evdConfig.isPointer);
mOut << " ";
}
else if (evdConfig.isReference)
{
mOut << toString(*evdConfig.isReference);
mOut << " ";
}
}
if (!isUBO)
{
if (type.isArray())
{
mOut << "metal::array<";
}
}
if (type.isInterpolant())
{
mOut << "metal::interpolant<";
}
if (type.isVector() || type.isMatrix())
{
mOut << "metal::";
}
if (etConfig.evdConfig && etConfig.evdConfig->isPacked)
{
mOut << "packed_";
}
emitBareTypeName(type, etConfig);
if (type.isVector())
{
mOut << static_cast<uint32_t>(type.getNominalSize());
}
else if (type.isMatrix())
{
mOut << static_cast<uint32_t>(type.getCols()) << "x"
<< static_cast<uint32_t>(type.getRows());
}
if (type.isInterpolant())
{
mOut << ", metal::interpolation::";
switch (type.getQualifier())
{
case EvqNoPerspectiveIn:
case EvqNoPerspectiveCentroidIn:
case EvqNoPerspectiveSampleIn:
mOut << "no_";
break;
default:
break;
}
mOut << "perspective>";
}
if (!isUBO)
{
if (type.isArray())
{
for (auto size : type.getArraySizes())
{
mOut << ", " << size;
}
mOut << ">";
}
}
if (etConfig.evdConfig)
{
const auto &evdConfig = *etConfig.evdConfig;
if (evdConfig.isPointer)
{
mOut << " *";
}
else if (evdConfig.isReference)
{
mOut << " &";
}
if (isUBO)
{
if (type.isArray())
{
for (auto size : type.getArraySizes())
{
mOut << ", " << size;
}
mOut << ">";
}
}
}
}
void GenMetalTraverser::emitFieldDeclaration(const TField &field,
const TStructure &parent,
FieldAnnotationIndices &annotationIndices)
{
const TType &type = *field.type();
const TBasicType basic = type.getBasicType();
EmitVariableDeclarationConfig evdConfig;
evdConfig.emitPostQualifier = true;
evdConfig.disableStructSpecifier = true;
evdConfig.isPacked = mSymbolEnv.isPacked(field);
evdConfig.isUBO = mSymbolEnv.isUBO(field);
evdConfig.isPointer = mSymbolEnv.isPointer(field);
evdConfig.isReference = mSymbolEnv.isReference(field);
emitVariableDeclaration(VarDecl(field), evdConfig);
const TQualifier qual = type.getQualifier();
switch (qual)
{
case TQualifier::EvqFlatIn:
if (mPipelineStructs.fragmentIn.external == &parent)
{
mOut << " [[flat]]";
TranslatorMetalReflection *reflection =
mtl::getTranslatorMetalReflection(&mCompiler);
reflection->hasFlatInput = true;
}
break;
case TQualifier::EvqNoPerspectiveIn:
if (mPipelineStructs.fragmentIn.external == &parent && !type.isInterpolant())
{
mOut << " [[center_no_perspective]]";
}
break;
case TQualifier::EvqCentroidIn:
if (mPipelineStructs.fragmentIn.external == &parent && !type.isInterpolant())
{
mOut << " [[centroid_perspective]]";
}
break;
case TQualifier::EvqSampleIn:
if (mPipelineStructs.fragmentIn.external == &parent && !type.isInterpolant())
{
mOut << " [[sample_perspective]]";
}
break;
case TQualifier::EvqNoPerspectiveCentroidIn:
if (mPipelineStructs.fragmentIn.external == &parent && !type.isInterpolant())
{
mOut << " [[centroid_no_perspective]]";
}
break;
case TQualifier::EvqNoPerspectiveSampleIn:
if (mPipelineStructs.fragmentIn.external == &parent && !type.isInterpolant())
{
mOut << " [[sample_no_perspective]]";
}
break;
case TQualifier::EvqFragColor:
mOut << " [[color(0)]]";
break;
case TQualifier::EvqSecondaryFragColorEXT:
case TQualifier::EvqSecondaryFragDataEXT:
mOut << " [[color(0), index(1)]]";
break;
case TQualifier::EvqFragmentOut:
case TQualifier::EvqFragmentInOut:
case TQualifier::EvqFragData:
if (mPipelineStructs.fragmentOut.external == &parent ||
mPipelineStructs.fragmentOut.externalExtra == &parent)
{
if ((type.isVector() &&
(basic == TBasicType::EbtInt || basic == TBasicType::EbtUInt ||
basic == TBasicType::EbtFloat)) ||
qual == EvqFragData)
{
// The OpenGL ES 3.0 spec says locations must be specified
// unless there is only a single output, in which case the
// location is 0. So, when we get to this point the shader
// will have been rejected if locations are not specified
// and there is more than one output.
const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
if (layoutQualifier.locationsSpecified)
{
mOut << " [[color(" << layoutQualifier.location << ")";
ASSERT(layoutQualifier.index >= -1 && layoutQualifier.index <= 1);
if (layoutQualifier.index == 1)
{
mOut << ", index(1)";
}
}
else if (qual == EvqFragData)
{
mOut << " [[color(" << annotationIndices.color++ << ")";
}
else
{
// Either the only output or EXT_blend_func_extended is used;
// actual assignment will happen in UpdateFragmentShaderOutputs.
mOut << " [[" << sh::kUnassignedFragmentOutputString;
}
if (mRasterOrderGroupsSupported && qual == TQualifier::EvqFragmentInOut)
{
// Put fragment inouts in their own raster order group for better
// parallelism.
// NOTE: this is not required for the reads to be ordered and coherent.
// TODO(anglebug.com/40096838): Consider making raster order groups a PLS
// layout qualifier?
mOut << ", raster_order_group(0)";
}
mOut << "]]";
}
}
break;
case TQualifier::EvqFragDepth:
mOut << " [[depth(";
switch (type.getLayoutQualifier().depth)
{
case EdGreater:
mOut << "greater";
break;
case EdLess:
mOut << "less";
break;
default:
mOut << "any";
break;
}
mOut << "), function_constant(" << sh::mtl::kDepthWriteEnabledConstName << ")]]";
break;
case TQualifier::EvqSampleMask:
if (field.symbolType() == SymbolType::AngleInternal)
{
mOut << " [[sample_mask, function_constant("
<< sh::mtl::kSampleMaskWriteEnabledConstName << ")]]";
}
break;
default:
break;
}
if (IsImage(type.getBasicType()))
{
if (type.getArraySizeProduct() != 1)
{
UNIMPLEMENTED();
}
mOut << " [[";
if (type.getLayoutQualifier().rasterOrdered)
{
mOut << "raster_order_group(0), ";
}
mOut << "texture(" << mMainTextureIndex << ")]]";
TranslatorMetalReflection *reflection = mtl::getTranslatorMetalReflection(&mCompiler);
reflection->addRWTextureBinding(type.getLayoutQualifier().binding,
static_cast<int>(mMainTextureIndex));
++mMainTextureIndex;
}
}
static std::map<Name, size_t> BuildExternalAttributeIndexMap(
const TCompiler &compiler,
const PipelineScoped<TStructure> &structure)
{
ASSERT(structure.isTotallyFull());
const auto &shaderVars = compiler.getAttributes();
const size_t shaderVarSize = shaderVars.size();
size_t shaderVarIndex = 0;
const auto &externalFields = structure.external->fields();
const size_t externalSize = externalFields.size();
size_t externalIndex = 0;
const auto &internalFields = structure.internal->fields();
const size_t internalSize = internalFields.size();
size_t internalIndex = 0;
// Internal fields are never split. External fields are sometimes split.
ASSERT(externalSize >= internalSize);
// Structures do not contain any inactive fields.
ASSERT(shaderVarSize >= internalSize);
std::map<Name, size_t> externalNameToAttributeIndex;
size_t attributeIndex = 0;
while (internalIndex < internalSize)
{
const TField &internalField = *internalFields[internalIndex];
const Name internalName = Name(internalField);
const TType &internalType = *internalField.type();
while (internalName.rawName() != shaderVars[shaderVarIndex].name &&
internalName.rawName() != shaderVars[shaderVarIndex].mappedName)
{
// This case represents an inactive field.
++shaderVarIndex;
ASSERT(shaderVarIndex < shaderVarSize);
++attributeIndex; // TODO: Might need to increment more if shader var type is a matrix.
}
const size_t cols =
(internalType.isMatrix() && !externalFields[externalIndex]->type()->isMatrix())
? internalType.getCols()
: 1;
for (size_t c = 0; c < cols; ++c)
{
const TField &externalField = *externalFields[externalIndex];
const Name externalName = Name(externalField);
externalNameToAttributeIndex[externalName] = attributeIndex;
++externalIndex;
++attributeIndex;
}
++shaderVarIndex;
++internalIndex;
}
ASSERT(shaderVarIndex <= shaderVarSize);
ASSERT(externalIndex <= externalSize); // less than if padding was introduced
ASSERT(internalIndex == internalSize);
return externalNameToAttributeIndex;
}
void GenMetalTraverser::emitAttributeDeclaration(const TField &field,
FieldAnnotationIndices &annotationIndices)
{
EmitVariableDeclarationConfig evdConfig;
evdConfig.disableStructSpecifier = true;
emitVariableDeclaration(VarDecl(field), evdConfig);
mOut << sh::kUnassignedAttributeString;
}
void GenMetalTraverser::emitUniformBufferDeclaration(const TField &field,
FieldAnnotationIndices &annotationIndices)
{
EmitVariableDeclarationConfig evdConfig;
evdConfig.disableStructSpecifier = true;
evdConfig.isUBO = mSymbolEnv.isUBO(field);
evdConfig.isPointer = mSymbolEnv.isPointer(field);
emitVariableDeclaration(VarDecl(field), evdConfig);
mOut << "[[id(" << annotationIndices.attribute << ")]]";
const TType &type = *field.type();
const int arraySize = type.isArray() ? type.getArraySizeProduct() : 1;
TranslatorMetalReflection *reflection = mtl::getTranslatorMetalReflection(&mCompiler);
ASSERT(type.getBasicType() == TBasicType::EbtStruct);
const TStructure *structure = type.getStruct();
const std::string originalName = reflection->getOriginalName(structure->uniqueId().get());
reflection->addUniformBufferBinding(
originalName,
{.bindIndex = annotationIndices.attribute, .arraySize = static_cast<size_t>(arraySize)});
annotationIndices.attribute += arraySize;
}
void GenMetalTraverser::emitStructDeclaration(const TType &type)
{
ASSERT(type.getBasicType() == TBasicType::EbtStruct);
ASSERT(type.isStructSpecifier());
mOut << "struct ";
emitBareTypeName(type, {});
mOut << "\n";
emitOpenBrace();
const TStructure &structure = *type.getStruct();
std::map<Name, size_t> fieldToAttributeIndex;
const bool hasAttributeIndices = mPipelineStructs.vertexIn.external == &structure;
const bool hasUniformBufferIndicies = mPipelineStructs.uniformBuffers.external == &structure;
const bool reclaimUnusedAttributeIndices = mCompiler.getShaderVersion() < 300;
if (hasAttributeIndices)
{
// When attribute aliasing is supported, external attribute struct is filled post-link.
if (mCompiler.supportsAttributeAliasing())
{
mtl::getTranslatorMetalReflection(&mCompiler)->hasAttributeAliasing = true;
mOut << "@@Attrib-Bindings@@\n";
emitCloseBrace();
return;
}
fieldToAttributeIndex =
BuildExternalAttributeIndexMap(mCompiler, mPipelineStructs.vertexIn);
}
FieldAnnotationIndices annotationIndices;
for (const TField *field : structure.fields())
{
emitIndentation();
if (hasAttributeIndices)
{
const auto it = fieldToAttributeIndex.find(Name(*field));
if (it == fieldToAttributeIndex.end())
{
ASSERT(field->symbolType() == SymbolType::AngleInternal);
ASSERT(field->name().beginsWith("_"));
ASSERT(angle::EndsWith(field->name().data(), "_pad"));
emitFieldDeclaration(*field, structure, annotationIndices);
}
else
{
ASSERT(field->symbolType() != SymbolType::AngleInternal ||
!field->name().beginsWith("_") ||
!angle::EndsWith(field->name().data(), "_pad"));
if (!reclaimUnusedAttributeIndices)
{
annotationIndices.attribute = it->second;
}
emitAttributeDeclaration(*field, annotationIndices);
}
}
else if (hasUniformBufferIndicies)
{
emitUniformBufferDeclaration(*field, annotationIndices);
}
else
{
emitFieldDeclaration(*field, structure, annotationIndices);
}
mOut << ";\n";
}
emitCloseBrace();
}
void GenMetalTraverser::emitOrdinaryVariableDeclaration(
const VarDecl &decl,
const EmitVariableDeclarationConfig &evdConfig)
{
EmitTypeConfig etConfig;
etConfig.evdConfig = &evdConfig;
const TType &type = decl.type();
if (type.getQualifier() == TQualifier::EvqClipDistance)
{
// Clip distance output uses float[n] type instead of metal::array.
// The element count is emitted after the post qualifier.
ASSERT(type.getBasicType() == TBasicType::EbtFloat);
mOut << "float";
}
else if (type.getQualifier() == TQualifier::EvqSampleID && evdConfig.isMainParameter)
{
// Metal's [[sample_id]] must be unsigned
ASSERT(type.getBasicType() == TBasicType::EbtInt);
mOut << "uint32_t";
}
else
{
emitType(type, etConfig);
}
if (decl.symbolType() != SymbolType::Empty)
{
mOut << " ";
emitNameOf(decl);
}
}
void GenMetalTraverser::emitVariableDeclaration(const VarDecl &decl,
const EmitVariableDeclarationConfig &evdConfig)
{
const SymbolType symbolType = decl.symbolType();
const TType &type = decl.type();
const TBasicType basicType = type.getBasicType();
switch (basicType)
{
case TBasicType::EbtStruct:
{
if (type.isStructSpecifier() && !evdConfig.disableStructSpecifier)
{
// It's invalid to declare a struct inside a function argument. When emitting a
// function parameter, the callsite should set evdConfig.disableStructSpecifier.
ASSERT(!evdConfig.isParameter);
emitStructDeclaration(type);
if (symbolType != SymbolType::Empty)
{
mOut << " ";
emitNameOf(decl);
}
}
else
{
emitOrdinaryVariableDeclaration(decl, evdConfig);
}
}
break;
default:
{
ASSERT(symbolType != SymbolType::Empty || evdConfig.isParameter);
emitOrdinaryVariableDeclaration(decl, evdConfig);
}
}
if (evdConfig.emitPostQualifier)
{
emitPostQualifier(evdConfig, decl, type.getQualifier());
}
}
void GenMetalTraverser::visitSymbol(TIntermSymbol *symbolNode)
{
const TVariable &var = symbolNode->variable();
const TType &type = var.getType();
ASSERT(var.symbolType() != SymbolType::Empty);
if (type.getBasicType() == TBasicType::EbtVoid)
{
mOut << "/*";
emitNameOf(var);
mOut << "*/";
}
else
{
emitNameOf(var);
}
}
void GenMetalTraverser::emitSingleConstant(const TConstantUnion *const constUnion)
{
switch (constUnion->getType())
{
case TBasicType::EbtBool:
{
mOut << (constUnion->getBConst() ? "true" : "false");
}
break;
case TBasicType::EbtFloat:
{
float value = constUnion->getFConst();
if (std::isnan(value))
{
mOut << "NAN";
}
else if (std::isinf(value))
{
if (value < 0)
{
mOut << "-";
}
mOut << "INFINITY";
}
else
{
mOut << value << "f";
}
}
break;
case TBasicType::EbtInt:
{
mOut << constUnion->getIConst();
}
break;
case TBasicType::EbtUInt:
{
mOut << constUnion->getUConst() << "u";
}
break;
default:
{
UNIMPLEMENTED();
}
}
}
const TConstantUnion *GenMetalTraverser::emitConstantUnionArray(
const TConstantUnion *const constUnion,
const size_t size)
{
const TConstantUnion *constUnionIterated = constUnion;
for (size_t i = 0; i < size; i++, constUnionIterated++)
{
emitSingleConstant(constUnionIterated);
if (i != size - 1)
{
mOut << ", ";
}
}
return constUnionIterated;
}
const TConstantUnion *GenMetalTraverser::emitConstantUnion(const TType &type,
const TConstantUnion *constUnionBegin)
{
const TConstantUnion *constUnionCurr = constUnionBegin;
const TStructure *structure = type.getStruct();
if (structure)
{
EmitTypeConfig config = EmitTypeConfig{nullptr};
emitType(type, config);
mOut << "{";
const TFieldList &fields = structure->fields();
for (size_t i = 0; i < fields.size(); ++i)
{
const TType *fieldType = fields[i]->type();
constUnionCurr = emitConstantUnion(*fieldType, constUnionCurr);
if (i != fields.size() - 1)
{
mOut << ", ";
}
}
mOut << "}";
}
else
{
size_t size = type.getObjectSize();
bool writeType = size > 1;
if (writeType)
{
EmitTypeConfig config = EmitTypeConfig{nullptr};
emitType(type, config);
mOut << "(";
}
constUnionCurr = emitConstantUnionArray(constUnionCurr, size);
if (writeType)
{
mOut << ")";
}
}
return constUnionCurr;
}
void GenMetalTraverser::visitConstantUnion(TIntermConstantUnion *constValueNode)
{
emitConstantUnion(constValueNode->getType(), constValueNode->getConstantValue());
}
bool GenMetalTraverser::visitSwizzle(Visit, TIntermSwizzle *swizzleNode)
{
groupedTraverse(*swizzleNode->getOperand());
mOut << ".";
{
#if defined(ANGLE_ENABLE_ASSERTS)
DebugSink::EscapedSink escapedOut(mOut.escape());
TInfoSinkBase &out = escapedOut.get();
#else
TInfoSinkBase &out = mOut;
#endif
swizzleNode->writeOffsetsAsXYZW(&out);
}
return false;
}
const TField &GenMetalTraverser::getDirectField(const TFieldListCollection &fieldListCollection,
const TConstantUnion &index)
{
ASSERT(index.getType() == TBasicType::EbtInt);
const TFieldList &fieldList = fieldListCollection.fields();
const int indexVal = index.getIConst();
const TField &field = *fieldList[indexVal];
return field;
}
const TField &GenMetalTraverser::getDirectField(const TIntermTyped &fieldsNode,
TIntermTyped &indexNode)
{
const TType &fieldsType = fieldsNode.getType();
const TFieldListCollection *fieldListCollection = fieldsType.getStruct();
if (fieldListCollection == nullptr)
{
fieldListCollection = fieldsType.getInterfaceBlock();
}
ASSERT(fieldListCollection);
const TIntermConstantUnion *indexNode_ = indexNode.getAsConstantUnion();
ASSERT(indexNode_);
const TConstantUnion &index = *indexNode_->getConstantValue();
return getDirectField(*fieldListCollection, index);
}
bool GenMetalTraverser::visitBinary(Visit, TIntermBinary *binaryNode)
{
const TOperator op = binaryNode->getOp();
TIntermTyped &leftNode = *binaryNode->getLeft();
TIntermTyped &rightNode = *binaryNode->getRight();
switch (op)
{
case TOperator::EOpIndexDirectStruct:
case TOperator::EOpIndexDirectInterfaceBlock:
{
const TField &field = getDirectField(leftNode, rightNode);
if (mSymbolEnv.isPointer(field) && mSymbolEnv.isUBO(field))
{
emitOpeningPointerParen();
}
groupedTraverse(leftNode);
if (!mSymbolEnv.isPointer(field))
{
emitClosingPointerParen();
}
mOut << ".";
emitNameOf(field);
}
break;
case TOperator::EOpIndexDirect:
case TOperator::EOpIndexIndirect:
{
TType leftType = leftNode.getType();
groupedTraverse(leftNode);
mOut << "[";
const TConstantUnion *constIndex = rightNode.getConstantValue();
// TODO(anglebug.com/42266914): Convert type and bound checks to
// assertions after AST validation is enabled for MSL translation.
if (!leftType.isUnsizedArray() && constIndex != nullptr &&
constIndex->getType() == EbtInt && constIndex->getIConst() >= 0 &&
constIndex->getIConst() < static_cast<int>(leftType.isArray()
? leftType.getOutermostArraySize()
: leftType.getNominalSize()))
{
emitSingleConstant(constIndex);
}
else
{
mOut << "ANGLE_int_clamp(";
groupedTraverse(rightNode);
mOut << ", 0, ";
if (leftType.isUnsizedArray())
{
groupedTraverse(leftNode);
mOut << ".size()";
}
else
{
uint32_t maxSize;
if (leftType.isArray())
{
maxSize = leftType.getOutermostArraySize() - 1;
}
else
{
maxSize = leftType.getNominalSize() - 1;
}
mOut << maxSize;
}
mOut << ")";
}
mOut << "]";
}
break;
default:
{
const TType &resultType = binaryNode->getType();
const TType &leftType = leftNode.getType();
const TType &rightType = rightNode.getType();
if (IsSymbolicOperator(op, resultType, &leftType, &rightType))
{
groupedTraverse(leftNode);
if (op != TOperator::EOpComma)
{
mOut << " ";
}
else
{
emitClosingPointerParen();
}
mOut << GetOperatorString(op, resultType, &leftType, &rightType, nullptr) << " ";
groupedTraverse(rightNode);
}
else
{
emitClosingPointerParen();
mOut << GetOperatorString(op, resultType, &leftType, &rightType, nullptr) << "(";
leftNode.traverse(this);
mOut << ", ";
rightNode.traverse(this);
mOut << ")";
}
}
}
return false;
}
static bool IsPostfix(TOperator op)
{
switch (op)
{
case TOperator::EOpPostIncrement:
case TOperator::EOpPostDecrement:
return true;
default:
return false;
}
}
bool GenMetalTraverser::visitUnary(Visit, TIntermUnary *unaryNode)
{
const TOperator op = unaryNode->getOp();
const TType &resultType = unaryNode->getType();
TIntermTyped &arg = *unaryNode->getOperand();
const TType &argType = arg.getType();
if (op == TOperator::EOpIsnan || op == TOperator::EOpIsinf)
{
mtl::getTranslatorMetalReflection(&mCompiler)->hasIsnanOrIsinf = true;
}
const char *name = GetOperatorString(op, resultType, &argType, nullptr, nullptr);
if (IsSymbolicOperator(op, resultType, &argType, nullptr))
{
const bool postfix = IsPostfix(op);
if (!postfix)
{
mOut << name;
}
groupedTraverse(arg);
if (postfix)
{
mOut << name;
}
}
else
{
mOut << name << "(";
arg.traverse(this);
mOut << ")";
}
return false;
}
bool GenMetalTraverser::visitTernary(Visit, TIntermTernary *conditionalNode)
{
groupedTraverse(*conditionalNode->getCondition());
mOut << " ? ";
groupedTraverse(*conditionalNode->getTrueExpression());
mOut << " : ";
groupedTraverse(*conditionalNode->getFalseExpression());
return false;
}
bool GenMetalTraverser::visitIfElse(Visit, TIntermIfElse *ifThenElseNode)
{
TIntermTyped &condNode = *ifThenElseNode->getCondition();
TIntermBlock *thenNode = ifThenElseNode->getTrueBlock();
TIntermBlock *elseNode = ifThenElseNode->getFalseBlock();
emitIndentation();
mOut << "if (";
condNode.traverse(this);
mOut << ")";
if (thenNode)
{
mOut << "\n";
thenNode->traverse(this);
}
else
{
mOut << " {}";
}
if (elseNode)
{
mOut << "\n";
emitIndentation();
mOut << "else\n";
elseNode->traverse(this);
}
else
{
// Always emit "else" even when empty block to avoid nested if-stmt issues.
mOut << " else {}";
}
return false;
}
bool GenMetalTraverser::visitSwitch(Visit, TIntermSwitch *switchNode)
{
emitIndentation();
mOut << "switch (";
switchNode->getInit()->traverse(this);
mOut << ")\n";
ASSERT(!mParentIsSwitch);
mParentIsSwitch = true;
switchNode->getStatementList()->traverse(this);
mParentIsSwitch = false;
return false;
}
bool GenMetalTraverser::visitCase(Visit, TIntermCase *caseNode)
{
emitIndentation();
if (caseNode->hasCondition())
{
TIntermTyped *condExpr = caseNode->getCondition();
mOut << "case ";
condExpr->traverse(this);
mOut << ":";
}
else
{
mOut << "default:\n";
}
return false;
}
void GenMetalTraverser::emitFunctionSignature(const TFunction &func)
{
const bool isMain = func.isMain();
emitFunctionReturn(func);
mOut << " ";
emitNameOf(func);
if (isMain)
{
mOut << "0";
}
mOut << "(";
bool emitComma = false;
const size_t paramCount = func.getParamCount();
for (size_t i = 0; i < paramCount; ++i)
{
if (emitComma)
{
mOut << ", ";
}
emitComma = true;
const TVariable &param = *func.getParam(i);
emitFunctionParameter(func, param);
}
if (isTraversingVertexMain)
{
mOut << " @@XFB-Bindings@@ ";
}
mOut << ")";
}
void GenMetalTraverser::emitFunctionReturn(const TFunction &func)
{
const bool isMain = func.isMain();
bool isVertexMain = false;
const TType &returnType = func.getReturnType();
if (isMain)
{
const TStructure *structure = returnType.getStruct();
if (mPipelineStructs.fragmentOut.matches(*structure))
{
if (mCompiler.isEarlyFragmentTestsSpecified())
{
mOut << "[[early_fragment_tests]]\n";
}
mOut << "fragment ";
}
else if (mPipelineStructs.vertexOut.matches(*structure))
{
ASSERT(structure != nullptr);
mOut << "vertex __VERTEX_OUT(";
isVertexMain = true;
}
else
{
UNIMPLEMENTED();
}
}
emitType(returnType, EmitTypeConfig());
if (isVertexMain)
mOut << ") ";
}
void GenMetalTraverser::emitFunctionParameter(const TFunction &func, const TVariable &param)
{
const bool isMain = func.isMain();
const TType &type = param.getType();
const TStructure *structure = type.getStruct();
EmitVariableDeclarationConfig evdConfig;
evdConfig.isParameter = true;
evdConfig.disableStructSpecifier = true; // It's invalid to declare a struct in a function arg.
evdConfig.isMainParameter = isMain;
evdConfig.emitPostQualifier = isMain;
evdConfig.isUBO = mSymbolEnv.isUBO(param);
evdConfig.isPointer = mSymbolEnv.isPointer(param);
evdConfig.isReference = mSymbolEnv.isReference(param);
emitVariableDeclaration(VarDecl(param), evdConfig);
if (isMain)
{
TranslatorMetalReflection *reflection = mtl::getTranslatorMetalReflection(&mCompiler);
if (structure)
{
if (mPipelineStructs.fragmentIn.matches(*structure) ||
mPipelineStructs.vertexIn.matches(*structure))
{
mOut << " [[stage_in]]";
}
else if (mPipelineStructs.angleUniforms.matches(*structure))
{
mOut << " [[buffer(" << mDriverUniformsBindingIndex << ")]]";
}
else if (mPipelineStructs.uniformBuffers.matches(*structure))
{
mOut << " [[buffer(" << mUBOArgumentBufferBindingIndex << ")]]";
reflection->hasUBOs = true;
}
else if (mPipelineStructs.userUniforms.matches(*structure))
{
mOut << " [[buffer(" << mMainUniformBufferIndex << ")]]";
reflection->addUserUniformBufferBinding(param.name().data(),
mMainUniformBufferIndex);
mMainUniformBufferIndex += type.getArraySizeProduct();
}
else if (structure->name() == "metal::sampler")
{
mOut << " [[sampler(" << (mMainSamplerIndex) << ")]]";
const std::string originalName =
reflection->getOriginalName(param.uniqueId().get());
reflection->addSamplerBinding(originalName, mMainSamplerIndex);
mMainSamplerIndex += type.getArraySizeProduct();
}
}
else if (IsSampler(type.getBasicType()))
{
mOut << " [[texture(" << (mMainTextureIndex) << ")]]";
const std::string originalName = reflection->getOriginalName(param.uniqueId().get());
reflection->addTextureBinding(originalName, mMainTextureIndex);
mMainTextureIndex += type.getArraySizeProduct();
}
else if (Name(param) == Pipeline{Pipeline::Type::InstanceId, nullptr}.getStructInstanceName(
Pipeline::Variant::Modified))
{
mOut << " [[instance_id]]";
}
else if (Name(param) == kBaseInstanceName)
{
mOut << " [[base_instance]]";
}
}
}
void GenMetalTraverser::visitFunctionPrototype(TIntermFunctionPrototype *funcProtoNode)
{
const TFunction &func = *funcProtoNode->getFunction();
emitIndentation();
emitFunctionSignature(func);
}
bool GenMetalTraverser::visitFunctionDefinition(Visit, TIntermFunctionDefinition *funcDefNode)
{
const TFunction &func = *funcDefNode->getFunction();
TIntermBlock &body = *funcDefNode->getBody();
if (func.isMain())
{
const TType &returnType = func.getReturnType();
const TStructure *structure = returnType.getStruct();
isTraversingVertexMain = (mPipelineStructs.vertexOut.matches(*structure)) &&
mCompiler.getShaderType() == GL_VERTEX_SHADER;
}
emitIndentation();
emitFunctionSignature(func);
mOut << "\n";
body.traverse(this);
if (isTraversingVertexMain)
{
isTraversingVertexMain = false;
}
return false;
}
GenMetalTraverser::FuncToName GenMetalTraverser::BuildFuncToName()
{
FuncToName map;
auto putAngle = [&](const char *nameStr) {
const ImmutableString name(nameStr);
ASSERT(map.find(name) == map.end());
map[name] = Name(nameStr, SymbolType::AngleInternal);
};
putAngle("texelFetch");
putAngle("texelFetchOffset");
putAngle("texture");
putAngle("texture2D");
putAngle("texture2DGradEXT");
putAngle("texture2DLod");
putAngle("texture2DLodEXT");
putAngle("texture2DProj");
putAngle("texture2DProjGradEXT");
putAngle("texture2DProjLod");
putAngle("texture2DProjLodEXT");
putAngle("texture3D");
putAngle("texture3DLod");
putAngle("texture3DProj");
putAngle("texture3DProjLod");
putAngle("textureCube");
putAngle("textureCubeGradEXT");
putAngle("textureCubeLod");
putAngle("textureCubeLodEXT");
putAngle("textureGrad");
putAngle("textureGradOffset");
putAngle("textureLod");
putAngle("textureLodOffset");
putAngle("textureOffset");
putAngle("textureProj");
putAngle("textureProjGrad");
putAngle("textureProjGradOffset");
putAngle("textureProjLod");
putAngle("textureProjLodOffset");
putAngle("textureProjOffset");
putAngle("textureSize");
putAngle("imageLoad");
putAngle("imageStore");
putAngle("memoryBarrierImage");
return map;
}
bool GenMetalTraverser::visitAggregate(Visit, TIntermAggregate *aggregateNode)
{
const TIntermSequence &args = *aggregateNode->getSequence();
auto emitArgList = [&](const char *open, const char *close) {
mOut << open;
bool emitComma = false;
for (TIntermNode *arg : args)
{
if (emitComma)
{
emitClosingPointerParen();
mOut << ", ";
}
emitComma = true;
arg->traverse(this);
}
mOut << close;
};
const TType &retType = aggregateNode->getType();
if (aggregateNode->isConstructor())
{
const bool isStandalone = getParentNode()->getAsBlock();
if (isStandalone)
{
// Prevent constructor from being interpreted as a declaration by wrapping in parens.
// This can happen if given something like:
// int(symbol); // <- This will be treated like `int symbol;`... don't want that.
// So instead emit:
// (int(symbol));
mOut << "(";
}
const EmitTypeConfig etConfig;
if (retType.isArray())
{
emitType(retType, etConfig);
emitArgList("{", "}");
}
else if (retType.getStruct())
{
emitType(retType, etConfig);
emitArgList("{", "}");
}
else
{
emitType(retType, etConfig);
emitArgList("(", ")");
}
if (isStandalone)
{
mOut << ")";
}
return false;
}
else
{
const TOperator op = aggregateNode->getOp();
switch (op)
{
case TOperator::EOpCallFunctionInAST:
case TOperator::EOpCallInternalRawFunction:
{
const TFunction &func = *aggregateNode->getFunction();
emitNameOf(func);
//'@' symbol in name specifices a macro substitution marker.
if (!func.name().contains("@"))
{
emitArgList("(", ")");
}
else
{
mTemporarilyDisableSemicolon =
true; // Disable semicolon for macro substitution.
}
return false;
}
default:
{
auto getArgType = [&](size_t index) -> const TType * {
if (index < args.size())
{
TIntermTyped *arg = args[index]->getAsTyped();
ASSERT(arg);
return &arg->getType();
}
return nullptr;
};
const TType *argType0 = getArgType(0);
const TType *argType1 = getArgType(1);
const TType *argType2 = getArgType(2);
const char *opName = GetOperatorString(op, retType, argType0, argType1, argType2);
if (IsSymbolicOperator(op, retType, argType0, argType1))
{
switch (args.size())
{
case 1:
{
TIntermNode &operandNode = *aggregateNode->getChildNode(0);
if (IsPostfix(op))
{
mOut << opName;
groupedTraverse(operandNode);
}
else
{
groupedTraverse(operandNode);
mOut << opName;
}
return false;
}
case 2:
{
TIntermNode &leftNode = *aggregateNode->getChildNode(0);
TIntermNode &rightNode = *aggregateNode->getChildNode(1);
groupedTraverse(leftNode);
mOut << " " << opName << " ";
groupedTraverse(rightNode);
return false;
}
default:
UNREACHABLE();
return false;
}
}
else if (opName == nullptr)
{
const TFunction &func = *aggregateNode->getFunction();
auto it = mFuncToName.find(func.name());
ASSERT(it != mFuncToName.end());
EmitName(mOut, it->second);
emitArgList("(", ")");
return false;
}
else
{
mOut << opName;
emitArgList("(", ")");
return false;
}
}
}
}
}
void GenMetalTraverser::emitOpenBrace()
{
ASSERT(mIndentLevel >= 0);
emitIndentation();
mOut << "{\n";
++mIndentLevel;
}
void GenMetalTraverser::emitCloseBrace()
{
ASSERT(mIndentLevel >= 1);
--mIndentLevel;
emitIndentation();
mOut << "}";
}
static bool RequiresSemicolonTerminator(TIntermNode &node)
{
if (node.getAsBlock())
{
return false;
}
if (node.getAsLoopNode())
{
return false;
}
if (node.getAsSwitchNode())
{
return false;
}
if (node.getAsIfElseNode())
{
return false;
}
if (node.getAsFunctionDefinition())
{
return false;
}
if (node.getAsCaseNode())
{
return false;
}
return true;
}
static bool NewlinePad(TIntermNode &node)
{
if (node.getAsFunctionDefinition())
{
return true;
}
if (TIntermDeclaration *declNode = node.getAsDeclarationNode())
{
ASSERT(declNode->getChildCount() == 1);
TIntermNode &childNode = *declNode->getChildNode(0);
if (TIntermSymbol *symbolNode = childNode.getAsSymbolNode())
{
const TVariable &var = symbolNode->variable();
return var.getType().isStructSpecifier();
}
return false;
}
return false;
}
bool GenMetalTraverser::visitBlock(Visit, TIntermBlock *blockNode)
{
ASSERT(mIndentLevel >= -1);
const bool isGlobalScope = mIndentLevel == -1;
const bool parentIsSwitch = mParentIsSwitch;
mParentIsSwitch = false;
if (isGlobalScope)
{
++mIndentLevel;
}
else
{
emitOpenBrace();
if (parentIsSwitch)
{
++mIndentLevel;
}
}
TIntermNode *prevStmtNode = nullptr;
const size_t stmtCount = blockNode->getChildCount();
for (size_t i = 0; i < stmtCount; ++i)
{
TIntermNode &stmtNode = *blockNode->getChildNode(i);
if (isGlobalScope && prevStmtNode && (NewlinePad(*prevStmtNode) || NewlinePad(stmtNode)))
{
mOut << "\n";
}
const bool isCase = stmtNode.getAsCaseNode();
mIndentLevel -= isCase;
emitIndentation();
mIndentLevel += isCase;
stmtNode.traverse(this);
if (RequiresSemicolonTerminator(stmtNode) && !mTemporarilyDisableSemicolon)
{
mOut << ";";
}
mTemporarilyDisableSemicolon = false;
mOut << "\n";
prevStmtNode = &stmtNode;
}
if (isGlobalScope)
{
ASSERT(mIndentLevel == 0);
--mIndentLevel;
}
else
{
if (parentIsSwitch)
{
ASSERT(mIndentLevel >= 1);
--mIndentLevel;
}
emitCloseBrace();
mParentIsSwitch = parentIsSwitch;
}
return false;
}
bool GenMetalTraverser::visitGlobalQualifierDeclaration(Visit, TIntermGlobalQualifierDeclaration *)
{
return false;
}
bool GenMetalTraverser::visitDeclaration(Visit, TIntermDeclaration *declNode)
{
ASSERT(declNode->getChildCount() == 1);
TIntermNode &node = *declNode->getChildNode(0);
EmitVariableDeclarationConfig evdConfig;
if (TIntermSymbol *symbolNode = node.getAsSymbolNode())
{
const TVariable &var = symbolNode->variable();
emitVariableDeclaration(VarDecl(var), evdConfig);
if (var.getType().isArray() && var.getType().getQualifier() == EvqTemporary)
{
// The translator frontend injects a loop-based init for user arrays when the source
// shader is using ESSL 1.00. Some Metal drivers may fail to access elements of such
// arrays at runtime depending on the array size. An empty literal initializer added
// to the generated MSL bypasses the issue. The frontend may be further optimized to
// skip the loop-based init when targeting MSL.
mOut << "{}";
}
}
else if (TIntermBinary *initNode = node.getAsBinaryNode())
{
ASSERT(initNode->getOp() == TOperator::EOpInitialize);
TIntermSymbol *leftSymbolNode = initNode->getLeft()->getAsSymbolNode();
TIntermTyped *valueNode = initNode->getRight()->getAsTyped();
ASSERT(leftSymbolNode && valueNode);
if (getRootNode() == getParentBlock())
{
// DeferGlobalInitializers should have turned non-const global initializers into
// deferred initializers. Note that variables marked as EvqGlobal can be treated as
// EvqConst in some ANGLE code but not actually have their qualifier actually changed to
// EvqConst. Thus just assume all EvqGlobal are actually EvqConst for all code run after
// DeferGlobalInitializers.
mOut << "constant ";
}
const TVariable &var = leftSymbolNode->variable();
const Name varName(var);
if (ExpressionContainsName(varName, *valueNode))
{
mRenamedSymbols[&var] = mIdGen.createNewName(varName);
}
emitVariableDeclaration(VarDecl(var), evdConfig);
mOut << " = ";
groupedTraverse(*valueNode);
}
else
{
UNREACHABLE();
}
return false;
}
bool GenMetalTraverser::visitLoop(Visit, TIntermLoop *loopNode)
{
const TLoopType loopType = loopNode->getType();
switch (loopType)
{
case TLoopType::ELoopFor:
return visitForLoop(loopNode);
case TLoopType::ELoopWhile:
return visitWhileLoop(loopNode);
case TLoopType::ELoopDoWhile:
return visitDoWhileLoop(loopNode);
}
}
bool GenMetalTraverser::visitForLoop(TIntermLoop *loopNode)
{
ASSERT(loopNode->getType() == TLoopType::ELoopFor);
TIntermNode *initNode = loopNode->getInit();
TIntermTyped *condNode = loopNode->getCondition();
TIntermTyped *exprNode = loopNode->getExpression();
mOut << "for (";
if (initNode)
{
initNode->traverse(this);
}
else
{
mOut << " ";
}
mOut << "; ";
if (condNode)
{
condNode->traverse(this);
}
mOut << "; ";
if (exprNode)
{
exprNode->traverse(this);
}
mOut << ")\n";
emitLoopBody(loopNode->getBody());
return false;
}
bool GenMetalTraverser::visitWhileLoop(TIntermLoop *loopNode)
{
ASSERT(loopNode->getType() == TLoopType::ELoopWhile);
TIntermNode *initNode = loopNode->getInit();
TIntermTyped *condNode = loopNode->getCondition();
TIntermTyped *exprNode = loopNode->getExpression();
ASSERT(condNode);
ASSERT(!initNode && !exprNode);
emitIndentation();
mOut << "while (";
condNode->traverse(this);
mOut << ")\n";
emitLoopBody(loopNode->getBody());
return false;
}
bool GenMetalTraverser::visitDoWhileLoop(TIntermLoop *loopNode)
{
ASSERT(loopNode->getType() == TLoopType::ELoopDoWhile);
TIntermNode *initNode = loopNode->getInit();
TIntermTyped *condNode = loopNode->getCondition();
TIntermTyped *exprNode = loopNode->getExpression();
ASSERT(condNode);
ASSERT(!initNode && !exprNode);
emitIndentation();
mOut << "do\n";
emitLoopBody(loopNode->getBody());
mOut << "\n";
emitIndentation();
mOut << "while (";
condNode->traverse(this);
mOut << ");";
return false;
}
bool GenMetalTraverser::visitBranch(Visit, TIntermBranch *branchNode)
{
const TOperator flowOp = branchNode->getFlowOp();
TIntermTyped *exprNode = branchNode->getExpression();
emitIndentation();
switch (flowOp)
{
case TOperator::EOpKill:
{
ASSERT(exprNode == nullptr);
mOut << "metal::discard_fragment()";
}
break;
case TOperator::EOpReturn:
{
if (isTraversingVertexMain)
{
mOut << "#if TRANSFORM_FEEDBACK_ENABLED\n";
emitIndentation();
mOut << "return;\n";
emitIndentation();
mOut << "#else\n";
emitIndentation();
}
mOut << "return";
if (exprNode)
{
mOut << " ";
exprNode->traverse(this);
mOut << ";";
}
if (isTraversingVertexMain)
{
mOut << "\n";
emitIndentation();
mOut << "#endif\n";
mTemporarilyDisableSemicolon = true;
}
}
break;
case TOperator::EOpBreak:
{
ASSERT(exprNode == nullptr);
mOut << "break";
}
break;
case TOperator::EOpContinue:
{
ASSERT(exprNode == nullptr);
mOut << "continue";
}
break;
default:
{
UNREACHABLE();
}
}
return false;
}
static size_t emitMetalCallCount = 0;
bool sh::EmitMetal(TCompiler &compiler,
TIntermBlock &root,
IdGen &idGen,
const PipelineStructs &pipelineStructs,
SymbolEnv &symbolEnv,
const ProgramPreludeConfig &ppc,
const ShCompileOptions &compileOptions)
{
TInfoSinkBase &out = compiler.getInfoSink().obj;
{
++emitMetalCallCount;
std::string filenameProto = angle::GetEnvironmentVar("GMD_FIXED_EMIT");
if (!filenameProto.empty())
{
if (filenameProto != "/dev/null")
{
auto tryOpen = [&](char const *ext) {
auto filename = filenameProto;
filename += std::to_string(emitMetalCallCount);
filename += ".";
filename += ext;
return fopen(filename.c_str(), "rb");
};
FILE *file = tryOpen("metal");
if (!file)
{
file = tryOpen("cpp");
}
ASSERT(file);
fseek(file, 0, SEEK_END);
size_t fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
std::vector<char> buff;
buff.resize(fileSize + 1);
fread(buff.data(), fileSize, 1, file);
buff.back() = '\0';
fclose(file);
out << buff.data();
}
return true;
}
}
out << "\n\n";
if (!EmitProgramPrelude(root, out, ppc))
{
return false;
}
{
#if defined(ANGLE_ENABLE_ASSERTS)
DebugSink outWrapper(out, angle::GetBoolEnvironmentVar("GMD_STDOUT"));
outWrapper.watch(angle::GetEnvironmentVar("GMD_WATCH_STRING"));
#else
TInfoSinkBase &outWrapper = out;
#endif
GenMetalTraverser gen(compiler, outWrapper, idGen, pipelineStructs, symbolEnv,
compileOptions);
root.traverse(&gen);
}
out << "\n";
return true;
}