Remove dynamic indexing of matrices and vectors in HLSL
HLSL doesn't support dynamic indexing of matrices and vectors, so replace
that with helper functions that unroll dynamic indexing into switch/case
and static indexing.
Both the indexed vector/matrix expression and the index may have side
effects, and these will be evaluated correctly. If necessary, index
expressions that have side effects will be written to a temporary
variable that will replace the index.
Besides dEQP tests, this change is tested by a WebGL 2 conformance test.
In the case that a dynamic index is out-of-range, the base ESSL 3.00 spec
allows undefined behavior. KHR_robust_buffer_access_behavior adds the
requirement that program termination should not occur and that
out-of-range reads must return either a value from the active program's
memory or zero, and out-of-range writes should only affect the active
program's memory or do nothing. This patch clamps out-of-range indices so
that either the first or last item of the matrix/vector is accessed.
The code is not transformed in case the it fits within the limited subset
of ESSL 1.00 given in Appendix A of the spec. If the code isn't within
the restricted subset, even ESSL 1.00 shaders may require this
workaround.
BUG=angleproject:1116
TEST=dEQP-GLES3.functional.shaders.indexing.* (all pass after change)
WebGL 2 conformance tests (glsl3/vector-dynamic-indexing.html)
Change-Id: I024722ef4ca1e14d5ad47fdc540397e18858bed6
Reviewed-on: https://chromium-review.googlesource.com/290515
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index b61fb6d..5994ede 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -151,6 +151,15 @@
{
}
+bool TCompiler::shouldRunLoopAndIndexingValidation(int compileOptions) const
+{
+ // If compiling an ESSL 1.00 shader for WebGL, or if its been requested through the API,
+ // validate loop and indexing as well (to verify that the shader only uses minimal functionality
+ // of ESSL 1.00 as in Appendix A of the spec).
+ return (IsWebGLBasedSpec(shaderSpec) && shaderVersion == 100) ||
+ (compileOptions & SH_VALIDATE_LOOP_INDEXING);
+}
+
bool TCompiler::Init(const ShBuiltInResources& resources)
{
shaderVersion = 100;
@@ -228,12 +237,6 @@
success = false;
}
- // If compiling an ESSL 1.00 shader for WebGL, or if its been requested through the API,
- // validate loop and indexing as well (to verify that the shader only uses minimal functionality
- // of ESSL 1.00 as in Appendix A of the spec).
- bool validateLoopAndIndexing = (IsWebGLBasedSpec(shaderSpec) && shaderVersion == 100) ||
- (compileOptions & SH_VALIDATE_LOOP_INDEXING);
-
TIntermNode *root = nullptr;
if (success)
@@ -276,7 +279,7 @@
if (success && shaderVersion == 300 && shaderType == GL_FRAGMENT_SHADER)
success = validateOutputs(root);
- if (success && validateLoopAndIndexing)
+ if (success && shouldRunLoopAndIndexingValidation(compileOptions))
success = validateLimitations(root);
if (success && (compileOptions & SH_TIMING_RESTRICTIONS))
diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h
index cedb20c..cf49f47 100644
--- a/src/compiler/translator/Compiler.h
+++ b/src/compiler/translator/Compiler.h
@@ -101,6 +101,8 @@
ShShaderOutput getOutputType() const { return outputType; }
const std::string &getBuiltInResourcesString() const { return builtInResourcesString; }
+ bool shouldRunLoopAndIndexingValidation(int compileOptions) const;
+
// Get the resources set by InitBuiltInSymbolTable
const ShBuiltInResources& getResources() const;
diff --git a/src/compiler/translator/IntermNode.cpp b/src/compiler/translator/IntermNode.cpp
index 187ad14..e9665f9 100644
--- a/src/compiler/translator/IntermNode.cpp
+++ b/src/compiler/translator/IntermNode.cpp
@@ -311,17 +311,13 @@
bool TIntermAggregate::insertChildNodes(TIntermSequence::size_type position, TIntermSequence insertions)
{
- TIntermSequence::size_type itPosition = 0;
- for (auto it = mSequence.begin(); it < mSequence.end(); ++it)
+ if (position > mSequence.size())
{
- if (itPosition == position)
- {
- mSequence.insert(it, insertions.begin(), insertions.end());
- return true;
- }
- ++itPosition;
+ return false;
}
- return false;
+ auto it = mSequence.begin() + position;
+ mSequence.insert(it, insertions.begin(), insertions.end());
+ return true;
}
void TIntermAggregate::setPrecisionFromChildren()
@@ -2550,9 +2546,20 @@
{
const NodeInsertMultipleEntry &insertion = mInsertions[ii];
ASSERT(insertion.parent);
- bool inserted = insertion.parent->insertChildNodes(insertion.position, insertion.insertions);
- ASSERT(inserted);
- UNUSED_ASSERTION_VARIABLE(inserted);
+ if (!insertion.insertionsAfter.empty())
+ {
+ bool inserted = insertion.parent->insertChildNodes(insertion.position + 1,
+ insertion.insertionsAfter);
+ ASSERT(inserted);
+ UNUSED_ASSERTION_VARIABLE(inserted);
+ }
+ if (!insertion.insertionsBefore.empty())
+ {
+ bool inserted =
+ insertion.parent->insertChildNodes(insertion.position, insertion.insertionsBefore);
+ ASSERT(inserted);
+ UNUSED_ASSERTION_VARIABLE(inserted);
+ }
}
for (size_t ii = 0; ii < mReplacements.size(); ++ii)
{
diff --git a/src/compiler/translator/IntermNode.h b/src/compiler/translator/IntermNode.h
index 8135ae8..5671c5a 100644
--- a/src/compiler/translator/IntermNode.h
+++ b/src/compiler/translator/IntermNode.h
@@ -798,16 +798,21 @@
// To insert multiple nodes on the parent aggregate node
struct NodeInsertMultipleEntry
{
- NodeInsertMultipleEntry(TIntermAggregate *_parent, TIntermSequence::size_type _position, TIntermSequence _insertions)
+ NodeInsertMultipleEntry(TIntermAggregate *_parent,
+ TIntermSequence::size_type _position,
+ TIntermSequence _insertionsBefore,
+ TIntermSequence _insertionsAfter)
: parent(_parent),
- position(_position),
- insertions(_insertions)
+ position(_position),
+ insertionsBefore(_insertionsBefore),
+ insertionsAfter(_insertionsAfter)
{
}
TIntermAggregate *parent;
TIntermSequence::size_type position;
- TIntermSequence insertions;
+ TIntermSequence insertionsBefore;
+ TIntermSequence insertionsAfter;
};
// During traversing, save all the changes that need to happen into
@@ -824,6 +829,11 @@
// supported.
void insertStatementsInParentBlock(const TIntermSequence &insertions);
+ // Same as above, but supports simultaneous insertion of statements before and after the node
+ // currently being traversed.
+ void insertStatementsInParentBlock(const TIntermSequence &insertionsBefore,
+ const TIntermSequence &insertionsAfter);
+
// Helper to create a temporary symbol node with the given qualifier.
TIntermSymbol *createTempSymbol(const TType &type, TQualifier qualifier);
// Helper to create a temporary symbol node.
diff --git a/src/compiler/translator/IntermTraverse.cpp b/src/compiler/translator/IntermTraverse.cpp
index f07a3e0..269a19b 100644
--- a/src/compiler/translator/IntermTraverse.cpp
+++ b/src/compiler/translator/IntermTraverse.cpp
@@ -81,8 +81,16 @@
void TIntermTraverser::insertStatementsInParentBlock(const TIntermSequence &insertions)
{
+ TIntermSequence emptyInsertionsAfter;
+ insertStatementsInParentBlock(insertions, emptyInsertionsAfter);
+}
+
+void TIntermTraverser::insertStatementsInParentBlock(const TIntermSequence &insertionsBefore,
+ const TIntermSequence &insertionsAfter)
+{
ASSERT(!mParentBlockStack.empty());
- NodeInsertMultipleEntry insert(mParentBlockStack.back().node, mParentBlockStack.back().pos, insertions);
+ NodeInsertMultipleEntry insert(mParentBlockStack.back().node, mParentBlockStack.back().pos,
+ insertionsBefore, insertionsAfter);
mInsertions.push_back(insert);
}
diff --git a/src/compiler/translator/RemoveDynamicIndexing.cpp b/src/compiler/translator/RemoveDynamicIndexing.cpp
new file mode 100644
index 0000000..5586f85
--- /dev/null
+++ b/src/compiler/translator/RemoveDynamicIndexing.cpp
@@ -0,0 +1,489 @@
+//
+// Copyright (c) 2002-2015 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.
+//
+// RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of vectors and matrices,
+// replacing them with calls to functions that choose which component to return or write.
+//
+
+#include "compiler/translator/RemoveDynamicIndexing.h"
+
+#include "compiler/translator/InfoSink.h"
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/SymbolTable.h"
+
+namespace
+{
+
+TName GetIndexFunctionName(const TType &type, bool write)
+{
+ TInfoSinkBase nameSink;
+ nameSink << "dyn_index_";
+ if (write)
+ {
+ nameSink << "write_";
+ }
+ if (type.isMatrix())
+ {
+ nameSink << "mat" << type.getCols() << "x" << type.getRows();
+ }
+ else
+ {
+ switch (type.getBasicType())
+ {
+ case EbtInt:
+ nameSink << "ivec";
+ break;
+ case EbtBool:
+ nameSink << "bvec";
+ break;
+ case EbtUInt:
+ nameSink << "uvec";
+ break;
+ case EbtFloat:
+ nameSink << "vec";
+ break;
+ default:
+ UNREACHABLE();
+ }
+ nameSink << type.getNominalSize();
+ }
+ TString nameString = TFunction::mangleName(nameSink.c_str());
+ TName name(nameString);
+ name.setInternal(true);
+ return name;
+}
+
+TIntermSymbol *CreateBaseSymbol(const TType &type)
+{
+ TIntermSymbol *symbol = new TIntermSymbol(0, "base", type);
+ symbol->setInternal(true);
+ return symbol;
+}
+
+TIntermSymbol *CreateIndexSymbol()
+{
+ TIntermSymbol *symbol = new TIntermSymbol(0, "index", TType(EbtInt, EbpHigh));
+ symbol->setInternal(true);
+ return symbol;
+}
+
+TIntermSymbol *CreateValueSymbol(const TType &type)
+{
+ TIntermSymbol *symbol = new TIntermSymbol(0, "value", type);
+ symbol->setInternal(true);
+ return symbol;
+}
+
+TIntermConstantUnion *CreateIntConstantNode(int i)
+{
+ TConstantUnion *constant = new TConstantUnion();
+ constant->setIConst(i);
+ return new TIntermConstantUnion(constant, TType(EbtInt, EbpHigh));
+}
+
+TIntermBinary *CreateIndexDirectBaseSymbolNode(const TType &indexedType,
+ const TType &fieldType,
+ const int index)
+{
+ TIntermBinary *indexNode = new TIntermBinary(EOpIndexDirect);
+ indexNode->setType(fieldType);
+ indexNode->setLeft(CreateBaseSymbol(indexedType));
+ indexNode->setRight(CreateIntConstantNode(index));
+ return indexNode;
+}
+
+TIntermBinary *CreateAssignValueSymbolNode(TIntermTyped *targetNode, const TType &assignedValueType)
+{
+ TIntermBinary *assignNode = new TIntermBinary(EOpAssign);
+ assignNode->setType(assignedValueType);
+ assignNode->setLeft(targetNode);
+ assignNode->setRight(CreateValueSymbol(assignedValueType));
+ return assignNode;
+}
+
+TIntermTyped *EnsureSignedInt(TIntermTyped *node)
+{
+ if (node->getBasicType() == EbtInt)
+ return node;
+
+ TIntermAggregate *convertedNode = new TIntermAggregate(EOpConstructInt);
+ convertedNode->setType(TType(EbtInt));
+ convertedNode->getSequence()->push_back(node);
+ convertedNode->setPrecisionFromChildren();
+ return convertedNode;
+}
+
+TType GetFieldType(const TType &indexedType)
+{
+ if (indexedType.isMatrix())
+ {
+ TType fieldType = TType(indexedType.getBasicType(), indexedType.getPrecision());
+ fieldType.setPrimarySize(unsigned char(indexedType.getRows()));
+ return fieldType;
+ }
+ else
+ {
+ return TType(indexedType.getBasicType(), indexedType.getPrecision());
+ }
+}
+
+// Generate a read or write function for one field in a vector/matrix.
+// Out-of-range indices are clamped. This is consistent with how ANGLE handles out-of-range
+// indices in other places.
+// Note that indices can be either int or uint. We create only int versions of the functions,
+// and convert uint indices to int at the call site.
+// read function example:
+// float dyn_index_vec2(in vec2 base, in int index) {
+// switch(index) {
+// case (0):
+// return base[0];
+// case (1):
+// return base[1];
+// default:
+// if (index < 0)
+// return base[0];
+// else
+// return base[1];
+// }
+// }
+// write function example:
+// void dyn_index_write_vec2(inout vec2 base, in int index, in float value) {
+// switch(index) {
+// case (0):
+// base[0] = value;
+// break;
+// case (1):
+// base[1] = value;
+// break;
+// default:
+// if (index < 0)
+// base[0] = value;
+// else
+// base[1] = value;
+// break;
+// }
+// }
+TIntermAggregate *GetIndexFunctionDefinition(TType type, bool write)
+{
+ ASSERT(!type.isArray());
+ // Conservatively use highp here, even if the indexed type is not highp. That way the code can't
+ // end up using mediump version of an indexing function for a highp value, if both mediump and
+ // highp values are being indexed in the shader. For HLSL precision doesn't matter, but in
+ // principle this code could be used with multiple backends.
+ type.setPrecision(EbpHigh);
+ TIntermAggregate *indexingFunction = new TIntermAggregate(EOpFunction);
+ indexingFunction->setNameObj(GetIndexFunctionName(type, write));
+
+ TType fieldType = GetFieldType(type);
+ int numCases = 0;
+ if (type.isMatrix())
+ {
+ numCases = type.getCols();
+ }
+ else
+ {
+ numCases = type.getNominalSize();
+ }
+ if (write)
+ {
+ indexingFunction->setType(TType(EbtVoid));
+ }
+ else
+ {
+ indexingFunction->setType(fieldType);
+ }
+
+ TIntermAggregate *paramsNode = new TIntermAggregate(EOpParameters);
+ TIntermSymbol *baseParam = CreateBaseSymbol(type);
+ if (write)
+ baseParam->getTypePointer()->setQualifier(EvqInOut);
+ else
+ baseParam->getTypePointer()->setQualifier(EvqIn);
+ paramsNode->getSequence()->push_back(baseParam);
+ TIntermSymbol *indexParam = CreateIndexSymbol();
+ indexParam->getTypePointer()->setQualifier(EvqIn);
+ paramsNode->getSequence()->push_back(indexParam);
+ if (write)
+ {
+ TIntermSymbol *valueParam = CreateValueSymbol(fieldType);
+ valueParam->getTypePointer()->setQualifier(EvqIn);
+ paramsNode->getSequence()->push_back(valueParam);
+ }
+ indexingFunction->getSequence()->push_back(paramsNode);
+
+ TIntermAggregate *statementList = new TIntermAggregate(EOpSequence);
+ for (int i = 0; i < numCases; ++i)
+ {
+ TIntermCase *caseNode = new TIntermCase(CreateIntConstantNode(i));
+ statementList->getSequence()->push_back(caseNode);
+
+ TIntermBinary *indexNode = CreateIndexDirectBaseSymbolNode(type, fieldType, i);
+ if (write)
+ {
+ TIntermBinary *assignNode = CreateAssignValueSymbolNode(indexNode, fieldType);
+ statementList->getSequence()->push_back(assignNode);
+ TIntermBranch *breakNode = new TIntermBranch(EOpBreak, nullptr);
+ statementList->getSequence()->push_back(breakNode);
+ }
+ else
+ {
+ TIntermBranch *returnNode = new TIntermBranch(EOpReturn, indexNode);
+ statementList->getSequence()->push_back(returnNode);
+ }
+ }
+
+ // Default case
+ TIntermCase *defaultNode = new TIntermCase(nullptr);
+ statementList->getSequence()->push_back(defaultNode);
+ TIntermBinary *cond = new TIntermBinary(EOpLessThan);
+ cond->setType(TType(EbtBool, EbpUndefined));
+ cond->setLeft(CreateIndexSymbol());
+ cond->setRight(CreateIntConstantNode(0));
+ TIntermAggregate *trueBlock = new TIntermAggregate(EOpSequence);
+ TIntermAggregate *falseBlock = new TIntermAggregate(EOpSequence);
+ TIntermBinary *indexFirstNode = CreateIndexDirectBaseSymbolNode(type, fieldType, 0);
+ TIntermBinary *indexLastNode = CreateIndexDirectBaseSymbolNode(type, fieldType, numCases - 1);
+ if (write)
+ {
+ TIntermBinary *assignFirstNode = CreateAssignValueSymbolNode(indexFirstNode, fieldType);
+ trueBlock->getSequence()->push_back(assignFirstNode);
+ TIntermBinary *assignLastNode = CreateAssignValueSymbolNode(indexLastNode, fieldType);
+ falseBlock->getSequence()->push_back(assignLastNode);
+ }
+ else
+ {
+ TIntermBranch *returnFirstNode = new TIntermBranch(EOpReturn, indexFirstNode);
+ trueBlock->getSequence()->push_back(returnFirstNode);
+
+ TIntermBranch *returnLastNode = new TIntermBranch(EOpReturn, indexLastNode);
+ falseBlock->getSequence()->push_back(returnLastNode);
+ }
+ TIntermSelection *ifNode = new TIntermSelection(cond, trueBlock, falseBlock);
+ statementList->getSequence()->push_back(ifNode);
+ TIntermSwitch *switchNode = new TIntermSwitch(CreateIndexSymbol(), statementList);
+
+ TIntermAggregate *bodyNode = new TIntermAggregate(EOpSequence);
+ bodyNode->getSequence()->push_back(switchNode);
+ indexingFunction->getSequence()->push_back(bodyNode);
+
+ return indexingFunction;
+}
+
+class RemoveDynamicIndexingTraverser : public TLValueTrackingTraverser
+{
+ public:
+ RemoveDynamicIndexingTraverser(const TSymbolTable &symbolTable, int shaderVersion);
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+
+ void insertHelperDefinitions(TIntermNode *root);
+
+ void nextIteration();
+
+ bool usedTreeInsertion() const { return mUsedTreeInsertion; }
+
+ protected:
+ // Sets of types that are indexed. Note that these can not store multiple variants
+ // of the same type with different precisions - only one precision gets stored.
+ std::set<TType> mIndexedVecAndMatrixTypes;
+ std::set<TType> mWrittenVecAndMatrixTypes;
+
+ bool mUsedTreeInsertion;
+
+ // When true, the traverser will remove side effects from any indexing expression.
+ // This is done so that in code like
+ // V[j++][i]++.
+ // where V is an array of vectors, j++ will only be evaluated once.
+ bool mRemoveIndexSideEffectsInSubtree;
+};
+
+RemoveDynamicIndexingTraverser::RemoveDynamicIndexingTraverser(const TSymbolTable &symbolTable,
+ int shaderVersion)
+ : TLValueTrackingTraverser(true, false, false, symbolTable, shaderVersion),
+ mUsedTreeInsertion(false),
+ mRemoveIndexSideEffectsInSubtree(false)
+{
+}
+
+void RemoveDynamicIndexingTraverser::insertHelperDefinitions(TIntermNode *root)
+{
+ TIntermAggregate *rootAgg = root->getAsAggregate();
+ ASSERT(rootAgg != nullptr && rootAgg->getOp() == EOpSequence);
+ TIntermSequence insertions;
+ for (TType type : mIndexedVecAndMatrixTypes)
+ {
+ insertions.push_back(GetIndexFunctionDefinition(type, false));
+ }
+ for (TType type : mWrittenVecAndMatrixTypes)
+ {
+ insertions.push_back(GetIndexFunctionDefinition(type, true));
+ }
+ mInsertions.push_back(NodeInsertMultipleEntry(rootAgg, 0, insertions, TIntermSequence()));
+}
+
+// Create a call to dyn_index_*() based on an indirect indexing op node
+TIntermAggregate *CreateIndexFunctionCall(TIntermBinary *node,
+ TIntermTyped *indexedNode,
+ TIntermTyped *index)
+{
+ ASSERT(node->getOp() == EOpIndexIndirect);
+ TIntermAggregate *indexingCall = new TIntermAggregate(EOpFunctionCall);
+ indexingCall->setLine(node->getLine());
+ indexingCall->setUserDefined();
+ indexingCall->setNameObj(GetIndexFunctionName(indexedNode->getType(), false));
+ indexingCall->getSequence()->push_back(indexedNode);
+ indexingCall->getSequence()->push_back(index);
+
+ TType fieldType = GetFieldType(indexedNode->getType());
+ indexingCall->setType(fieldType);
+ return indexingCall;
+}
+
+TIntermAggregate *CreateIndexedWriteFunctionCall(TIntermBinary *node,
+ TIntermTyped *index,
+ TIntermTyped *writtenValue)
+{
+ // Deep copy the left node so that two pointers to the same node don't end up in the tree.
+ TIntermNode *leftCopy = node->getLeft()->deepCopy();
+ ASSERT(leftCopy != nullptr && leftCopy->getAsTyped() != nullptr);
+ TIntermAggregate *indexedWriteCall =
+ CreateIndexFunctionCall(node, leftCopy->getAsTyped(), index);
+ indexedWriteCall->setNameObj(GetIndexFunctionName(node->getLeft()->getType(), true));
+ indexedWriteCall->setType(TType(EbtVoid));
+ indexedWriteCall->getSequence()->push_back(writtenValue);
+ return indexedWriteCall;
+}
+
+bool RemoveDynamicIndexingTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ if (mUsedTreeInsertion)
+ return false;
+
+ if (node->getOp() == EOpIndexIndirect)
+ {
+ if (mRemoveIndexSideEffectsInSubtree)
+ {
+ ASSERT(node->getRight()->hasSideEffects());
+ // In case we're just removing index side effects, convert
+ // v_expr[index_expr]
+ // to this:
+ // int s0 = index_expr; v_expr[s0];
+ // Now v_expr[s0] can be safely executed several times without unintended side effects.
+
+ // Init the temp variable holding the index
+ TIntermAggregate *initIndex = createTempInitDeclaration(node->getRight());
+ TIntermSequence insertions;
+ insertions.push_back(initIndex);
+ insertStatementsInParentBlock(insertions);
+ mUsedTreeInsertion = true;
+
+ // Replace the index with the temp variable
+ TIntermSymbol *tempIndex = createTempSymbol(node->getRight()->getType());
+ NodeUpdateEntry replaceIndex(node, node->getRight(), tempIndex, false);
+ mReplacements.push_back(replaceIndex);
+ }
+ else if (!node->getLeft()->isArray() && node->getLeft()->getBasicType() != EbtStruct)
+ {
+ bool write = isLValueRequiredHere();
+
+ TType type = node->getLeft()->getType();
+ mIndexedVecAndMatrixTypes.insert(type);
+
+ if (write)
+ {
+ // Convert:
+ // v_expr[index_expr]++;
+ // to this:
+ // int s0 = index_expr; float s1 = dyn_index(v_expr, s0); s1++;
+ // dyn_index_write(v_expr, s0, s1);
+ // This works even if index_expr has some side effects.
+ if (node->getLeft()->hasSideEffects())
+ {
+ // If v_expr has side effects, those need to be removed before proceeding.
+ // Otherwise the side effects of v_expr would be evaluated twice.
+ // The only case where an l-value can have side effects is when it is
+ // indexing. For example, it can be V[j++] where V is an array of vectors.
+ mRemoveIndexSideEffectsInSubtree = true;
+ return true;
+ }
+ // TODO(oetuaho@nvidia.com): This is not optimal if the expression using the value
+ // only writes it and doesn't need the previous value. http://anglebug.com/1116
+
+ mWrittenVecAndMatrixTypes.insert(type);
+ TType fieldType = GetFieldType(type);
+
+ TIntermSequence insertionsBefore;
+ TIntermSequence insertionsAfter;
+
+ // Store the index in a temporary signed int variable.
+ TIntermTyped *indexInitializer = EnsureSignedInt(node->getRight());
+ TIntermAggregate *initIndex = createTempInitDeclaration(indexInitializer);
+ initIndex->setLine(node->getLine());
+ insertionsBefore.push_back(initIndex);
+
+ TIntermAggregate *indexingCall = CreateIndexFunctionCall(
+ node, node->getLeft(), createTempSymbol(indexInitializer->getType()));
+
+ // Create a node for referring to the index after the nextTemporaryIndex() call
+ // below.
+ TIntermSymbol *tempIndex = createTempSymbol(indexInitializer->getType());
+
+ nextTemporaryIndex(); // From now on, creating temporary symbols that refer to the
+ // field value.
+ insertionsBefore.push_back(createTempInitDeclaration(indexingCall));
+
+ TIntermAggregate *indexedWriteCall =
+ CreateIndexedWriteFunctionCall(node, tempIndex, createTempSymbol(fieldType));
+ insertionsAfter.push_back(indexedWriteCall);
+ insertStatementsInParentBlock(insertionsBefore, insertionsAfter);
+ NodeUpdateEntry replaceIndex(getParentNode(), node, createTempSymbol(fieldType),
+ false);
+ mReplacements.push_back(replaceIndex);
+ mUsedTreeInsertion = true;
+ }
+ else
+ {
+ // The indexed value is not being written, so we can simply convert
+ // v_expr[index_expr]
+ // into
+ // dyn_index(v_expr, index_expr)
+ // If the index_expr is unsigned, we'll convert it to signed.
+ ASSERT(!mRemoveIndexSideEffectsInSubtree);
+ TIntermAggregate *indexingCall = CreateIndexFunctionCall(
+ node, node->getLeft(), EnsureSignedInt(node->getRight()));
+ NodeUpdateEntry replaceIndex(getParentNode(), node, indexingCall, false);
+ mReplacements.push_back(replaceIndex);
+ }
+ }
+ }
+ return !mUsedTreeInsertion;
+}
+
+void RemoveDynamicIndexingTraverser::nextIteration()
+{
+ mUsedTreeInsertion = false;
+ mRemoveIndexSideEffectsInSubtree = false;
+ nextTemporaryIndex();
+}
+
+} // namespace
+
+void RemoveDynamicIndexing(TIntermNode *root,
+ unsigned int *temporaryIndex,
+ const TSymbolTable &symbolTable,
+ int shaderVersion)
+{
+ RemoveDynamicIndexingTraverser traverser(symbolTable, shaderVersion);
+ ASSERT(temporaryIndex != nullptr);
+ traverser.useTemporaryIndex(temporaryIndex);
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ traverser.updateTree();
+ } while (traverser.usedTreeInsertion());
+ traverser.insertHelperDefinitions(root);
+ traverser.updateTree();
+}
diff --git a/src/compiler/translator/RemoveDynamicIndexing.h b/src/compiler/translator/RemoveDynamicIndexing.h
new file mode 100644
index 0000000..ae3a93c
--- /dev/null
+++ b/src/compiler/translator/RemoveDynamicIndexing.h
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2002-2015 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.
+//
+// RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of vectors and matrices,
+// replacing them with calls to functions that choose which component to return or write.
+//
+
+#ifndef COMPILER_TRANSLATOR_REMOVEDYNAMICINDEXING_H_
+#define COMPILER_TRANSLATOR_REMOVEDYNAMICINDEXING_H_
+
+class TIntermNode;
+class TSymbolTable;
+
+void RemoveDynamicIndexing(TIntermNode *root,
+ unsigned int *temporaryIndex,
+ const TSymbolTable &symbolTable,
+ int shaderVersion);
+
+#endif // COMPILER_TRANSLATOR_REMOVEDYNAMICINDEXING_H_
diff --git a/src/compiler/translator/TranslatorHLSL.cpp b/src/compiler/translator/TranslatorHLSL.cpp
index cbf8341..3ddf715 100644
--- a/src/compiler/translator/TranslatorHLSL.cpp
+++ b/src/compiler/translator/TranslatorHLSL.cpp
@@ -8,6 +8,7 @@
#include "compiler/translator/ArrayReturnValueToOutParameter.h"
#include "compiler/translator/OutputHLSL.h"
+#include "compiler/translator/RemoveDynamicIndexing.h"
#include "compiler/translator/RewriteElseBlocks.h"
#include "compiler/translator/SeparateArrayInitialization.h"
#include "compiler/translator/SeparateDeclarations.h"
@@ -40,6 +41,12 @@
// as a return value to use an out parameter to transfer the array data instead.
ArrayReturnValueToOutParameter(root, &temporaryIndex);
+ if (!shouldRunLoopAndIndexingValidation(compileOptions))
+ {
+ // HLSL doesn't support dynamic indexing of vectors and matrices.
+ RemoveDynamicIndexing(root, &temporaryIndex, getSymbolTable(), getShaderVersion());
+ }
+
// Work around D3D9 bug that would manifest in vertex shaders with selection blocks which
// use a vertex attribute as a condition, and some related computation in the else block.
if (getOutputType() == SH_HLSL9_OUTPUT && getShaderType() == GL_VERTEX_SHADER)