diff options
Diffstat (limited to 'gfx/angle/checkout/src/compiler/translator/tree_ops/d3d')
34 files changed, 3328 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp new file mode 100644 index 0000000000..8790c4f5f2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp @@ -0,0 +1,61 @@ +// +// Copyright 2016 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. +// +// AddDefaultReturnStatements.cpp: Add default return statements to functions that do not end in a +// return. +// + +#include "compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +bool NeedsReturnStatement(TIntermFunctionDefinition *node, TType *returnType) +{ + *returnType = node->getFunctionPrototype()->getType(); + if (returnType->getBasicType() == EbtVoid) + { + return false; + } + + TIntermBlock *bodyNode = node->getBody(); + TIntermBranch *returnNode = bodyNode->getSequence()->back()->getAsBranchNode(); + if (returnNode != nullptr && returnNode->getFlowOp() == EOpReturn) + { + return false; + } + + return true; +} + +} // anonymous namespace + +bool AddDefaultReturnStatements(TCompiler *compiler, TIntermBlock *root) +{ + TType returnType; + for (TIntermNode *node : *root->getSequence()) + { + TIntermFunctionDefinition *definition = node->getAsFunctionDefinition(); + if (definition != nullptr && NeedsReturnStatement(definition, &returnType)) + { + TIntermBranch *branch = new TIntermBranch(EOpReturn, CreateZeroNode(returnType)); + + TIntermBlock *bodyNode = definition->getBody(); + bodyNode->getSequence()->push_back(branch); + } + } + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h new file mode 100644 index 0000000000..52b601514c --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h @@ -0,0 +1,24 @@ +// +// Copyright 2016 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. +// +// AddDefaultReturnStatements.h: Add default return statements to functions that do not end in a +// return. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; + +[[nodiscard]] bool AddDefaultReturnStatements(TCompiler *compiler, TIntermBlock *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp new file mode 100644 index 0000000000..d9a0c9bebc --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp @@ -0,0 +1,82 @@ +// +// Copyright 2022 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 "compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h" + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +class AggregateAssignArraysInSSBOsTraverser : public TIntermTraverser +{ + public: + AggregateAssignArraysInSSBOsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable) + {} + + protected: + bool visitBinary(Visit visit, TIntermBinary *node) override + { + // Replace all aggregate assignments to arrays in SSBOs with element-by-element assignments. + // TODO(anglebug.com/7363): this implementation only works for the simple case (assignment + // statement), not more complex cases such as assignment-as-expression or functions with + // side effects in the RHS. + + if (node->getOp() != EOpAssign) + { + return true; + } + else if (!node->getLeft()->getType().isArray()) + { + return true; + } + else if (!IsInShaderStorageBlock(node->getLeft())) + { + return true; + } + const TType *mediumpIndexType = StaticType::Get<EbtInt, EbpMedium, EvqTemporary, 1, 1>(); + auto *indexVariable = CreateTempVariable(mSymbolTable, mediumpIndexType); + auto *indexInit = + CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType())); + auto *arraySizeNode = CreateIndexNode(node->getOutermostArraySize()); + auto *indexSymbolNode = CreateTempSymbolNode(indexVariable); + auto *cond = new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode); + auto *indexIncrement = + new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr); + auto *forLoopBody = new TIntermBlock(); + auto *indexedLeft = + new TIntermBinary(EOpIndexDirect, node->getLeft(), indexSymbolNode->deepCopy()); + auto *indexedRight = + new TIntermBinary(EOpIndexDirect, node->getRight(), indexSymbolNode->deepCopy()); + auto *assign = new TIntermBinary(TOperator::EOpAssign, indexedLeft, indexedRight); + forLoopBody->appendStatement(assign); + auto *forLoop = + new TIntermLoop(ELoopFor, indexInit, cond, indexIncrement, EnsureBlock(forLoopBody)); + queueReplacement(forLoop, OriginalNode::IS_DROPPED); + return false; + } +}; + +} // namespace + +bool AggregateAssignArraysInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + AggregateAssignArraysInSSBOsTraverser traverser(symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h new file mode 100644 index 0000000000..ce965bb002 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h @@ -0,0 +1,23 @@ +// +// Copyright 2022 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool AggregateAssignArraysInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp new file mode 100644 index 0000000000..4293f4c4da --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp @@ -0,0 +1,76 @@ +// +// Copyright 2022 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 "compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h" + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +class AggregateAssignStructsInSSBOsTraverser : public TIntermTraverser +{ + public: + AggregateAssignStructsInSSBOsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable) + {} + + protected: + bool visitBinary(Visit visit, TIntermBinary *node) override + { + // Replace all assignments to structs in SSBOs with field-by-field asignments. + // TODO(anglebug.com/7362): this implementation only works for the simple case (assignment + // statement), not more complex cases such as assignment-as-expression or functions with + // side effects in the RHS. + const TStructure *s; + if (node->getOp() != EOpAssign) + { + return true; + } + else if (!IsInShaderStorageBlock(node->getLeft())) + { + return true; + } + else if (!(s = node->getLeft()->getType().getStruct())) + { + return true; + } + ASSERT(node->getRight()->getType().getStruct() == s); + auto *block = new TIntermBlock(); + for (int i = 0; i < static_cast<int>(s->fields().size()); ++i) + { + auto *left = new TIntermBinary(EOpIndexDirectStruct, node->getLeft()->deepCopy(), + CreateIndexNode(i)); + auto *right = new TIntermBinary(EOpIndexDirectStruct, node->getRight()->deepCopy(), + CreateIndexNode(i)); + auto *assign = new TIntermBinary(TOperator::EOpAssign, left, right); + block->appendStatement(assign); + } + + queueReplacement(block, OriginalNode::IS_DROPPED); + return false; + } +}; + +} // namespace + +bool AggregateAssignStructsInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + AggregateAssignStructsInSSBOsTraverser traverser(symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h new file mode 100644 index 0000000000..910345902b --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h @@ -0,0 +1,23 @@ +// +// Copyright 2022 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool AggregateAssignStructsInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp new file mode 100644 index 0000000000..54d8fc0808 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp @@ -0,0 +1,233 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The ArrayReturnValueToOutParameter function changes return values of an array type to out +// parameters in function definitions, prototypes, and call sites. + +#include "compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h" + +#include <map> + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +constexpr const ImmutableString kReturnValueVariableName("angle_return"); + +class ArrayReturnValueToOutParameterTraverser : private TIntermTraverser +{ + public: + [[nodiscard]] static bool apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + + private: + ArrayReturnValueToOutParameterTraverser(TSymbolTable *symbolTable); + + void visitFunctionPrototype(TIntermFunctionPrototype *node) override; + bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitBranch(Visit visit, TIntermBranch *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + + TIntermAggregate *createReplacementCall(TIntermAggregate *originalCall, + TIntermTyped *returnValueTarget); + + // Set when traversal is inside a function with array return value. + TIntermFunctionDefinition *mFunctionWithArrayReturnValue; + + struct ChangedFunction + { + const TVariable *returnValueVariable; + const TFunction *func; + }; + + // Map from function symbol ids to the changed function. + std::map<int, ChangedFunction> mChangedFunctions; +}; + +TIntermAggregate *ArrayReturnValueToOutParameterTraverser::createReplacementCall( + TIntermAggregate *originalCall, + TIntermTyped *returnValueTarget) +{ + TIntermSequence replacementArguments; + TIntermSequence *originalArguments = originalCall->getSequence(); + for (auto &arg : *originalArguments) + { + replacementArguments.push_back(arg); + } + replacementArguments.push_back(returnValueTarget); + ASSERT(originalCall->getFunction()); + const TSymbolUniqueId &originalId = originalCall->getFunction()->uniqueId(); + TIntermAggregate *replacementCall = TIntermAggregate::CreateFunctionCall( + *mChangedFunctions[originalId.get()].func, &replacementArguments); + replacementCall->setLine(originalCall->getLine()); + return replacementCall; +} + +bool ArrayReturnValueToOutParameterTraverser::apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + ArrayReturnValueToOutParameterTraverser arrayReturnValueToOutParam(symbolTable); + root->traverse(&arrayReturnValueToOutParam); + return arrayReturnValueToOutParam.updateTree(compiler, root); +} + +ArrayReturnValueToOutParameterTraverser::ArrayReturnValueToOutParameterTraverser( + TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable), mFunctionWithArrayReturnValue(nullptr) +{} + +bool ArrayReturnValueToOutParameterTraverser::visitFunctionDefinition( + Visit visit, + TIntermFunctionDefinition *node) +{ + if (node->getFunctionPrototype()->isArray() && visit == PreVisit) + { + // Replacing the function header is done on visitFunctionPrototype(). + mFunctionWithArrayReturnValue = node; + } + if (visit == PostVisit) + { + mFunctionWithArrayReturnValue = nullptr; + } + return true; +} + +void ArrayReturnValueToOutParameterTraverser::visitFunctionPrototype(TIntermFunctionPrototype *node) +{ + if (node->isArray()) + { + // Replace the whole prototype node with another node that has the out parameter + // added. Also set the function to return void. + const TSymbolUniqueId &functionId = node->getFunction()->uniqueId(); + if (mChangedFunctions.find(functionId.get()) == mChangedFunctions.end()) + { + TType *returnValueVariableType = new TType(node->getType()); + returnValueVariableType->setQualifier(EvqParamOut); + ChangedFunction changedFunction; + changedFunction.returnValueVariable = + new TVariable(mSymbolTable, kReturnValueVariableName, returnValueVariableType, + SymbolType::AngleInternal); + TFunction *func = new TFunction(mSymbolTable, node->getFunction()->name(), + node->getFunction()->symbolType(), + StaticType::GetBasic<EbtVoid, EbpUndefined>(), false); + for (size_t i = 0; i < node->getFunction()->getParamCount(); ++i) + { + func->addParameter(node->getFunction()->getParam(i)); + } + func->addParameter(changedFunction.returnValueVariable); + changedFunction.func = func; + mChangedFunctions[functionId.get()] = changedFunction; + } + TIntermFunctionPrototype *replacement = + new TIntermFunctionPrototype(mChangedFunctions[functionId.get()].func); + replacement->setLine(node->getLine()); + + queueReplacement(replacement, OriginalNode::IS_DROPPED); + } +} + +bool ArrayReturnValueToOutParameterTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + ASSERT(!node->isArray() || node->getOp() != EOpCallInternalRawFunction); + if (visit == PreVisit && node->isArray() && node->getOp() == EOpCallFunctionInAST) + { + // Handle call sites where the returned array is not assigned. + // Examples where f() is a function returning an array: + // 1. f(); + // 2. another_array == f(); + // 3. another_function(f()); + // 4. return f(); + // Cases 2 to 4 are already converted to simpler cases by + // SeparateExpressionsReturningArrays, so we only need to worry about the case where a + // function call returning an array forms an expression by itself. + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + if (parentBlock) + { + // replace + // f(); + // with + // type s0[size]; f(s0); + TIntermSequence replacements; + + // type s0[size]; + TIntermDeclaration *returnValueDeclaration = nullptr; + TVariable *returnValue = DeclareTempVariable(mSymbolTable, new TType(node->getType()), + EvqTemporary, &returnValueDeclaration); + replacements.push_back(returnValueDeclaration); + + // f(s0); + TIntermSymbol *returnValueSymbol = CreateTempSymbolNode(returnValue); + replacements.push_back(createReplacementCall(node, returnValueSymbol)); + mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements)); + } + return false; + } + return true; +} + +bool ArrayReturnValueToOutParameterTraverser::visitBranch(Visit visit, TIntermBranch *node) +{ + if (mFunctionWithArrayReturnValue && node->getFlowOp() == EOpReturn) + { + // Instead of returning a value, assign to the out parameter and then return. + TIntermSequence replacements; + + TIntermTyped *expression = node->getExpression(); + ASSERT(expression != nullptr); + const TSymbolUniqueId &functionId = + mFunctionWithArrayReturnValue->getFunction()->uniqueId(); + ASSERT(mChangedFunctions.find(functionId.get()) != mChangedFunctions.end()); + TIntermSymbol *returnValueSymbol = + new TIntermSymbol(mChangedFunctions[functionId.get()].returnValueVariable); + TIntermBinary *replacementAssignment = + new TIntermBinary(EOpAssign, returnValueSymbol, expression); + replacementAssignment->setLine(expression->getLine()); + replacements.push_back(replacementAssignment); + + TIntermBranch *replacementBranch = new TIntermBranch(EOpReturn, nullptr); + replacementBranch->setLine(node->getLine()); + replacements.push_back(replacementBranch); + + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(replacements)); + } + return false; +} + +bool ArrayReturnValueToOutParameterTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (node->getOp() == EOpAssign && node->getLeft()->isArray()) + { + TIntermAggregate *rightAgg = node->getRight()->getAsAggregate(); + ASSERT(rightAgg == nullptr || rightAgg->getOp() != EOpCallInternalRawFunction); + if (rightAgg != nullptr && rightAgg->getOp() == EOpCallFunctionInAST) + { + TIntermAggregate *replacementCall = createReplacementCall(rightAgg, node->getLeft()); + queueReplacement(replacementCall, OriginalNode::IS_DROPPED); + } + } + return false; +} + +} // namespace + +bool ArrayReturnValueToOutParameter(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + return ArrayReturnValueToOutParameterTraverser::apply(compiler, root, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h new file mode 100644 index 0000000000..ae8d04ae9a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h @@ -0,0 +1,27 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The ArrayReturnValueToOutParameter function changes return values of an array type to out +// parameters in function definitions, prototypes and call sites. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool ArrayReturnValueToOutParameter(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp new file mode 100644 index 0000000000..908da7a8c2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp @@ -0,0 +1,110 @@ +// +// Copyright 2016 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. +// + +// BreakVariableAliasingInInnerLoops.h: To optimize simple assignments, the HLSL compiler frontend +// may record a variable as aliasing another. Sometimes the alias information gets garbled +// so we work around this issue by breaking the aliasing chain in inner loops. + +#include "compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +// A HLSL compiler developer gave us more details on the root cause and the workaround needed: +// The root problem is that if the HLSL compiler is applying aliasing information even on +// incomplete simulations (in this case, a single pass). The bug is triggered by an assignment +// that comes from a series of assignments, possibly with swizzled or ternary operators with +// known conditionals, where the source is before the loop. +// So, a workaround is to add a +0 term to variables the first time they are assigned to in +// an inner loop (if they are declared in an outside scope, otherwise there is no need). +// This will break the aliasing chain. + +// For simplicity here we add a +0 to any assignment that is in at least two nested loops. Because +// the bug only shows up with swizzles, and ternary assignment, whole array or whole structure +// assignment don't need a workaround. + +namespace sh +{ + +namespace +{ + +class AliasingBreaker : public TIntermTraverser +{ + public: + AliasingBreaker() : TIntermTraverser(true, false, true) {} + + protected: + bool visitBinary(Visit visit, TIntermBinary *binary) override + { + if (visit != PreVisit) + { + return false; + } + + if (mLoopLevel < 2 || !binary->isAssignment()) + { + return true; + } + + TIntermTyped *B = binary->getRight(); + TType type = B->getType(); + + if (!type.isScalar() && !type.isVector() && !type.isMatrix()) + { + return true; + } + + if (type.isArray() || IsSampler(type.getBasicType())) + { + return true; + } + + // We have a scalar / vector / matrix assignment with loop depth 2. + // Transform it from + // A = B + // to + // A = (B + typeof<B>(0)); + + TIntermBinary *bPlusZero = new TIntermBinary(EOpAdd, B, CreateZeroNode(type)); + bPlusZero->setLine(B->getLine()); + + binary->replaceChildNode(B, bPlusZero); + + return true; + } + + bool visitLoop(Visit visit, TIntermLoop *loop) override + { + if (visit == PreVisit) + { + mLoopLevel++; + } + else + { + ASSERT(mLoopLevel > 0); + mLoopLevel--; + } + + return true; + } + + private: + int mLoopLevel = 0; +}; + +} // anonymous namespace + +bool BreakVariableAliasingInInnerLoops(TCompiler *compiler, TIntermNode *root) +{ + AliasingBreaker breaker; + root->traverse(&breaker); + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h new file mode 100644 index 0000000000..455c7c322d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h @@ -0,0 +1,25 @@ +// +// Copyright 2016 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. +// + +// BreakVariableAliasingInInnerLoops.h: To optimize simple assignments, the HLSL compiler frontend +// may record a variable as aliasing another. Sometimes the alias information gets garbled +// so we work around this issue by breaking the aliasing chain in inner loops. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +[[nodiscard]] bool BreakVariableAliasingInInnerLoops(TCompiler *compiler, TIntermNode *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp new file mode 100644 index 0000000000..e873db56cd --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp @@ -0,0 +1,152 @@ +// +// Copyright 2016 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. +// +// Implementation of the integer pow expressions HLSL bug workaround. +// See header for more info. + +#include "compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h" + +#include <cmath> +#include <cstdlib> + +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class Traverser : public TIntermTraverser +{ + public: + [[nodiscard]] static bool Apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + + private: + Traverser(TSymbolTable *symbolTable); + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + void nextIteration(); + + bool mFound = false; +}; + +// static +bool Traverser::Apply(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + Traverser traverser(symbolTable); + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.mFound) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.mFound); + + return true; +} + +Traverser::Traverser(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable) +{} + +void Traverser::nextIteration() +{ + mFound = false; +} + +bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (mFound) + { + return false; + } + + // Test 0: skip non-pow operators. + if (node->getOp() != EOpPow) + { + return true; + } + + const TIntermSequence *sequence = node->getSequence(); + ASSERT(sequence->size() == 2u); + const TIntermConstantUnion *constantExponent = sequence->at(1)->getAsConstantUnion(); + + // Test 1: check for a single constant. + if (!constantExponent || constantExponent->getNominalSize() != 1) + { + return true; + } + + float exponentValue = constantExponent->getConstantValue()->getFConst(); + + // Test 2: exponentValue is in the problematic range. + if (exponentValue < -5.0f || exponentValue > 9.0f) + { + return true; + } + + // Test 3: exponentValue is integer or pretty close to an integer. + if (std::abs(exponentValue - std::round(exponentValue)) > 0.0001f) + { + return true; + } + + // Test 4: skip -1, 0, and 1 + int exponent = static_cast<int>(std::round(exponentValue)); + int n = std::abs(exponent); + if (n < 2) + { + return true; + } + + // Potential problem case detected, apply workaround. + + TIntermTyped *lhs = sequence->at(0)->getAsTyped(); + ASSERT(lhs); + + TIntermDeclaration *lhsVariableDeclaration = nullptr; + TVariable *lhsVariable = + DeclareTempVariable(mSymbolTable, lhs, EvqTemporary, &lhsVariableDeclaration); + insertStatementInParentBlock(lhsVariableDeclaration); + + // Create a chain of n-1 multiples. + TIntermTyped *current = CreateTempSymbolNode(lhsVariable); + for (int i = 1; i < n; ++i) + { + TIntermBinary *mul = new TIntermBinary(EOpMul, current, CreateTempSymbolNode(lhsVariable)); + mul->setLine(node->getLine()); + current = mul; + } + + // For negative pow, compute the reciprocal of the positive pow. + if (exponent < 0) + { + TConstantUnion *oneVal = new TConstantUnion(); + oneVal->setFConst(1.0f); + TIntermConstantUnion *oneNode = new TIntermConstantUnion(oneVal, node->getType()); + TIntermBinary *div = new TIntermBinary(EOpDiv, oneNode, current); + current = div; + } + + queueReplacement(current, OriginalNode::IS_DROPPED); + mFound = true; + return false; +} + +} // anonymous namespace + +bool ExpandIntegerPowExpressions(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + return Traverser::Apply(compiler, root, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h new file mode 100644 index 0000000000..2404fb08ac --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h @@ -0,0 +1,34 @@ +// +// Copyright 2016 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. +// +// This mutating tree traversal works around a bug in the HLSL compiler optimizer with "pow" that +// manifests under the following conditions: +// +// - If pow() has a literal exponent value +// - ... and this value is integer or within 10e-6 of an integer +// - ... and it is in {-4, -3, -2, 2, 3, 4, 5, 6, 7, 8} +// +// The workaround is to replace the pow with a series of multiplies. +// See http://anglebug.com/851 + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool ExpandIntegerPowExpressions(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp new file mode 100644 index 0000000000..18d49814ac --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp @@ -0,0 +1,385 @@ +// +// 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. +// +// RecordUniformBlocksWithLargeArrayMember.h: +// Collect all uniform blocks which have one or more large array members, +// and the array sizes are greater than or equal to 50. If some of them +// satify some conditions, we will translate them to StructuredBuffers +// on Direct3D backend. +// + +#include "compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ +// Only when a uniform block member's array size is greater than or equal to +// kMinArraySizeUseStructuredBuffer, then we may translate the uniform block +// to a StructuredBuffer on Direct3D backend. +const unsigned int kMinArraySizeUseStructuredBuffer = 50u; + +// There is a maximum of D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT(128) slots that are +// available for shader resources on Direct3D 11. When shader version is 300, we only use +// D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT(16) slots for texture units. We allow StructuredBuffer +// to use the maximum of 60 slots, that is enough here. +const unsigned int kMaxAllowToUseRegisterCount = 60u; + +// Traverser that all uniform blocks which have one or more large array members, and the array +// sizes are greater than or equal to 50. +class UniformBlocksWithLargeArrayMemberTraverser : public TIntermTraverser +{ + public: + UniformBlocksWithLargeArrayMemberTraverser(); + + void visitSymbol(TIntermSymbol *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + std::map<int, const TInterfaceBlock *> &getUniformBlockMayTranslation() + { + return mUniformBlockMayTranslation; + } + std::map<int, const TInterfaceBlock *> &getUniformBlockNotAllowTranslation() + { + return mUniformBlockNotAllowTranslation; + } + std::map<int, unsigned int> &getUniformBlockUsedRegisterCount() + { + return mUniformBlockUsedRegisterCount; + } + std::map<int, const TInterfaceBlock *> &getUniformBlockWithLargeArrayMember() + { + return mUniformBlockWithLargeArrayMember; + } + + private: + std::map<int, const TInterfaceBlock *> mUniformBlockMayTranslation; + std::map<int, const TInterfaceBlock *> mUniformBlockNotAllowTranslation; + std::map<int, unsigned int> mUniformBlockUsedRegisterCount; + std::map<int, const TInterfaceBlock *> mUniformBlockWithLargeArrayMember; +}; + +UniformBlocksWithLargeArrayMemberTraverser::UniformBlocksWithLargeArrayMemberTraverser() + : TIntermTraverser(true, true, false) +{} + +static bool IsSupportedTypeForStructuredBuffer(const TType &type) +{ + const TStructure *structure = type.getStruct(); + const TLayoutMatrixPacking matrixPacking = type.getLayoutQualifier().matrixPacking; + if (structure) + { + const TFieldList &fields = structure->fields(); + for (size_t i = 0; i < fields.size(); i++) + { + const TType &fieldType = *fields[i]->type(); + // Do not allow the structure's member is array or structure. + if (!fieldType.isArray() && !fieldType.getStruct() && + (fieldType.isScalar() || fieldType.isVector() || + (fieldType.isMatrix() && + ((matrixPacking != EmpRowMajor && fieldType.getRows() == 4) || + (matrixPacking == EmpRowMajor && fieldType.getCols() == 4))))) + { + return true; + } + } + return false; + } + else if (type.isMatrix()) + { + // Only supports the matrix types that we do not need to pad in a structure or an array + // explicitly. + return (matrixPacking != EmpRowMajor && type.getRows() == 4) || + (matrixPacking == EmpRowMajor && type.getCols() == 4); + } + else + { + // Supports vector and scalar types in a structure or an array. + return true; + } +} + +static bool CanTranslateUniformBlockToStructuredBuffer(const TInterfaceBlock &interfaceBlock) +{ + const TLayoutBlockStorage blockStorage = interfaceBlock.blockStorage(); + + if (blockStorage == EbsStd140 && interfaceBlock.fields().size() == 1u) + { + const TType &fieldType = *interfaceBlock.fields()[0]->type(); + if (fieldType.getNumArraySizes() == 1u && + fieldType.getOutermostArraySize() >= kMinArraySizeUseStructuredBuffer) + { + return IsSupportedTypeForStructuredBuffer(fieldType); + } + } + + return false; +} + +static bool FieldIsOrHasLargeArrayField(const TField &field) +{ + const TType *type = field.type(); + if (type->getArraySizeProduct() >= kMinArraySizeUseStructuredBuffer) + { + return true; + } + + const TStructure *structure = type->getStruct(); + if (structure) + { + const TFieldList &fields = structure->fields(); + bool hasLargeArrayField = false; + for (size_t i = 0; i < fields.size(); i++) + { + hasLargeArrayField = FieldIsOrHasLargeArrayField(*fields[i]); + if (hasLargeArrayField) + { + break; + } + } + return hasLargeArrayField; + } + + return false; +} + +static bool IsInterfaceBlockWithLargeArrayField(const TInterfaceBlock &interfaceBlock) +{ + const TFieldList &fields = interfaceBlock.fields(); + bool isLargeArrayField = false; + for (size_t i = 0; i < fields.size(); i++) + { + isLargeArrayField = FieldIsOrHasLargeArrayField(*fields[i]); + if (isLargeArrayField) + { + break; + } + } + + return isLargeArrayField; +} + +void UniformBlocksWithLargeArrayMemberTraverser::visitSymbol(TIntermSymbol *node) +{ + const TVariable &variable = node->variable(); + const TType &variableType = variable.getType(); + TQualifier qualifier = variable.getType().getQualifier(); + + if (qualifier == EvqUniform) + { + const TInterfaceBlock *interfaceBlock = variableType.getInterfaceBlock(); + if (interfaceBlock) + { + if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock)) + { + if (mUniformBlockMayTranslation.count(interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockMayTranslation[interfaceBlock->uniqueId().get()] = interfaceBlock; + } + + if (!variableType.isInterfaceBlock()) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + // The uniform block variable is array type, only indexing operator is allowed + // to operate on the variable, otherwise do not translate the uniform block to + // HLSL StructuredBuffer. + if (!accessorAsBinary || + !(accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect))) + { + if (mUniformBlockNotAllowTranslation.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockNotAllowTranslation[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + else + { + if (mUniformBlockUsedRegisterCount.count( + interfaceBlock->uniqueId().get()) == 0) + { + // The uniform block is not an instanced one, so it only uses one + // register. + mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] = 1; + } + } + } + else + { + if (mUniformBlockUsedRegisterCount.count(interfaceBlock->uniqueId().get()) == 0) + { + // The uniform block is an instanced one, the count of used registers + // depends on the array size of variable. + mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] = + variableType.isArray() ? variableType.getOutermostArraySize() : 1; + } + } + } + + if (interfaceBlock->blockStorage() == EbsStd140 && + IsInterfaceBlockWithLargeArrayField(*interfaceBlock)) + { + if (!variableType.isInterfaceBlock()) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + if (accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect)) + { + if (mUniformBlockWithLargeArrayMember.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + } + } + } + } +} + +bool UniformBlocksWithLargeArrayMemberTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + switch (node->getOp()) + { + case EOpIndexDirect: + { + if (visit == PreVisit) + { + const TType &leftType = node->getLeft()->getType(); + if (leftType.isInterfaceBlock()) + { + const TInterfaceBlock *interfaceBlock = leftType.getInterfaceBlock(); + if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock) && + mUniformBlockMayTranslation.count(interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockMayTranslation[interfaceBlock->uniqueId().get()] = + interfaceBlock; + if (mUniformBlockUsedRegisterCount.count( + interfaceBlock->uniqueId().get()) == 0) + { + // The uniform block is an instanced one, the count of used registers + // depends on the array size of variable. + mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] = + leftType.isArray() ? leftType.getOutermostArraySize() : 1; + } + return false; + } + + if (interfaceBlock->blockStorage() == EbsStd140 && + IsInterfaceBlockWithLargeArrayField(*interfaceBlock)) + { + if (mUniformBlockWithLargeArrayMember.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + } + } + break; + } + case EOpIndexDirectInterfaceBlock: + { + if (visit == InVisit) + { + const TInterfaceBlock *interfaceBlock = + node->getLeft()->getType().getInterfaceBlock(); + if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock)) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + // The uniform block variable is array type, only indexing operator is allowed + // to operate on the variable, otherwise do not translate the uniform block to + // HLSL StructuredBuffer. + if ((!accessorAsBinary || + !(accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect))) && + mUniformBlockNotAllowTranslation.count(interfaceBlock->uniqueId().get()) == + 0) + { + mUniformBlockNotAllowTranslation[interfaceBlock->uniqueId().get()] = + interfaceBlock; + return false; + } + } + + if (interfaceBlock->blockStorage() == EbsStd140 && + IsInterfaceBlockWithLargeArrayField(*interfaceBlock)) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + if (accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect)) + { + if (mUniformBlockWithLargeArrayMember.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + } + } + break; + } + default: + break; + } + + return true; +} +} // namespace + +bool RecordUniformBlocksWithLargeArrayMember( + TIntermNode *root, + std::map<int, const TInterfaceBlock *> &uniformBlockOptimizedMap, + std::set<std::string> &slowCompilingUniformBlockSet) +{ + UniformBlocksWithLargeArrayMemberTraverser traverser; + root->traverse(&traverser); + std::map<int, const TInterfaceBlock *> &uniformBlockMayTranslation = + traverser.getUniformBlockMayTranslation(); + std::map<int, const TInterfaceBlock *> &uniformBlockNotAllowTranslation = + traverser.getUniformBlockNotAllowTranslation(); + std::map<int, unsigned int> &uniformBlockUsedRegisterCount = + traverser.getUniformBlockUsedRegisterCount(); + std::map<int, const TInterfaceBlock *> &uniformBlockWithLargeArrayMember = + traverser.getUniformBlockWithLargeArrayMember(); + + unsigned int usedRegisterCount = 0; + for (auto &uniformBlock : uniformBlockMayTranslation) + { + if (uniformBlockNotAllowTranslation.count(uniformBlock.first) == 0) + { + usedRegisterCount += uniformBlockUsedRegisterCount[uniformBlock.first]; + if (usedRegisterCount > kMaxAllowToUseRegisterCount) + { + break; + } + uniformBlockOptimizedMap[uniformBlock.first] = uniformBlock.second; + } + } + + for (auto &uniformBlock : uniformBlockWithLargeArrayMember) + { + if (uniformBlockOptimizedMap.count(uniformBlock.first) == 0) + { + slowCompilingUniformBlockSet.insert(uniformBlock.second->name().data()); + } + } + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h new file mode 100644 index 0000000000..d7825c28cb --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h @@ -0,0 +1,28 @@ +// +// 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. +// +// RecordUniformBlocksWithLargeArrayMember.h: +// Collect all uniform blocks which have one or more large array members, +// and the array sizes are greater than or equal to 50. If some of them +// satify some conditions, we will translate them to StructuredBuffers +// on Direct3D backend. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_ + +#include "compiler/translator/IntermNode.h" + +namespace sh +{ +class TIntermNode; + +[[nodiscard]] bool RecordUniformBlocksWithLargeArrayMember( + TIntermNode *root, + std::map<int, const TInterfaceBlock *> &uniformBlockOptimizedMap, + std::set<std::string> &slowCompilingUniformBlockSet); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp new file mode 100644 index 0000000000..6360f1083c --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp @@ -0,0 +1,270 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// RemoveSwitchFallThrough.cpp: Remove fall-through from switch statements. +// Note that it is unsafe to do further AST transformations on the AST generated +// by this function. It leaves duplicate nodes in the AST making replacements +// unreliable. + +#include "compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h" + +#include "compiler/translator/Diagnostics.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class RemoveSwitchFallThroughTraverser : public TIntermTraverser +{ + public: + static TIntermBlock *removeFallThrough(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics); + + private: + RemoveSwitchFallThroughTraverser(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics); + + void visitSymbol(TIntermSymbol *node) override; + void visitConstantUnion(TIntermConstantUnion *node) override; + bool visitDeclaration(Visit, TIntermDeclaration *node) override; + bool visitBinary(Visit, TIntermBinary *node) override; + bool visitUnary(Visit, TIntermUnary *node) override; + bool visitTernary(Visit visit, TIntermTernary *node) override; + bool visitSwizzle(Visit, TIntermSwizzle *node) override; + bool visitIfElse(Visit visit, TIntermIfElse *node) override; + bool visitSwitch(Visit, TIntermSwitch *node) override; + bool visitCase(Visit, TIntermCase *node) override; + bool visitAggregate(Visit, TIntermAggregate *node) override; + bool visitBlock(Visit, TIntermBlock *node) override; + bool visitLoop(Visit, TIntermLoop *node) override; + bool visitBranch(Visit, TIntermBranch *node) override; + + void outputSequence(TIntermSequence *sequence, size_t startIndex); + void handlePreviousCase(); + + TIntermBlock *mStatementList; + TIntermBlock *mStatementListOut; + bool mLastStatementWasBreak; + TIntermBlock *mPreviousCase; + std::vector<TIntermBlock *> mCasesSharingBreak; + PerformanceDiagnostics *mPerfDiagnostics; +}; + +TIntermBlock *RemoveSwitchFallThroughTraverser::removeFallThrough( + TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics) +{ + RemoveSwitchFallThroughTraverser rm(statementList, perfDiagnostics); + ASSERT(statementList); + statementList->traverse(&rm); + ASSERT(rm.mPreviousCase || statementList->getSequence()->empty()); + if (!rm.mLastStatementWasBreak && rm.mPreviousCase) + { + // Make sure that there's a branch at the end of the final case inside the switch statement. + // This also ensures that any cases that fall through to the final case will get the break. + TIntermBranch *finalBreak = new TIntermBranch(EOpBreak, nullptr); + rm.mPreviousCase->getSequence()->push_back(finalBreak); + rm.mLastStatementWasBreak = true; + } + rm.handlePreviousCase(); + return rm.mStatementListOut; +} + +RemoveSwitchFallThroughTraverser::RemoveSwitchFallThroughTraverser( + TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics) + : TIntermTraverser(true, false, false), + mStatementList(statementList), + mLastStatementWasBreak(false), + mPreviousCase(nullptr), + mPerfDiagnostics(perfDiagnostics) +{ + mStatementListOut = new TIntermBlock(); +} + +void RemoveSwitchFallThroughTraverser::visitSymbol(TIntermSymbol *node) +{ + // Note that this assumes that switch statements which don't begin by a case statement + // have already been weeded out in validation. + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; +} + +void RemoveSwitchFallThroughTraverser::visitConstantUnion(TIntermConstantUnion *node) +{ + // Conditions of case labels are not traversed, so this is a constant statement like "0;". + // These are no-ops so there's no need to add them back to the statement list. Should have + // already been pruned out of the AST, in fact. + UNREACHABLE(); +} + +bool RemoveSwitchFallThroughTraverser::visitDeclaration(Visit, TIntermDeclaration *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitBinary(Visit, TIntermBinary *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitUnary(Visit, TIntermUnary *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitTernary(Visit, TIntermTernary *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitSwizzle(Visit, TIntermSwizzle *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitIfElse(Visit, TIntermIfElse *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitSwitch(Visit, TIntermSwitch *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + // Don't go into nested switch statements + return false; +} + +void RemoveSwitchFallThroughTraverser::outputSequence(TIntermSequence *sequence, size_t startIndex) +{ + for (size_t i = startIndex; i < sequence->size(); ++i) + { + mStatementListOut->getSequence()->push_back(sequence->at(i)); + } +} + +void RemoveSwitchFallThroughTraverser::handlePreviousCase() +{ + if (mPreviousCase) + mCasesSharingBreak.push_back(mPreviousCase); + if (mLastStatementWasBreak) + { + for (size_t i = 0; i < mCasesSharingBreak.size(); ++i) + { + ASSERT(!mCasesSharingBreak.at(i)->getSequence()->empty()); + if (mCasesSharingBreak.at(i)->getSequence()->size() == 1) + { + // Fall-through is allowed in case the label has no statements. + outputSequence(mCasesSharingBreak.at(i)->getSequence(), 0); + } + else + { + // Include all the statements that this case can fall through under the same label. + if (mCasesSharingBreak.size() > i + 1u) + { + mPerfDiagnostics->warning(mCasesSharingBreak.at(i)->getLine(), + "Performance: non-empty fall-through cases in " + "switch statements generate extra code.", + "switch"); + } + for (size_t j = i; j < mCasesSharingBreak.size(); ++j) + { + size_t startIndex = + j > i ? 1 : 0; // Add the label only from the first sequence. + outputSequence(mCasesSharingBreak.at(j)->getSequence(), startIndex); + } + } + } + mCasesSharingBreak.clear(); + } + mLastStatementWasBreak = false; + mPreviousCase = nullptr; +} + +bool RemoveSwitchFallThroughTraverser::visitCase(Visit, TIntermCase *node) +{ + handlePreviousCase(); + mPreviousCase = new TIntermBlock(); + mPreviousCase->getSequence()->push_back(node); + mPreviousCase->setLine(node->getLine()); + // Don't traverse the condition of the case statement + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitAggregate(Visit, TIntermAggregate *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool DoesBlockAlwaysBreak(TIntermBlock *node) +{ + if (node->getSequence()->empty()) + { + return false; + } + + TIntermBlock *lastStatementAsBlock = node->getSequence()->back()->getAsBlock(); + if (lastStatementAsBlock) + { + return DoesBlockAlwaysBreak(lastStatementAsBlock); + } + + TIntermBranch *lastStatementAsBranch = node->getSequence()->back()->getAsBranchNode(); + return lastStatementAsBranch != nullptr; +} + +bool RemoveSwitchFallThroughTraverser::visitBlock(Visit, TIntermBlock *node) +{ + if (node != mStatementList) + { + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = DoesBlockAlwaysBreak(node); + return false; + } + return true; +} + +bool RemoveSwitchFallThroughTraverser::visitLoop(Visit, TIntermLoop *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitBranch(Visit, TIntermBranch *node) +{ + mPreviousCase->getSequence()->push_back(node); + // TODO: Verify that accepting return or continue statements here doesn't cause problems. + mLastStatementWasBreak = true; + return false; +} + +} // anonymous namespace + +TIntermBlock *RemoveSwitchFallThrough(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics) +{ + return RemoveSwitchFallThroughTraverser::removeFallThrough(statementList, perfDiagnostics); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h new file mode 100644 index 0000000000..b92e7e5f6d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h @@ -0,0 +1,27 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// RemoveSwitchFallThrough.h: Remove fall-through from switch statements. +// Note that it is unsafe to do further AST transformations on the AST generated +// by this function. It leaves duplicate nodes in the AST making replacements +// unreliable. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_ + +namespace sh +{ + +class TIntermBlock; +class PerformanceDiagnostics; + +// When given a statementList from a switch AST node, return an updated +// statementList that has fall-through removed. +TIntermBlock *RemoveSwitchFallThrough(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp new file mode 100644 index 0000000000..b2de6079b7 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp @@ -0,0 +1,183 @@ +// +// Copyright 2018 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. +// +// Implementation of the function RewriteAtomicFunctionExpressions. +// See the header for more details. + +#include "compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h" + +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ +namespace +{ +// Traverser that simplifies all the atomic function expressions into the ones that can be directly +// translated into HLSL. +// +// case 1 (only for atomicExchange and atomicCompSwap): +// original: +// atomicExchange(counter, newValue); +// new: +// tempValue = atomicExchange(counter, newValue); +// +// case 2 (atomic function, temporary variable required): +// original: +// value = atomicAdd(counter, 1) * otherValue; +// someArray[atomicAdd(counter, 1)] = someOtherValue; +// new: +// value = ((tempValue = atomicAdd(counter, 1)), tempValue) * otherValue; +// someArray[((tempValue = atomicAdd(counter, 1)), tempValue)] = someOtherValue; +// +// case 3 (atomic function used directly initialize a variable): +// original: +// int value = atomicAdd(counter, 1); +// new: +// tempValue = atomicAdd(counter, 1); +// int value = tempValue; +// +class RewriteAtomicFunctionExpressionsTraverser : public TIntermTraverser +{ + public: + RewriteAtomicFunctionExpressionsTraverser(TSymbolTable *symbolTable, int shaderVersion); + + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitBlock(Visit visit, TIntermBlock *node) override; + + private: + static bool IsAtomicExchangeOrCompSwapNoReturnValue(TIntermAggregate *node, + TIntermNode *parentNode); + static bool IsAtomicFunctionInsideExpression(TIntermAggregate *node, TIntermNode *parentNode); + + void rewriteAtomicFunctionCallNode(TIntermAggregate *oldAtomicFunctionNode); + + const TVariable *getTempVariable(const TType *type); + + int mShaderVersion; + TIntermSequence mTempVariables; +}; + +RewriteAtomicFunctionExpressionsTraverser::RewriteAtomicFunctionExpressionsTraverser( + TSymbolTable *symbolTable, + int shaderVersion) + : TIntermTraverser(false, false, true, symbolTable), mShaderVersion(shaderVersion) +{} + +void RewriteAtomicFunctionExpressionsTraverser::rewriteAtomicFunctionCallNode( + TIntermAggregate *oldAtomicFunctionNode) +{ + ASSERT(oldAtomicFunctionNode); + + const TVariable *returnVariable = getTempVariable(&oldAtomicFunctionNode->getType()); + + TIntermBinary *rewrittenNode = new TIntermBinary( + TOperator::EOpAssign, CreateTempSymbolNode(returnVariable), oldAtomicFunctionNode); + + auto *parentNode = getParentNode(); + + auto *parentBinary = parentNode->getAsBinaryNode(); + if (parentBinary && parentBinary->getOp() == EOpInitialize) + { + insertStatementInParentBlock(rewrittenNode); + queueReplacement(CreateTempSymbolNode(returnVariable), OriginalNode::IS_DROPPED); + } + else + { + // As all atomic function assignment will be converted to the last argument of an + // interlocked function, if we need the return value, assignment needs to be wrapped with + // the comma operator and the temporary variables. + if (!parentNode->getAsBlock()) + { + rewrittenNode = TIntermBinary::CreateComma( + rewrittenNode, new TIntermSymbol(returnVariable), mShaderVersion); + } + + queueReplacement(rewrittenNode, OriginalNode::IS_DROPPED); + } +} + +const TVariable *RewriteAtomicFunctionExpressionsTraverser::getTempVariable(const TType *type) +{ + TIntermDeclaration *variableDeclaration; + TVariable *returnVariable = + DeclareTempVariable(mSymbolTable, type, EvqTemporary, &variableDeclaration); + mTempVariables.push_back(variableDeclaration); + return returnVariable; +} + +bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicExchangeOrCompSwapNoReturnValue( + TIntermAggregate *node, + TIntermNode *parentNode) +{ + ASSERT(node); + return (node->getOp() == EOpAtomicExchange || node->getOp() == EOpAtomicCompSwap) && + parentNode && parentNode->getAsBlock(); +} + +bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicFunctionInsideExpression( + TIntermAggregate *node, + TIntermNode *parentNode) +{ + ASSERT(node); + // We only need to handle atomic functions with a parent that it is not block nodes. If the + // parent node is block, it means that the atomic function is not inside an expression. + if (!BuiltInGroup::IsAtomicMemory(node->getOp()) || parentNode->getAsBlock()) + { + return false; + } + + auto *parentAsBinary = parentNode->getAsBinaryNode(); + // Assignments are handled in OutputHLSL + return !parentAsBinary || parentAsBinary->getOp() != EOpAssign; +} + +bool RewriteAtomicFunctionExpressionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + ASSERT(visit == PostVisit); + // Skip atomic memory functions for SSBO. They will be processed in the OutputHLSL traverser. + if (BuiltInGroup::IsAtomicMemory(node->getOp()) && + IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped())) + { + return false; + } + + TIntermNode *parentNode = getParentNode(); + if (IsAtomicExchangeOrCompSwapNoReturnValue(node, parentNode) || + IsAtomicFunctionInsideExpression(node, parentNode)) + { + rewriteAtomicFunctionCallNode(node); + } + + return true; +} + +bool RewriteAtomicFunctionExpressionsTraverser::visitBlock(Visit visit, TIntermBlock *node) +{ + ASSERT(visit == PostVisit); + + if (!mTempVariables.empty() && getParentNode()->getAsFunctionDefinition()) + { + insertStatementsInBlockAtPosition(node, 0, mTempVariables, TIntermSequence()); + mTempVariables.clear(); + } + + return true; +} + +} // anonymous namespace + +bool RewriteAtomicFunctionExpressions(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + int shaderVersion) +{ + RewriteAtomicFunctionExpressionsTraverser traverser(symbolTable, shaderVersion); + traverser.traverse(root); + return traverser.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h new file mode 100644 index 0000000000..60e6ffa2d1 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h @@ -0,0 +1,42 @@ +// +// Copyright 2018 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. +// +// RewriteAtomicFunctionExpressions rewrites the expressions that contain +// atomic function calls and cannot be directly translated into HLSL into +// several simple ones that can be easily handled in the HLSL translator. +// +// We need to rewite these expressions because: +// 1. All GLSL atomic functions have return values, which all represent the +// original value of the shared or ssbo variable; while all HLSL atomic +// functions don't, and the original value can be stored in the last +// parameter of the function call. +// 2. For HLSL atomic functions, the last parameter that stores the original +// value is optional except for InterlockedExchange and +// InterlockedCompareExchange. Missing original_value in the call of +// InterlockedExchange or InterlockedCompareExchange results in a compile +// error from HLSL compiler. +// +// RewriteAtomicFunctionExpressions is a function that can modify the AST +// to ensure all the expressions that contain atomic function calls can be +// directly translated into HLSL expressions. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RewriteAtomicFunctionExpressions(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + int shaderVersion); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp new file mode 100644 index 0000000000..10647d1bf1 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp @@ -0,0 +1,123 @@ +// +// Copyright 2014 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. +// +// RewriteElseBlocks.cpp: Implementation for tree transform to change +// all if-else blocks to if-if blocks. +// + +#include "compiler/translator/tree_ops/d3d/RewriteElseBlocks.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/NodeSearch.h" + +namespace sh +{ + +namespace +{ + +class ElseBlockRewriter : public TIntermTraverser +{ + public: + ElseBlockRewriter(TSymbolTable *symbolTable); + + protected: + bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *aggregate) override; + bool visitBlock(Visit visit, TIntermBlock *block) override; + + private: + TIntermNode *rewriteIfElse(TIntermIfElse *ifElse); + + const TType *mFunctionType; +}; + +ElseBlockRewriter::ElseBlockRewriter(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable), mFunctionType(nullptr) +{} + +bool ElseBlockRewriter::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) +{ + // Store the current function context (see comment below) + mFunctionType = ((visit == PreVisit) ? &node->getFunctionPrototype()->getType() : nullptr); + return true; +} + +bool ElseBlockRewriter::visitBlock(Visit visit, TIntermBlock *node) +{ + if (visit == PostVisit) + { + for (size_t statementIndex = 0; statementIndex != node->getSequence()->size(); + statementIndex++) + { + TIntermNode *statement = (*node->getSequence())[statementIndex]; + TIntermIfElse *ifElse = statement->getAsIfElseNode(); + if (ifElse && ifElse->getFalseBlock() != nullptr) + { + (*node->getSequence())[statementIndex] = rewriteIfElse(ifElse); + } + } + } + return true; +} + +TIntermNode *ElseBlockRewriter::rewriteIfElse(TIntermIfElse *ifElse) +{ + ASSERT(ifElse != nullptr); + + TIntermDeclaration *storeCondition = nullptr; + TVariable *conditionVariable = + DeclareTempVariable(mSymbolTable, ifElse->getCondition(), EvqTemporary, &storeCondition); + + TIntermBlock *falseBlock = nullptr; + + TType boolType(EbtBool, EbpUndefined, EvqTemporary); + + if (ifElse->getFalseBlock()) + { + TIntermBlock *negatedElse = nullptr; + // crbug.com/346463 + // D3D generates error messages claiming a function has no return value, when rewriting + // an if-else clause that returns something non-void in a function. By appending mock + // returns (that are unreachable) we can silence this compile error. + if (mFunctionType && mFunctionType->getBasicType() != EbtVoid) + { + TIntermNode *returnNode = new TIntermBranch(EOpReturn, CreateZeroNode(*mFunctionType)); + negatedElse = new TIntermBlock(); + negatedElse->appendStatement(returnNode); + } + + TIntermSymbol *conditionSymbolElse = CreateTempSymbolNode(conditionVariable); + TIntermUnary *negatedCondition = + new TIntermUnary(EOpLogicalNot, conditionSymbolElse, nullptr); + TIntermIfElse *falseIfElse = + new TIntermIfElse(negatedCondition, ifElse->getFalseBlock(), negatedElse); + falseBlock = EnsureBlock(falseIfElse); + } + + TIntermSymbol *conditionSymbolSel = CreateTempSymbolNode(conditionVariable); + TIntermIfElse *newIfElse = + new TIntermIfElse(conditionSymbolSel, ifElse->getTrueBlock(), falseBlock); + + TIntermBlock *block = new TIntermBlock(); + block->getSequence()->push_back(storeCondition); + block->getSequence()->push_back(newIfElse); + + return block; +} + +} // anonymous namespace + +bool RewriteElseBlocks(TCompiler *compiler, TIntermNode *node, TSymbolTable *symbolTable) +{ + ElseBlockRewriter rewriter(symbolTable); + node->traverse(&rewriter); + + return compiler->validateAST(node); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h new file mode 100644 index 0000000000..cab0f7090c --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h @@ -0,0 +1,27 @@ +// +// Copyright 2014 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. +// +// RewriteElseBlocks.h: Prototype for tree transform to change +// all if-else blocks to if-if blocks. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RewriteElseBlocks(TCompiler *compiler, + TIntermNode *node, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp new file mode 100644 index 0000000000..709f394878 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp @@ -0,0 +1,420 @@ +// +// Copyright 2018 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. +// +// RewriteExpressionsWithShaderStorageBlock rewrites the expressions that contain shader storage +// block calls into several simple ones that can be easily handled in the HLSL translator. After the +// AST pass, all ssbo related blocks will be like below: +// ssbo_access_chain = ssbo_access_chain; +// ssbo_access_chain = expr_no_ssbo; +// lvalue_no_ssbo = ssbo_access_chain; +// + +#include "compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h" + +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ +namespace +{ + +bool IsIncrementOrDecrementOperator(TOperator op) +{ + switch (op) + { + case EOpPostIncrement: + case EOpPostDecrement: + case EOpPreIncrement: + case EOpPreDecrement: + return true; + default: + return false; + } +} + +bool IsCompoundAssignment(TOperator op) +{ + switch (op) + { + case EOpAddAssign: + case EOpSubAssign: + case EOpMulAssign: + case EOpVectorTimesMatrixAssign: + case EOpVectorTimesScalarAssign: + case EOpMatrixTimesScalarAssign: + case EOpMatrixTimesMatrixAssign: + case EOpDivAssign: + case EOpIModAssign: + case EOpBitShiftLeftAssign: + case EOpBitShiftRightAssign: + case EOpBitwiseAndAssign: + case EOpBitwiseXorAssign: + case EOpBitwiseOrAssign: + return true; + default: + return false; + } +} + +// EOpIndexDirect, EOpIndexIndirect, EOpIndexDirectStruct, EOpIndexDirectInterfaceBlock belong to +// operators in SSBO access chain. +bool IsReadonlyBinaryOperatorNotInSSBOAccessChain(TOperator op) +{ + switch (op) + { + case EOpComma: + case EOpAdd: + case EOpSub: + case EOpMul: + case EOpDiv: + case EOpIMod: + case EOpBitShiftLeft: + case EOpBitShiftRight: + case EOpBitwiseAnd: + case EOpBitwiseXor: + case EOpBitwiseOr: + case EOpEqual: + case EOpNotEqual: + case EOpLessThan: + case EOpGreaterThan: + case EOpLessThanEqual: + case EOpGreaterThanEqual: + case EOpVectorTimesScalar: + case EOpMatrixTimesScalar: + case EOpVectorTimesMatrix: + case EOpMatrixTimesVector: + case EOpMatrixTimesMatrix: + case EOpLogicalOr: + case EOpLogicalXor: + case EOpLogicalAnd: + return true; + default: + return false; + } +} + +bool HasSSBOAsFunctionArgument(TIntermSequence *arguments) +{ + for (TIntermNode *arg : *arguments) + { + TIntermTyped *typedArg = arg->getAsTyped(); + if (IsInShaderStorageBlock(typedArg)) + { + return true; + } + } + return false; +} + +class RewriteExpressionsWithShaderStorageBlockTraverser : public TIntermTraverser +{ + public: + RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable); + void nextIteration(); + bool foundSSBO() const { return mFoundSSBO; } + + private: + bool visitBinary(Visit, TIntermBinary *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitUnary(Visit visit, TIntermUnary *node) override; + + TIntermSymbol *insertInitStatementAndReturnTempSymbol(TIntermTyped *node, + TIntermSequence *insertions); + + bool mFoundSSBO; +}; + +RewriteExpressionsWithShaderStorageBlockTraverser:: + RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, true, false, symbolTable), mFoundSSBO(false) +{} + +TIntermSymbol * +RewriteExpressionsWithShaderStorageBlockTraverser::insertInitStatementAndReturnTempSymbol( + TIntermTyped *node, + TIntermSequence *insertions) +{ + TIntermDeclaration *variableDeclaration; + TVariable *tempVariable = + DeclareTempVariable(mSymbolTable, node, EvqTemporary, &variableDeclaration); + + insertions->push_back(variableDeclaration); + return CreateTempSymbolNode(tempVariable); +} + +bool RewriteExpressionsWithShaderStorageBlockTraverser::visitBinary(Visit visit, + TIntermBinary *node) +{ + // Make sure that the expression is caculated from left to right. + if (visit != InVisit) + { + return true; + } + + if (mFoundSSBO) + { + return false; + } + + bool rightSSBO = IsInShaderStorageBlock(node->getRight()); + bool leftSSBO = IsInShaderStorageBlock(node->getLeft()); + if (!leftSSBO && !rightSSBO) + { + return true; + } + + // case 1: Compound assigment operator + // original: + // lssbo += expr; + // new: + // var rvalue = expr; + // var temp = lssbo; + // temp += rvalue; + // lssbo = temp; + // + // original: + // lvalue_no_ssbo += rssbo; + // new: + // var rvalue = rssbo; + // lvalue_no_ssbo += rvalue; + if (IsCompoundAssignment(node->getOp())) + { + mFoundSSBO = true; + TIntermSequence insertions; + TIntermTyped *rightNode = + insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions); + if (leftSSBO) + { + TIntermSymbol *tempSymbol = + insertInitStatementAndReturnTempSymbol(node->getLeft()->deepCopy(), &insertions); + TIntermBinary *tempCompoundOperate = + new TIntermBinary(node->getOp(), tempSymbol->deepCopy(), rightNode->deepCopy()); + insertions.push_back(tempCompoundOperate); + insertStatementsInParentBlock(insertions); + + TIntermBinary *assignTempValueToSSBO = + new TIntermBinary(EOpAssign, node->getLeft(), tempSymbol->deepCopy()); + queueReplacement(assignTempValueToSSBO, OriginalNode::IS_DROPPED); + } + else + { + insertStatementsInParentBlock(insertions); + TIntermBinary *compoundAssignRValueToLValue = + new TIntermBinary(node->getOp(), node->getLeft(), rightNode->deepCopy()); + queueReplacement(compoundAssignRValueToLValue, OriginalNode::IS_DROPPED); + } + } + // case 2: Readonly binary operator + // original: + // ssbo0 + ssbo1 + ssbo2; + // new: + // var temp0 = ssbo0; + // var temp1 = ssbo1; + // var temp2 = ssbo2; + // temp0 + temp1 + temp2; + else if (IsReadonlyBinaryOperatorNotInSSBOAccessChain(node->getOp()) && (leftSSBO || rightSSBO)) + { + mFoundSSBO = true; + TIntermTyped *rightNode = node->getRight(); + TIntermTyped *leftNode = node->getLeft(); + TIntermSequence insertions; + if (rightSSBO) + { + rightNode = insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions); + } + if (leftSSBO) + { + leftNode = insertInitStatementAndReturnTempSymbol(node->getLeft(), &insertions); + } + + insertStatementsInParentBlock(insertions); + TIntermBinary *newExpr = + new TIntermBinary(node->getOp(), leftNode->deepCopy(), rightNode->deepCopy()); + queueReplacement(newExpr, OriginalNode::IS_DROPPED); + } + return !mFoundSSBO; +} + +// case 3: ssbo as the argument of aggregate type +// original: +// foo(ssbo); +// new: +// var tempArg = ssbo; +// foo(tempArg); +// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument) +// +// original: +// foo(ssbo) * expr; +// new: +// var tempArg = ssbo; +// var tempReturn = foo(tempArg); +// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument) +// tempReturn * expr; +bool RewriteExpressionsWithShaderStorageBlockTraverser::visitAggregate(Visit visit, + TIntermAggregate *node) +{ + // Make sure that visitAggregate is only executed once for same node. + if (visit != PreVisit) + { + return true; + } + + if (mFoundSSBO) + { + return false; + } + + // We still need to process the ssbo as the non-first argument of atomic memory functions. + if (BuiltInGroup::IsAtomicMemory(node->getOp()) && + IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped())) + { + return true; + } + + if (!HasSSBOAsFunctionArgument(node->getSequence())) + { + return true; + } + + mFoundSSBO = true; + TIntermSequence insertions; + TIntermSequence readBackToSSBOs; + TIntermSequence *originalArguments = node->getSequence(); + for (size_t i = 0; i < node->getChildCount(); ++i) + { + TIntermTyped *ssboArgument = (*originalArguments)[i]->getAsTyped(); + if (IsInShaderStorageBlock(ssboArgument)) + { + TIntermSymbol *argumentCopy = + insertInitStatementAndReturnTempSymbol(ssboArgument, &insertions); + if (node->getFunction() != nullptr) + { + TQualifier qual = node->getFunction()->getParam(i)->getType().getQualifier(); + if (qual == EvqParamInOut || qual == EvqParamOut) + { + TIntermBinary *readBackToSSBO = new TIntermBinary( + EOpAssign, ssboArgument->deepCopy(), argumentCopy->deepCopy()); + readBackToSSBOs.push_back(readBackToSSBO); + } + } + node->replaceChildNode(ssboArgument, argumentCopy); + } + } + + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + if (parentBlock) + { + // Aggregate node is as a single sentence. + insertions.push_back(node); + if (!readBackToSSBOs.empty()) + { + insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end()); + } + mMultiReplacements.emplace_back(parentBlock, node, std::move(insertions)); + } + else + { + // Aggregate node is inside an expression. + TIntermSymbol *tempSymbol = insertInitStatementAndReturnTempSymbol(node, &insertions); + if (!readBackToSSBOs.empty()) + { + insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end()); + } + insertStatementsInParentBlock(insertions); + queueReplacement(tempSymbol->deepCopy(), OriginalNode::IS_DROPPED); + } + + return false; +} + +bool RewriteExpressionsWithShaderStorageBlockTraverser::visitUnary(Visit visit, TIntermUnary *node) +{ + if (mFoundSSBO) + { + return false; + } + + if (!IsInShaderStorageBlock(node->getOperand())) + { + return true; + } + + // .length() is processed in OutputHLSL. + if (node->getOp() == EOpArrayLength) + { + return true; + } + + mFoundSSBO = true; + + // case 4: ssbo as the operand of ++/-- + // original: + // ++ssbo * expr; + // new: + // var temp1 = ssbo; + // var temp2 = ++temp1; + // ssbo = temp1; + // temp2 * expr; + if (IsIncrementOrDecrementOperator(node->getOp())) + { + TIntermSequence insertions; + TIntermSymbol *temp1 = + insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions); + TIntermUnary *newUnary = new TIntermUnary(node->getOp(), temp1->deepCopy(), nullptr); + TIntermSymbol *temp2 = insertInitStatementAndReturnTempSymbol(newUnary, &insertions); + TIntermBinary *readBackToSSBO = + new TIntermBinary(EOpAssign, node->getOperand()->deepCopy(), temp1->deepCopy()); + insertions.push_back(readBackToSSBO); + insertStatementsInParentBlock(insertions); + queueReplacement(temp2->deepCopy(), OriginalNode::IS_DROPPED); + } + // case 5: ssbo as the operand of readonly unary operator + // original: + // ~ssbo * expr; + // new: + // var temp = ssbo; + // ~temp * expr; + else + { + TIntermSequence insertions; + TIntermSymbol *temp = + insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions); + insertStatementsInParentBlock(insertions); + node->replaceChildNode(node->getOperand(), temp->deepCopy()); + } + return false; +} + +void RewriteExpressionsWithShaderStorageBlockTraverser::nextIteration() +{ + mFoundSSBO = false; +} + +} // anonymous namespace + +bool RewriteExpressionsWithShaderStorageBlock(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + RewriteExpressionsWithShaderStorageBlockTraverser traverser(symbolTable); + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundSSBO()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundSSBO()); + + return true; +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h new file mode 100644 index 0000000000..bf824c4a23 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h @@ -0,0 +1,37 @@ +// +// Copyright 2018 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. +// +// RewriteExpressionsWithShaderStorageBlock rewrites the expressions that contain shader storage +// block calls into several simple ones that can be easily handled in the HLSL translator. After the +// AST pass, all ssbo related blocks will be like below: +// ssbo_access_chain = ssbo_access_chain; +// ssbo_access_chain = expr_no_ssbo; +// lvalue_no_ssbo = ssbo_access_chain; +// +// Below situations are needed to be rewritten (Details can be found in .cpp file). +// SSBO as the operand of compound assignment operators. +// SSBO as the operand of ++/--. +// SSBO as the operand of repeated assignment. +// SSBO as the operand of readonly unary/binary/ternary operators. +// SSBO as the argument of aggregate type. +// SSBO as the condition of if/switch/while/do-while/for + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RewriteExpressionsWithShaderStorageBlock(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp new file mode 100644 index 0000000000..5d8cdb657d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp @@ -0,0 +1,117 @@ +// +// Copyright 2016 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. +// +// Implementation of evaluating unary integer variable bug workaround. +// See header for more info. + +#include "compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h" + +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class Traverser : public TIntermTraverser +{ + public: + [[nodiscard]] static bool Apply(TCompiler *compiler, TIntermNode *root); + + private: + Traverser(); + bool visitUnary(Visit visit, TIntermUnary *node) override; + void nextIteration(); + + bool mFound = false; +}; + +// static +bool Traverser::Apply(TCompiler *compiler, TIntermNode *root) +{ + Traverser traverser; + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.mFound) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.mFound); + + return true; +} + +Traverser::Traverser() : TIntermTraverser(true, false, false) {} + +void Traverser::nextIteration() +{ + mFound = false; +} + +bool Traverser::visitUnary(Visit visit, TIntermUnary *node) +{ + if (mFound) + { + return false; + } + + // Decide if the current unary operator is unary minus. + if (node->getOp() != EOpNegative) + { + return true; + } + + // Decide if the current operand is an integer variable. + TIntermTyped *opr = node->getOperand(); + if (!opr->getType().isScalarInt()) + { + return true; + } + + // Potential problem case detected, apply workaround: -(int) -> ~(int) + 1. + // ~(int) + TIntermUnary *bitwiseNot = new TIntermUnary(EOpBitwiseNot, opr, nullptr); + bitwiseNot->setLine(opr->getLine()); + + // Constant 1 (or 1u) + TConstantUnion *one = new TConstantUnion(); + if (opr->getType().getBasicType() == EbtInt) + { + one->setIConst(1); + } + else + { + one->setUConst(1u); + } + TType *oneType = new TType(opr->getType()); + oneType->setQualifier(EvqConst); + + TIntermConstantUnion *oneNode = new TIntermConstantUnion(one, *oneType); + oneNode->setLine(opr->getLine()); + + // ~(int) + 1 + TIntermBinary *add = new TIntermBinary(EOpAdd, bitwiseNot, oneNode); + add->setLine(opr->getLine()); + + queueReplacement(add, OriginalNode::IS_DROPPED); + + mFound = true; + return false; +} + +} // anonymous namespace + +bool RewriteUnaryMinusOperatorInt(TCompiler *compiler, TIntermNode *root) +{ + return Traverser::Apply(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h new file mode 100644 index 0000000000..cc5ac86456 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h @@ -0,0 +1,23 @@ +// Copyright 2016 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. +// +// This mutating tree traversal works around a bug on evaluating unary +// integer variable on Intel D3D driver. It works by rewriting -(int) to +// ~(int) + 1 when evaluating unary integer variables. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +[[nodiscard]] bool RewriteUnaryMinusOperatorInt(TCompiler *compiler, TIntermNode *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp new file mode 100644 index 0000000000..170b29acd7 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp @@ -0,0 +1,83 @@ +// +// Copyright 2018 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. +// +// SeparateArrayConstructorStatements splits statements that are array constructors and drops all of +// their constant arguments. For example, a statement like: +// int[2](0, i++); +// Will be changed to: +// i++; + +#include "compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h" + +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +void SplitConstructorArgs(const TIntermSequence &originalArgs, TIntermSequence *argsOut) +{ + for (TIntermNode *arg : originalArgs) + { + TIntermTyped *argTyped = arg->getAsTyped(); + if (argTyped->hasSideEffects()) + { + TIntermAggregate *argAggregate = argTyped->getAsAggregate(); + if (argTyped->isArray() && argAggregate && argAggregate->isConstructor()) + { + SplitConstructorArgs(*argAggregate->getSequence(), argsOut); + } + else + { + argsOut->push_back(argTyped); + } + } + } +} + +class SeparateArrayConstructorStatementsTraverser : public TIntermTraverser +{ + public: + SeparateArrayConstructorStatementsTraverser(); + + bool visitAggregate(Visit visit, TIntermAggregate *node) override; +}; + +SeparateArrayConstructorStatementsTraverser::SeparateArrayConstructorStatementsTraverser() + : TIntermTraverser(true, false, false) +{} + +bool SeparateArrayConstructorStatementsTraverser::visitAggregate(Visit visit, + TIntermAggregate *node) +{ + TIntermBlock *parentAsBlock = getParentNode()->getAsBlock(); + if (!parentAsBlock) + { + return false; + } + if (!node->isArray() || !node->isConstructor()) + { + return false; + } + + TIntermSequence constructorArgs; + SplitConstructorArgs(*node->getSequence(), &constructorArgs); + mMultiReplacements.emplace_back(parentAsBlock, node, std::move(constructorArgs)); + + return false; +} + +} // namespace + +bool SeparateArrayConstructorStatements(TCompiler *compiler, TIntermBlock *root) +{ + SeparateArrayConstructorStatementsTraverser traverser; + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h new file mode 100644 index 0000000000..225a4651da --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h @@ -0,0 +1,25 @@ +// +// Copyright 2018 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. +// +// SeparateArrayConstructorStatements splits statements that are array constructors and drops all of +// their constant arguments. For example, a statement like: +// int[2](0, i++); +// Will be changed to: +// i++; + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; + +[[nodiscard]] bool SeparateArrayConstructorStatements(TCompiler *compiler, TIntermBlock *root); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp new file mode 100644 index 0000000000..de5c393cf6 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp @@ -0,0 +1,89 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The SeparateArrayInitialization function splits each array initialization into a declaration and +// an assignment. +// Example: +// type[n] a = initializer; +// will effectively become +// type[n] a; +// a = initializer; +// +// Note that if the array is declared as const, the initialization may still be split, making the +// AST technically invalid. Because of that this transformation should only be used when subsequent +// stages don't care about const qualifiers. However, the initialization will not be split if the +// initializer can be written as a HLSL literal. + +#include "compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h" + +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/OutputHLSL.h" + +namespace sh +{ + +namespace +{ + +class SeparateArrayInitTraverser : private TIntermTraverser +{ + public: + [[nodiscard]] static bool apply(TCompiler *compiler, TIntermNode *root); + + private: + SeparateArrayInitTraverser(); + bool visitDeclaration(Visit, TIntermDeclaration *node) override; +}; + +bool SeparateArrayInitTraverser::apply(TCompiler *compiler, TIntermNode *root) +{ + SeparateArrayInitTraverser separateInit; + root->traverse(&separateInit); + return separateInit.updateTree(compiler, root); +} + +SeparateArrayInitTraverser::SeparateArrayInitTraverser() : TIntermTraverser(true, false, false) {} + +bool SeparateArrayInitTraverser::visitDeclaration(Visit, TIntermDeclaration *node) +{ + TIntermSequence *sequence = node->getSequence(); + TIntermBinary *initNode = sequence->back()->getAsBinaryNode(); + if (initNode != nullptr && initNode->getOp() == EOpInitialize) + { + TIntermTyped *initializer = initNode->getRight(); + if (initializer->isArray() && !initializer->hasConstantValue()) + { + // We rely on that array declarations have been isolated to single declarations. + ASSERT(sequence->size() == 1); + TIntermTyped *symbol = initNode->getLeft(); + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + ASSERT(parentBlock != nullptr); + + TIntermSequence replacements; + + TIntermDeclaration *replacementDeclaration = new TIntermDeclaration(); + replacementDeclaration->appendDeclarator(symbol); + replacementDeclaration->setLine(symbol->getLine()); + replacements.push_back(replacementDeclaration); + + TIntermBinary *replacementAssignment = + new TIntermBinary(EOpAssign, symbol, initializer); + replacementAssignment->setLine(symbol->getLine()); + replacements.push_back(replacementAssignment); + + mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements)); + } + } + return false; +} + +} // namespace + +bool SeparateArrayInitialization(TCompiler *compiler, TIntermNode *root) +{ + return SeparateArrayInitTraverser::apply(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h new file mode 100644 index 0000000000..e83554186a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h @@ -0,0 +1,32 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The SeparateArrayInitialization function splits each array initialization into a declaration and +// an assignment. +// Example: +// type[n] a = initializer; +// will effectively become +// type[n] a; +// a = initializer; +// +// Note that if the array is declared as const, the initialization may still be split, making the +// AST technically invalid. Because of that this transformation should only be used when subsequent +// stages don't care about const qualifiers. However, the initialization will not be split if the +// initializer can be written as a HLSL literal. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +[[nodiscard]] bool SeparateArrayInitialization(TCompiler *compiler, TIntermNode *root); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp new file mode 100644 index 0000000000..58d8a0a9be --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp @@ -0,0 +1,138 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// SeparateExpressionsReturningArrays splits array-returning expressions that are not array names +// from more complex expressions, assigning them to a temporary variable a#. +// Examples where a, b and c are all arrays: +// (a = b) == (a = c) is split into a = b; type[n] a1 = a; a = c; type[n] a2 = a; a1 == a2; +// type d = type[n](...)[i]; is split into type[n] a1 = type[n](...); type d = a1[i]; + +#include "compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h" + +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// Traverser that separates one array expression into a statement at a time. +class SeparateExpressionsTraverser : public TIntermTraverser +{ + public: + SeparateExpressionsTraverser(TSymbolTable *symbolTable); + + bool visitBinary(Visit visit, TIntermBinary *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + + void nextIteration(); + bool foundArrayExpression() const { return mFoundArrayExpression; } + + protected: + // Marked to true once an operation that needs to be hoisted out of the expression has been + // found. After that, no more AST updates are performed on that traversal. + bool mFoundArrayExpression; + + IntermNodePatternMatcher mPatternToSeparateMatcher; +}; + +SeparateExpressionsTraverser::SeparateExpressionsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable), + mFoundArrayExpression(false), + mPatternToSeparateMatcher(IntermNodePatternMatcher::kExpressionReturningArray) +{} + +// Performs a shallow copy of an assignment node. +// These shallow copies are useful when a node gets inserted into an aggregate node +// and also needs to be replaced in its original location by a different node. +TIntermBinary *CopyAssignmentNode(TIntermBinary *node) +{ + return new TIntermBinary(node->getOp(), node->getLeft(), node->getRight()); +} + +bool SeparateExpressionsTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (mFoundArrayExpression) + return false; + + // Return if the expression is not an array or if we're not inside a complex expression. + if (!mPatternToSeparateMatcher.match(node, getParentNode())) + return true; + + ASSERT(node->getOp() == EOpAssign); + + mFoundArrayExpression = true; + + TIntermSequence insertions; + insertions.push_back(CopyAssignmentNode(node)); + // TODO(oetuaho): In some cases it would be more optimal to not add the temporary node, but just + // use the original target of the assignment. Care must be taken so that this doesn't happen + // when the same array symbol is a target of assignment more than once in one expression. + TIntermDeclaration *arrayVariableDeclaration; + TVariable *arrayVariable = + DeclareTempVariable(mSymbolTable, node->getLeft(), EvqTemporary, &arrayVariableDeclaration); + insertions.push_back(arrayVariableDeclaration); + insertStatementsInParentBlock(insertions); + + queueReplacement(CreateTempSymbolNode(arrayVariable), OriginalNode::IS_DROPPED); + + return false; +} + +bool SeparateExpressionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (mFoundArrayExpression) + return false; // No need to traverse further + + if (!mPatternToSeparateMatcher.match(node, getParentNode())) + return true; + + ASSERT(node->isConstructor() || node->getOp() == EOpCallFunctionInAST); + + mFoundArrayExpression = true; + + TIntermDeclaration *arrayVariableDeclaration; + TVariable *arrayVariable = DeclareTempVariable(mSymbolTable, node->shallowCopy(), EvqTemporary, + &arrayVariableDeclaration); + insertStatementInParentBlock(arrayVariableDeclaration); + + queueReplacement(CreateTempSymbolNode(arrayVariable), OriginalNode::IS_DROPPED); + + return false; +} + +void SeparateExpressionsTraverser::nextIteration() +{ + mFoundArrayExpression = false; +} + +} // namespace + +bool SeparateExpressionsReturningArrays(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + SeparateExpressionsTraverser traverser(symbolTable); + // Separate one expression at a time, and reset the traverser between iterations. + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundArrayExpression()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundArrayExpression()); + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h new file mode 100644 index 0000000000..6a9350d89a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h @@ -0,0 +1,28 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// SeparateExpressionsReturningArrays splits array-returning expressions that are not array names +// from more complex expressions, assigning them to a temporary variable a#. +// Examples where a, b and c are all arrays: +// (a = b) == (a = c) is split into a = b; type[n] a1 = a; a = c; type[n] a2 = a; a1 == a2; +// type d = type[n](...)[i]; is split into type[n] a1 = type[n](...); type d = a1[i]; + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool SeparateExpressionsReturningArrays(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp new file mode 100644 index 0000000000..5b104c4889 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp @@ -0,0 +1,200 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// UnfoldShortCircuitToIf is an AST traverser to convert short-circuiting operators to if-else +// statements. +// The results are assigned to s# temporaries, which are used by the main translator instead of +// the original expression. +// + +#include "compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h" + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// Traverser that unfolds one short-circuiting operation at a time. +class UnfoldShortCircuitTraverser : public TIntermTraverser +{ + public: + UnfoldShortCircuitTraverser(TSymbolTable *symbolTable); + + bool visitBinary(Visit visit, TIntermBinary *node) override; + bool visitTernary(Visit visit, TIntermTernary *node) override; + + void nextIteration(); + bool foundShortCircuit() const { return mFoundShortCircuit; } + + protected: + // Marked to true once an operation that needs to be unfolded has been found. + // After that, no more unfolding is performed on that traversal. + bool mFoundShortCircuit; + + IntermNodePatternMatcher mPatternToUnfoldMatcher; +}; + +UnfoldShortCircuitTraverser::UnfoldShortCircuitTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable), + mFoundShortCircuit(false), + mPatternToUnfoldMatcher(IntermNodePatternMatcher::kUnfoldedShortCircuitExpression) +{} + +bool UnfoldShortCircuitTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (mFoundShortCircuit) + return false; + + if (visit != PreVisit) + return true; + + if (!mPatternToUnfoldMatcher.match(node, getParentNode())) + return true; + + // If our right node doesn't have side effects, we know we don't need to unfold this + // expression: there will be no short-circuiting side effects to avoid + // (note: unfolding doesn't depend on the left node -- it will always be evaluated) + ASSERT(node->getRight()->hasSideEffects()); + + mFoundShortCircuit = true; + + switch (node->getOp()) + { + case EOpLogicalOr: + { + // "x || y" is equivalent to "x ? true : y", which unfolds to "bool s; if(x) s = true; + // else s = y;", + // and then further simplifies down to "bool s = x; if(!s) s = y;". + + TIntermSequence insertions; + const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>(); + TVariable *resultVariable = CreateTempVariable(mSymbolTable, boolType); + + ASSERT(node->getLeft()->getType() == *boolType); + insertions.push_back(CreateTempInitDeclarationNode(resultVariable, node->getLeft())); + + TIntermBlock *assignRightBlock = new TIntermBlock(); + ASSERT(node->getRight()->getType() == *boolType); + assignRightBlock->getSequence()->push_back( + CreateTempAssignmentNode(resultVariable, node->getRight())); + + TIntermUnary *notTempSymbol = + new TIntermUnary(EOpLogicalNot, CreateTempSymbolNode(resultVariable), nullptr); + TIntermIfElse *ifNode = new TIntermIfElse(notTempSymbol, assignRightBlock, nullptr); + insertions.push_back(ifNode); + + insertStatementsInParentBlock(insertions); + + queueReplacement(CreateTempSymbolNode(resultVariable), OriginalNode::IS_DROPPED); + return false; + } + case EOpLogicalAnd: + { + // "x && y" is equivalent to "x ? y : false", which unfolds to "bool s; if(x) s = y; + // else s = false;", + // and then further simplifies down to "bool s = x; if(s) s = y;". + TIntermSequence insertions; + const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>(); + TVariable *resultVariable = CreateTempVariable(mSymbolTable, boolType); + + ASSERT(node->getLeft()->getType() == *boolType); + insertions.push_back(CreateTempInitDeclarationNode(resultVariable, node->getLeft())); + + TIntermBlock *assignRightBlock = new TIntermBlock(); + ASSERT(node->getRight()->getType() == *boolType); + assignRightBlock->getSequence()->push_back( + CreateTempAssignmentNode(resultVariable, node->getRight())); + + TIntermIfElse *ifNode = + new TIntermIfElse(CreateTempSymbolNode(resultVariable), assignRightBlock, nullptr); + insertions.push_back(ifNode); + + insertStatementsInParentBlock(insertions); + + queueReplacement(CreateTempSymbolNode(resultVariable), OriginalNode::IS_DROPPED); + return false; + } + default: + UNREACHABLE(); + return true; + } +} + +bool UnfoldShortCircuitTraverser::visitTernary(Visit visit, TIntermTernary *node) +{ + if (mFoundShortCircuit) + return false; + + if (visit != PreVisit) + return true; + + if (!mPatternToUnfoldMatcher.match(node)) + return true; + + mFoundShortCircuit = true; + + // Unfold "b ? x : y" into "type s; if(b) s = x; else s = y;" + TIntermSequence insertions; + TIntermDeclaration *tempDeclaration = nullptr; + TVariable *resultVariable = DeclareTempVariable(mSymbolTable, new TType(node->getType()), + EvqTemporary, &tempDeclaration); + insertions.push_back(tempDeclaration); + + TIntermBlock *trueBlock = new TIntermBlock(); + TIntermBinary *trueAssignment = + CreateTempAssignmentNode(resultVariable, node->getTrueExpression()); + trueBlock->getSequence()->push_back(trueAssignment); + + TIntermBlock *falseBlock = new TIntermBlock(); + TIntermBinary *falseAssignment = + CreateTempAssignmentNode(resultVariable, node->getFalseExpression()); + falseBlock->getSequence()->push_back(falseAssignment); + + TIntermIfElse *ifNode = + new TIntermIfElse(node->getCondition()->getAsTyped(), trueBlock, falseBlock); + insertions.push_back(ifNode); + + insertStatementsInParentBlock(insertions); + + TIntermSymbol *ternaryResult = CreateTempSymbolNode(resultVariable); + queueReplacement(ternaryResult, OriginalNode::IS_DROPPED); + + return false; +} + +void UnfoldShortCircuitTraverser::nextIteration() +{ + mFoundShortCircuit = false; +} + +} // namespace + +bool UnfoldShortCircuitToIf(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + UnfoldShortCircuitTraverser traverser(symbolTable); + // Unfold one operator at a time, and reset the traverser between iterations. + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundShortCircuit()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundShortCircuit()); + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h new file mode 100644 index 0000000000..97587e4d38 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h @@ -0,0 +1,30 @@ +// +// Copyright 2002 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// UnfoldShortCircuitToIf is an AST traverser to convert short-circuiting operators to if-else +// statements. +// The results are assigned to s# temporaries, which are used by the main translator instead of +// the original expression. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool UnfoldShortCircuitToIf(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp new file mode 100644 index 0000000000..58a941da0e --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp @@ -0,0 +1,126 @@ +// +// Copyright 2017 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. +// +// WrapSwitchStatementsInBlocks.cpp: Wrap switch statements in blocks and declare all switch-scoped +// variables there to make the AST compatible with HLSL output. +// +// switch (init) +// { +// case 0: +// float f; +// default: +// f = 1.0; +// } +// +// becomes +// +// { +// float f; +// switch (init) +// { +// case 0: +// default: +// f = 1.0; +// } +// } + +#include "compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h" + +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class WrapSwitchStatementsInBlocksTraverser : public TIntermTraverser +{ + public: + WrapSwitchStatementsInBlocksTraverser() : TIntermTraverser(true, false, false) {} + + bool visitSwitch(Visit visit, TIntermSwitch *node) override; +}; + +bool WrapSwitchStatementsInBlocksTraverser::visitSwitch(Visit, TIntermSwitch *node) +{ + std::vector<TIntermDeclaration *> declarations; + TIntermSequence *statementList = node->getStatementList()->getSequence(); + for (TIntermNode *statement : *statementList) + { + TIntermDeclaration *asDeclaration = statement->getAsDeclarationNode(); + if (asDeclaration) + { + declarations.push_back(asDeclaration); + } + } + if (declarations.empty()) + { + // We don't need to wrap the switch if it doesn't contain declarations as its direct + // descendants. + return true; + } + + TIntermBlock *wrapperBlock = new TIntermBlock(); + for (TIntermDeclaration *declaration : declarations) + { + // SeparateDeclarations should have already been run. + ASSERT(declaration->getSequence()->size() == 1); + + TIntermDeclaration *declarationInBlock = new TIntermDeclaration(); + TIntermSymbol *declaratorAsSymbol = declaration->getSequence()->at(0)->getAsSymbolNode(); + if (declaratorAsSymbol) + { + // This is a simple declaration like: "float f;" + // Remove the declaration from inside the switch and put it in the wrapping block. + TIntermSequence emptyReplacement; + mMultiReplacements.emplace_back(node->getStatementList(), declaration, + std::move(emptyReplacement)); + + declarationInBlock->appendDeclarator(declaratorAsSymbol->deepCopy()); + // The declaration can't be the last statement inside the switch since unused variables + // should already have been pruned. + ASSERT(declaration != statementList->back()); + } + else + { + // This is an init declaration like: "float f = 0.0;" + // Change the init declaration inside the switch into an assignment and put a plain + // declaration in the wrapping block. + TIntermBinary *declaratorAsBinary = + declaration->getSequence()->at(0)->getAsBinaryNode(); + ASSERT(declaratorAsBinary); + + TIntermBinary *initAssignment = new TIntermBinary( + EOpAssign, declaratorAsBinary->getLeft(), declaratorAsBinary->getRight()); + + queueReplacementWithParent(node->getStatementList(), declaration, initAssignment, + OriginalNode::IS_DROPPED); + + declarationInBlock->appendDeclarator(declaratorAsBinary->getLeft()->deepCopy()); + } + wrapperBlock->appendStatement(declarationInBlock); + } + + wrapperBlock->appendStatement(node); + queueReplacement(wrapperBlock, OriginalNode::BECOMES_CHILD); + + // Should be fine to process multiple switch statements, even nesting ones in the same + // traversal. + return true; +} + +} // anonymous namespace + +// Wrap switch statements in the AST into blocks when needed. +bool WrapSwitchStatementsInBlocks(TCompiler *compiler, TIntermBlock *root) +{ + WrapSwitchStatementsInBlocksTraverser traverser; + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h new file mode 100644 index 0000000000..0c765c306a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h @@ -0,0 +1,25 @@ +// +// Copyright 2017 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. +// +// WrapSwitchStatementsInBlocks.h: Wrap switch statements in blocks and declare all switch-scoped +// variables there to make the AST compatible with HLSL output. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; + +// Wrap switch statements in the AST into blocks when needed. Returns true if the AST was changed. +[[nodiscard]] bool WrapSwitchStatementsInBlocks(TCompiler *compiler, TIntermBlock *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_ |