1 //
2 // Copyright 2002 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of non-SSBO vectors and
7 // matrices, replacing them with calls to functions that choose which component to return or write.
8 // We don't need to consider dynamic indexing in SSBO since it can be directly as part of the offset
9 // of RWByteAddressBuffer.
10 //
11 
12 #include "compiler/translator/tree_ops/RemoveDynamicIndexing.h"
13 
14 #include "compiler/translator/Compiler.h"
15 #include "compiler/translator/Diagnostics.h"
16 #include "compiler/translator/InfoSink.h"
17 #include "compiler/translator/StaticType.h"
18 #include "compiler/translator/SymbolTable.h"
19 #include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
20 #include "compiler/translator/tree_util/IntermNode_util.h"
21 #include "compiler/translator/tree_util/IntermTraverse.h"
22 
23 namespace sh
24 {
25 
26 namespace
27 {
28 
29 using DynamicIndexingNodeMatcher = std::function<bool(TIntermBinary *)>;
30 
31 const TType *kIndexType = StaticType::Get<EbtInt, EbpHigh, EvqIn, 1, 1>();
32 
33 constexpr const ImmutableString kBaseName("base");
34 constexpr const ImmutableString kIndexName("index");
35 constexpr const ImmutableString kValueName("value");
36 
GetIndexFunctionName(const TType & type,bool write)37 std::string GetIndexFunctionName(const TType &type, bool write)
38 {
39     TInfoSinkBase nameSink;
40     nameSink << "dyn_index_";
41     if (write)
42     {
43         nameSink << "write_";
44     }
45     if (type.isMatrix())
46     {
47         nameSink << "mat" << type.getCols() << "x" << type.getRows();
48     }
49     else
50     {
51         switch (type.getBasicType())
52         {
53             case EbtInt:
54                 nameSink << "ivec";
55                 break;
56             case EbtBool:
57                 nameSink << "bvec";
58                 break;
59             case EbtUInt:
60                 nameSink << "uvec";
61                 break;
62             case EbtFloat:
63                 nameSink << "vec";
64                 break;
65             default:
66                 UNREACHABLE();
67         }
68         nameSink << type.getNominalSize();
69     }
70     return nameSink.str();
71 }
72 
CreateIntConstantNode(int i)73 TIntermConstantUnion *CreateIntConstantNode(int i)
74 {
75     TConstantUnion *constant = new TConstantUnion();
76     constant->setIConst(i);
77     return new TIntermConstantUnion(constant, TType(EbtInt, EbpHigh));
78 }
79 
EnsureSignedInt(TIntermTyped * node)80 TIntermTyped *EnsureSignedInt(TIntermTyped *node)
81 {
82     if (node->getBasicType() == EbtInt)
83         return node;
84 
85     TIntermSequence *arguments = new TIntermSequence();
86     arguments->push_back(node);
87     return TIntermAggregate::CreateConstructor(TType(EbtInt), arguments);
88 }
89 
GetFieldType(const TType & indexedType)90 TType *GetFieldType(const TType &indexedType)
91 {
92     if (indexedType.isMatrix())
93     {
94         TType *fieldType = new TType(indexedType.getBasicType(), indexedType.getPrecision());
95         fieldType->setPrimarySize(static_cast<unsigned char>(indexedType.getRows()));
96         return fieldType;
97     }
98     else
99     {
100         return new TType(indexedType.getBasicType(), indexedType.getPrecision());
101     }
102 }
103 
GetBaseType(const TType & type,bool write)104 const TType *GetBaseType(const TType &type, bool write)
105 {
106     TType *baseType = new TType(type);
107     // Conservatively use highp here, even if the indexed type is not highp. That way the code can't
108     // end up using mediump version of an indexing function for a highp value, if both mediump and
109     // highp values are being indexed in the shader. For HLSL precision doesn't matter, but in
110     // principle this code could be used with multiple backends.
111     baseType->setPrecision(EbpHigh);
112     baseType->setQualifier(EvqInOut);
113     if (!write)
114         baseType->setQualifier(EvqIn);
115     return baseType;
116 }
117 
118 // Generate a read or write function for one field in a vector/matrix.
119 // Out-of-range indices are clamped. This is consistent with how ANGLE handles out-of-range
120 // indices in other places.
121 // Note that indices can be either int or uint. We create only int versions of the functions,
122 // and convert uint indices to int at the call site.
123 // read function example:
124 // float dyn_index_vec2(in vec2 base, in int index)
125 // {
126 //    switch(index)
127 //    {
128 //      case (0):
129 //        return base[0];
130 //      case (1):
131 //        return base[1];
132 //      default:
133 //        break;
134 //    }
135 //    if (index < 0)
136 //      return base[0];
137 //    return base[1];
138 // }
139 // write function example:
140 // void dyn_index_write_vec2(inout vec2 base, in int index, in float value)
141 // {
142 //    switch(index)
143 //    {
144 //      case (0):
145 //        base[0] = value;
146 //        return;
147 //      case (1):
148 //        base[1] = value;
149 //        return;
150 //      default:
151 //        break;
152 //    }
153 //    if (index < 0)
154 //    {
155 //      base[0] = value;
156 //      return;
157 //    }
158 //    base[1] = value;
159 // }
160 // Note that else is not used in above functions to avoid the RewriteElseBlocks transformation.
GetIndexFunctionDefinition(const TType & type,bool write,const TFunction & func,TSymbolTable * symbolTable)161 TIntermFunctionDefinition *GetIndexFunctionDefinition(const TType &type,
162                                                       bool write,
163                                                       const TFunction &func,
164                                                       TSymbolTable *symbolTable)
165 {
166     ASSERT(!type.isArray());
167 
168     int numCases = 0;
169     if (type.isMatrix())
170     {
171         numCases = type.getCols();
172     }
173     else
174     {
175         numCases = type.getNominalSize();
176     }
177 
178     std::string functionName                = GetIndexFunctionName(type, write);
179     TIntermFunctionPrototype *prototypeNode = CreateInternalFunctionPrototypeNode(func);
180 
181     TIntermSymbol *baseParam  = new TIntermSymbol(func.getParam(0));
182     TIntermSymbol *indexParam = new TIntermSymbol(func.getParam(1));
183     TIntermSymbol *valueParam = nullptr;
184     if (write)
185     {
186         valueParam = new TIntermSymbol(func.getParam(2));
187     }
188 
189     TIntermBlock *statementList = new TIntermBlock();
190     for (int i = 0; i < numCases; ++i)
191     {
192         TIntermCase *caseNode = new TIntermCase(CreateIntConstantNode(i));
193         statementList->getSequence()->push_back(caseNode);
194 
195         TIntermBinary *indexNode =
196             new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(i));
197         if (write)
198         {
199             TIntermBinary *assignNode =
200                 new TIntermBinary(EOpAssign, indexNode, valueParam->deepCopy());
201             statementList->getSequence()->push_back(assignNode);
202             TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr);
203             statementList->getSequence()->push_back(returnNode);
204         }
205         else
206         {
207             TIntermBranch *returnNode = new TIntermBranch(EOpReturn, indexNode);
208             statementList->getSequence()->push_back(returnNode);
209         }
210     }
211 
212     // Default case
213     TIntermCase *defaultNode = new TIntermCase(nullptr);
214     statementList->getSequence()->push_back(defaultNode);
215     TIntermBranch *breakNode = new TIntermBranch(EOpBreak, nullptr);
216     statementList->getSequence()->push_back(breakNode);
217 
218     TIntermSwitch *switchNode = new TIntermSwitch(indexParam->deepCopy(), statementList);
219 
220     TIntermBlock *bodyNode = new TIntermBlock();
221     bodyNode->getSequence()->push_back(switchNode);
222 
223     TIntermBinary *cond =
224         new TIntermBinary(EOpLessThan, indexParam->deepCopy(), CreateIntConstantNode(0));
225 
226     // Two blocks: one accesses (either reads or writes) the first element and returns,
227     // the other accesses the last element.
228     TIntermBlock *useFirstBlock = new TIntermBlock();
229     TIntermBlock *useLastBlock  = new TIntermBlock();
230     TIntermBinary *indexFirstNode =
231         new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(0));
232     TIntermBinary *indexLastNode =
233         new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(numCases - 1));
234     if (write)
235     {
236         TIntermBinary *assignFirstNode =
237             new TIntermBinary(EOpAssign, indexFirstNode, valueParam->deepCopy());
238         useFirstBlock->getSequence()->push_back(assignFirstNode);
239         TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr);
240         useFirstBlock->getSequence()->push_back(returnNode);
241 
242         TIntermBinary *assignLastNode =
243             new TIntermBinary(EOpAssign, indexLastNode, valueParam->deepCopy());
244         useLastBlock->getSequence()->push_back(assignLastNode);
245     }
246     else
247     {
248         TIntermBranch *returnFirstNode = new TIntermBranch(EOpReturn, indexFirstNode);
249         useFirstBlock->getSequence()->push_back(returnFirstNode);
250 
251         TIntermBranch *returnLastNode = new TIntermBranch(EOpReturn, indexLastNode);
252         useLastBlock->getSequence()->push_back(returnLastNode);
253     }
254     TIntermIfElse *ifNode = new TIntermIfElse(cond, useFirstBlock, nullptr);
255     bodyNode->getSequence()->push_back(ifNode);
256     bodyNode->getSequence()->push_back(useLastBlock);
257 
258     TIntermFunctionDefinition *indexingFunction =
259         new TIntermFunctionDefinition(prototypeNode, bodyNode);
260     return indexingFunction;
261 }
262 
263 class RemoveDynamicIndexingTraverser : public TLValueTrackingTraverser
264 {
265   public:
266     RemoveDynamicIndexingTraverser(DynamicIndexingNodeMatcher &&matcher,
267                                    TSymbolTable *symbolTable,
268                                    PerformanceDiagnostics *perfDiagnostics);
269 
270     bool visitBinary(Visit visit, TIntermBinary *node) override;
271 
272     void insertHelperDefinitions(TIntermNode *root);
273 
274     void nextIteration();
275 
usedTreeInsertion() const276     bool usedTreeInsertion() const { return mUsedTreeInsertion; }
277 
278   protected:
279     // Maps of types that are indexed to the indexing function ids used for them. Note that these
280     // can not store multiple variants of the same type with different precisions - only one
281     // precision gets stored.
282     std::map<TType, TFunction *> mIndexedVecAndMatrixTypes;
283     std::map<TType, TFunction *> mWrittenVecAndMatrixTypes;
284 
285     bool mUsedTreeInsertion;
286 
287     // When true, the traverser will remove side effects from any indexing expression.
288     // This is done so that in code like
289     //   V[j++][i]++.
290     // where V is an array of vectors, j++ will only be evaluated once.
291     bool mRemoveIndexSideEffectsInSubtree;
292 
293     DynamicIndexingNodeMatcher mMatcher;
294     PerformanceDiagnostics *mPerfDiagnostics;
295 };
296 
RemoveDynamicIndexingTraverser(DynamicIndexingNodeMatcher && matcher,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)297 RemoveDynamicIndexingTraverser::RemoveDynamicIndexingTraverser(
298     DynamicIndexingNodeMatcher &&matcher,
299     TSymbolTable *symbolTable,
300     PerformanceDiagnostics *perfDiagnostics)
301     : TLValueTrackingTraverser(true, false, false, symbolTable),
302       mUsedTreeInsertion(false),
303       mRemoveIndexSideEffectsInSubtree(false),
304       mMatcher(matcher),
305       mPerfDiagnostics(perfDiagnostics)
306 {}
307 
insertHelperDefinitions(TIntermNode * root)308 void RemoveDynamicIndexingTraverser::insertHelperDefinitions(TIntermNode *root)
309 {
310     TIntermBlock *rootBlock = root->getAsBlock();
311     ASSERT(rootBlock != nullptr);
312     TIntermSequence insertions;
313     for (auto &type : mIndexedVecAndMatrixTypes)
314     {
315         insertions.push_back(
316             GetIndexFunctionDefinition(type.first, false, *type.second, mSymbolTable));
317     }
318     for (auto &type : mWrittenVecAndMatrixTypes)
319     {
320         insertions.push_back(
321             GetIndexFunctionDefinition(type.first, true, *type.second, mSymbolTable));
322     }
323     rootBlock->insertChildNodes(0, insertions);
324 }
325 
326 // Create a call to dyn_index_*() based on an indirect indexing op node
CreateIndexFunctionCall(TIntermBinary * node,TIntermTyped * index,TFunction * indexingFunction)327 TIntermAggregate *CreateIndexFunctionCall(TIntermBinary *node,
328                                           TIntermTyped *index,
329                                           TFunction *indexingFunction)
330 {
331     ASSERT(node->getOp() == EOpIndexIndirect);
332     TIntermSequence *arguments = new TIntermSequence();
333     arguments->push_back(node->getLeft());
334     arguments->push_back(index);
335 
336     TIntermAggregate *indexingCall =
337         TIntermAggregate::CreateFunctionCall(*indexingFunction, arguments);
338     indexingCall->setLine(node->getLine());
339     return indexingCall;
340 }
341 
CreateIndexedWriteFunctionCall(TIntermBinary * node,TVariable * index,TVariable * writtenValue,TFunction * indexedWriteFunction)342 TIntermAggregate *CreateIndexedWriteFunctionCall(TIntermBinary *node,
343                                                  TVariable *index,
344                                                  TVariable *writtenValue,
345                                                  TFunction *indexedWriteFunction)
346 {
347     ASSERT(node->getOp() == EOpIndexIndirect);
348     TIntermSequence *arguments = new TIntermSequence();
349     // Deep copy the child nodes so that two pointers to the same node don't end up in the tree.
350     arguments->push_back(node->getLeft()->deepCopy());
351     arguments->push_back(CreateTempSymbolNode(index));
352     arguments->push_back(CreateTempSymbolNode(writtenValue));
353 
354     TIntermAggregate *indexedWriteCall =
355         TIntermAggregate::CreateFunctionCall(*indexedWriteFunction, arguments);
356     indexedWriteCall->setLine(node->getLine());
357     return indexedWriteCall;
358 }
359 
visitBinary(Visit visit,TIntermBinary * node)360 bool RemoveDynamicIndexingTraverser::visitBinary(Visit visit, TIntermBinary *node)
361 {
362     if (mUsedTreeInsertion)
363         return false;
364 
365     if (node->getOp() == EOpIndexIndirect)
366     {
367         if (mRemoveIndexSideEffectsInSubtree)
368         {
369             ASSERT(node->getRight()->hasSideEffects());
370             // In case we're just removing index side effects, convert
371             //   v_expr[index_expr]
372             // to this:
373             //   int s0 = index_expr; v_expr[s0];
374             // Now v_expr[s0] can be safely executed several times without unintended side effects.
375             TIntermDeclaration *indexVariableDeclaration = nullptr;
376             TVariable *indexVariable = DeclareTempVariable(mSymbolTable, node->getRight(),
377                                                            EvqTemporary, &indexVariableDeclaration);
378             insertStatementInParentBlock(indexVariableDeclaration);
379             mUsedTreeInsertion = true;
380 
381             // Replace the index with the temp variable
382             TIntermSymbol *tempIndex = CreateTempSymbolNode(indexVariable);
383             queueReplacementWithParent(node, node->getRight(), tempIndex, OriginalNode::IS_DROPPED);
384         }
385         else if (mMatcher(node))
386         {
387             if (mPerfDiagnostics)
388             {
389                 mPerfDiagnostics->warning(node->getLine(),
390                                           "Performance: dynamic indexing of vectors and "
391                                           "matrices is emulated and can be slow.",
392                                           "[]");
393             }
394             bool write = isLValueRequiredHere();
395 
396 #if defined(ANGLE_ENABLE_ASSERTS)
397             // Make sure that IntermNodePatternMatcher is consistent with the slightly differently
398             // implemented checks in this traverser.
399             IntermNodePatternMatcher matcher(
400                 IntermNodePatternMatcher::kDynamicIndexingOfVectorOrMatrixInLValue);
401             ASSERT(matcher.match(node, getParentNode(), isLValueRequiredHere()) == write);
402 #endif
403 
404             const TType &type = node->getLeft()->getType();
405             ImmutableString indexingFunctionName(GetIndexFunctionName(type, false));
406             TFunction *indexingFunction = nullptr;
407             if (mIndexedVecAndMatrixTypes.find(type) == mIndexedVecAndMatrixTypes.end())
408             {
409                 indexingFunction =
410                     new TFunction(mSymbolTable, indexingFunctionName, SymbolType::AngleInternal,
411                                   GetFieldType(type), true);
412                 indexingFunction->addParameter(new TVariable(
413                     mSymbolTable, kBaseName, GetBaseType(type, false), SymbolType::AngleInternal));
414                 indexingFunction->addParameter(
415                     new TVariable(mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal));
416                 mIndexedVecAndMatrixTypes[type] = indexingFunction;
417             }
418             else
419             {
420                 indexingFunction = mIndexedVecAndMatrixTypes[type];
421             }
422 
423             if (write)
424             {
425                 // Convert:
426                 //   v_expr[index_expr]++;
427                 // to this:
428                 //   int s0 = index_expr; float s1 = dyn_index(v_expr, s0); s1++;
429                 //   dyn_index_write(v_expr, s0, s1);
430                 // This works even if index_expr has some side effects.
431                 if (node->getLeft()->hasSideEffects())
432                 {
433                     // If v_expr has side effects, those need to be removed before proceeding.
434                     // Otherwise the side effects of v_expr would be evaluated twice.
435                     // The only case where an l-value can have side effects is when it is
436                     // indexing. For example, it can be V[j++] where V is an array of vectors.
437                     mRemoveIndexSideEffectsInSubtree = true;
438                     return true;
439                 }
440 
441                 TIntermBinary *leftBinary = node->getLeft()->getAsBinaryNode();
442                 if (leftBinary != nullptr && mMatcher(leftBinary))
443                 {
444                     // This is a case like:
445                     // mat2 m;
446                     // m[a][b]++;
447                     // Process the child node m[a] first.
448                     return true;
449                 }
450 
451                 // TODO(oetuaho@nvidia.com): This is not optimal if the expression using the value
452                 // only writes it and doesn't need the previous value. http://anglebug.com/1116
453 
454                 TFunction *indexedWriteFunction = nullptr;
455                 if (mWrittenVecAndMatrixTypes.find(type) == mWrittenVecAndMatrixTypes.end())
456                 {
457                     ImmutableString functionName(
458                         GetIndexFunctionName(node->getLeft()->getType(), true));
459                     indexedWriteFunction =
460                         new TFunction(mSymbolTable, functionName, SymbolType::AngleInternal,
461                                       StaticType::GetBasic<EbtVoid>(), false);
462                     indexedWriteFunction->addParameter(new TVariable(mSymbolTable, kBaseName,
463                                                                      GetBaseType(type, true),
464                                                                      SymbolType::AngleInternal));
465                     indexedWriteFunction->addParameter(new TVariable(
466                         mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal));
467                     TType *valueType = GetFieldType(type);
468                     valueType->setQualifier(EvqIn);
469                     indexedWriteFunction->addParameter(new TVariable(
470                         mSymbolTable, kValueName, static_cast<const TType *>(valueType),
471                         SymbolType::AngleInternal));
472                     mWrittenVecAndMatrixTypes[type] = indexedWriteFunction;
473                 }
474                 else
475                 {
476                     indexedWriteFunction = mWrittenVecAndMatrixTypes[type];
477                 }
478 
479                 TIntermSequence insertionsBefore;
480                 TIntermSequence insertionsAfter;
481 
482                 // Store the index in a temporary signed int variable.
483                 // s0 = index_expr;
484                 TIntermTyped *indexInitializer               = EnsureSignedInt(node->getRight());
485                 TIntermDeclaration *indexVariableDeclaration = nullptr;
486                 TVariable *indexVariable                     = DeclareTempVariable(
487                     mSymbolTable, indexInitializer, EvqTemporary, &indexVariableDeclaration);
488                 insertionsBefore.push_back(indexVariableDeclaration);
489 
490                 // s1 = dyn_index(v_expr, s0);
491                 TIntermAggregate *indexingCall = CreateIndexFunctionCall(
492                     node, CreateTempSymbolNode(indexVariable), indexingFunction);
493                 TIntermDeclaration *fieldVariableDeclaration = nullptr;
494                 TVariable *fieldVariable                     = DeclareTempVariable(
495                     mSymbolTable, indexingCall, EvqTemporary, &fieldVariableDeclaration);
496                 insertionsBefore.push_back(fieldVariableDeclaration);
497 
498                 // dyn_index_write(v_expr, s0, s1);
499                 TIntermAggregate *indexedWriteCall = CreateIndexedWriteFunctionCall(
500                     node, indexVariable, fieldVariable, indexedWriteFunction);
501                 insertionsAfter.push_back(indexedWriteCall);
502                 insertStatementsInParentBlock(insertionsBefore, insertionsAfter);
503 
504                 // replace the node with s1
505                 queueReplacement(CreateTempSymbolNode(fieldVariable), OriginalNode::IS_DROPPED);
506                 mUsedTreeInsertion = true;
507             }
508             else
509             {
510                 // The indexed value is not being written, so we can simply convert
511                 //   v_expr[index_expr]
512                 // into
513                 //   dyn_index(v_expr, index_expr)
514                 // If the index_expr is unsigned, we'll convert it to signed.
515                 ASSERT(!mRemoveIndexSideEffectsInSubtree);
516                 TIntermAggregate *indexingCall = CreateIndexFunctionCall(
517                     node, EnsureSignedInt(node->getRight()), indexingFunction);
518                 queueReplacement(indexingCall, OriginalNode::IS_DROPPED);
519             }
520         }
521     }
522     return !mUsedTreeInsertion;
523 }
524 
nextIteration()525 void RemoveDynamicIndexingTraverser::nextIteration()
526 {
527     mUsedTreeInsertion               = false;
528     mRemoveIndexSideEffectsInSubtree = false;
529 }
530 
RemoveDynamicIndexingIf(DynamicIndexingNodeMatcher && matcher,TCompiler * compiler,TIntermNode * root,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)531 bool RemoveDynamicIndexingIf(DynamicIndexingNodeMatcher &&matcher,
532                              TCompiler *compiler,
533                              TIntermNode *root,
534                              TSymbolTable *symbolTable,
535                              PerformanceDiagnostics *perfDiagnostics)
536 {
537     RemoveDynamicIndexingTraverser traverser(std::move(matcher), symbolTable, perfDiagnostics);
538     do
539     {
540         traverser.nextIteration();
541         root->traverse(&traverser);
542         if (!traverser.updateTree(compiler, root))
543         {
544             return false;
545         }
546     } while (traverser.usedTreeInsertion());
547     // TODO(oetuaho@nvidia.com): It might be nicer to add the helper definitions also in the middle
548     // of traversal. Now the tree ends up in an inconsistent state in the middle, since there are
549     // function call nodes with no corresponding definition nodes. This needs special handling in
550     // TIntermLValueTrackingTraverser, and creates intricacies that are not easily apparent from a
551     // superficial reading of the code.
552     traverser.insertHelperDefinitions(root);
553     return compiler->validateAST(root);
554 }
555 
556 }  // namespace
557 
RemoveDynamicIndexingOfNonSSBOVectorOrMatrix(TCompiler * compiler,TIntermNode * root,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)558 ANGLE_NO_DISCARD bool RemoveDynamicIndexingOfNonSSBOVectorOrMatrix(
559     TCompiler *compiler,
560     TIntermNode *root,
561     TSymbolTable *symbolTable,
562     PerformanceDiagnostics *perfDiagnostics)
563 {
564     DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) {
565         return IntermNodePatternMatcher::IsDynamicIndexingOfNonSSBOVectorOrMatrix(node);
566     };
567     return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable,
568                                    perfDiagnostics);
569 }
570 
RemoveDynamicIndexingOfSwizzledVector(TCompiler * compiler,TIntermNode * root,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)571 ANGLE_NO_DISCARD bool RemoveDynamicIndexingOfSwizzledVector(TCompiler *compiler,
572                                                             TIntermNode *root,
573                                                             TSymbolTable *symbolTable,
574                                                             PerformanceDiagnostics *perfDiagnostics)
575 {
576     DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) {
577         return IntermNodePatternMatcher::IsDynamicIndexingOfSwizzledVector(node);
578     };
579     return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable,
580                                    perfDiagnostics);
581 }
582 
583 }  // namespace sh
584