1 //
2 // Copyright 2021 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 // FindPreciseNodes.cpp: Propagates |precise| to AST nodes.
7 //
8 // The high level algorithm is as follows. For every node that "assigns" to a precise object,
9 // subobject (a precise struct whose field is being assigned) or superobject (a struct with a
10 // precise field), two things happen:
11 //
12 // - The operation is marked precise if it's an arithmetic operation
13 // - The right hand side of the assignment is made precise. If only a subobject is precise, only
14 // the corresponding subobject of the right hand side is made precise.
15 //
16
17 #include "compiler/translator/tree_util/FindPreciseNodes.h"
18
19 #include "common/hash_containers.h"
20 #include "common/hash_utils.h"
21 #include "compiler/translator/Compiler.h"
22 #include "compiler/translator/IntermNode.h"
23 #include "compiler/translator/Symbol.h"
24 #include "compiler/translator/tree_util/IntermTraverse.h"
25
26 namespace sh
27 {
28
29 namespace
30 {
31
32 // An access chain applied to a variable. The |precise|-ness of a node does not change when
33 // indexing arrays, selecting matrix columns or swizzle vectors. This access chain thus only
34 // includes block field selections. The access chain is used to identify the part of an object
35 // that is or should be |precise|. If both a.b.c and a.b are precise, only a.b is ever considered.
36 class AccessChain
37 {
38 public:
39 AccessChain() = default;
40
operator ==(const AccessChain & other) const41 bool operator==(const AccessChain &other) const { return mChain == other.mChain; }
42
43 const TVariable *build(TIntermTyped *lvalue);
44
getChain() const45 const TVector<size_t> &getChain() const { return mChain; }
46
reduceChain(size_t newSize)47 void reduceChain(size_t newSize)
48 {
49 ASSERT(newSize <= mChain.size());
50 mChain.resize(newSize);
51 }
clear()52 void clear() { reduceChain(0); }
push_back(size_t index)53 void push_back(size_t index) { mChain.push_back(index); }
54 void pop_front(size_t n);
append(const AccessChain & other)55 void append(const AccessChain &other)
56 {
57 mChain.insert(mChain.end(), other.mChain.begin(), other.mChain.end());
58 }
59 bool removePrefix(const AccessChain &other);
60
61 private:
62 TVector<size_t> mChain;
63 };
64
IsIndexOp(TOperator op)65 bool IsIndexOp(TOperator op)
66 {
67 switch (op)
68 {
69 case EOpIndexDirect:
70 case EOpIndexDirectStruct:
71 case EOpIndexDirectInterfaceBlock:
72 case EOpIndexIndirect:
73 return true;
74 default:
75 return false;
76 }
77 }
78
build(TIntermTyped * lvalue)79 const TVariable *AccessChain::build(TIntermTyped *lvalue)
80 {
81 if (lvalue->getAsSwizzleNode())
82 {
83 return build(lvalue->getAsSwizzleNode()->getOperand());
84 }
85 if (lvalue->getAsSymbolNode())
86 {
87 const TVariable *var = &lvalue->getAsSymbolNode()->variable();
88
89 // For fields of nameless interface blocks, add the field index too.
90 if (var->getType().getInterfaceBlock() != nullptr)
91 {
92 mChain.push_back(var->getType().getInterfaceBlockFieldIndex());
93 }
94
95 return var;
96 }
97 if (lvalue->getAsAggregate())
98 {
99 return nullptr;
100 }
101
102 TIntermBinary *binary = lvalue->getAsBinaryNode();
103 ASSERT(binary);
104
105 TOperator op = binary->getOp();
106 ASSERT(IsIndexOp(op));
107
108 const TVariable *var = build(binary->getLeft());
109
110 if (op == EOpIndexDirectStruct || op == EOpIndexDirectInterfaceBlock)
111 {
112 int fieldIndex = binary->getRight()->getAsConstantUnion()->getIConst(0);
113 mChain.push_back(fieldIndex);
114 }
115
116 return var;
117 }
118
pop_front(size_t n)119 void AccessChain::pop_front(size_t n)
120 {
121 std::rotate(mChain.begin(), mChain.begin() + n, mChain.end());
122 reduceChain(mChain.size() - n);
123 }
124
removePrefix(const AccessChain & other)125 bool AccessChain::removePrefix(const AccessChain &other)
126 {
127 // First, make sure the common part of the two access chains match.
128 size_t commonSize = std::min(mChain.size(), other.mChain.size());
129
130 for (size_t index = 0; index < commonSize; ++index)
131 {
132 if (mChain[index] != other.mChain[index])
133 {
134 return false;
135 }
136 }
137
138 // Remove the common part from the access chain. If other is a deeper access chain, this access
139 // chain will become empty.
140 pop_front(commonSize);
141
142 return true;
143 }
144
GetAssignmentAccessChain(TIntermOperator * node)145 AccessChain GetAssignmentAccessChain(TIntermOperator *node)
146 {
147 // The assignment is either a unary or a binary node, and the lvalue is always the first child.
148 AccessChain lvalueAccessChain;
149 lvalueAccessChain.build(node->getChildNode(0)->getAsTyped());
150 return lvalueAccessChain;
151 }
152
153 template <typename Traverser>
TraverseIndexNodesOnly(TIntermNode * node,Traverser * traverser)154 void TraverseIndexNodesOnly(TIntermNode *node, Traverser *traverser)
155 {
156 if (node->getAsSwizzleNode())
157 {
158 node = node->getAsSwizzleNode()->getOperand();
159 }
160
161 if (node->getAsSymbolNode() || node->getAsAggregate())
162 {
163 return;
164 }
165
166 TIntermBinary *binary = node->getAsBinaryNode();
167 ASSERT(binary);
168
169 TOperator op = binary->getOp();
170 ASSERT(IsIndexOp(op));
171
172 if (op == EOpIndexIndirect)
173 {
174 binary->getRight()->traverse(traverser);
175 }
176
177 TraverseIndexNodesOnly(binary->getLeft(), traverser);
178 }
179
180 // An object, which could be a sub-object of a variable.
181 struct ObjectAndAccessChain
182 {
183 const TVariable *variable;
184 AccessChain accessChain;
185 };
186
operator ==(const ObjectAndAccessChain & a,const ObjectAndAccessChain & b)187 bool operator==(const ObjectAndAccessChain &a, const ObjectAndAccessChain &b)
188 {
189 return a.variable == b.variable && a.accessChain == b.accessChain;
190 }
191
192 struct ObjectAndAccessChainHash
193 {
operator ()sh::__anon7ec839090111::ObjectAndAccessChainHash194 size_t operator()(const ObjectAndAccessChain &object) const
195 {
196 size_t result = angle::ComputeGenericHash(&object.variable, sizeof(object.variable));
197 if (!object.accessChain.getChain().empty())
198 {
199 result =
200 result ^ angle::ComputeGenericHash(object.accessChain.getChain().data(),
201 object.accessChain.getChain().size() *
202 sizeof(object.accessChain.getChain()[0]));
203 }
204 return result;
205 }
206 };
207
208 // A map from variables to AST nodes that modify them (i.e. nodes where IsAssignment(op)).
209 using VariableToAssignmentNodeMap = angle::HashMap<const TVariable *, TVector<TIntermOperator *>>;
210 // A set of |return| nodes from functions with a |precise| return value.
211 using PreciseReturnNodes = angle::HashSet<TIntermBranch *>;
212 // A set of precise objects that need processing, or have been processed.
213 using PreciseObjectSet = angle::HashSet<ObjectAndAccessChain, ObjectAndAccessChainHash>;
214
215 struct ASTInfo
216 {
217 // Generic information about the tree:
218 VariableToAssignmentNodeMap variableAssignmentNodeMap;
219 // Information pertaining to |precise| expressions:
220 PreciseReturnNodes preciseReturnNodes;
221 PreciseObjectSet preciseObjectsToProcess;
222 PreciseObjectSet preciseObjectsVisited;
223 };
224
GetObjectPreciseSubChainLength(const ObjectAndAccessChain & object)225 int GetObjectPreciseSubChainLength(const ObjectAndAccessChain &object)
226 {
227 const TType &type = object.variable->getType();
228
229 if (type.isPrecise())
230 {
231 return 0;
232 }
233
234 const TFieldListCollection *block = type.getInterfaceBlock();
235 if (block == nullptr)
236 {
237 block = type.getStruct();
238 }
239 const TVector<size_t> &accessChain = object.accessChain.getChain();
240
241 for (size_t length = 0; length < accessChain.size(); ++length)
242 {
243 ASSERT(block != nullptr);
244
245 const TField *field = block->fields()[accessChain[length]];
246 if (field->type()->isPrecise())
247 {
248 return static_cast<int>(length + 1);
249 }
250
251 block = field->type()->getStruct();
252 }
253
254 return -1;
255 }
256
AddPreciseObject(ASTInfo * info,const ObjectAndAccessChain & object)257 void AddPreciseObject(ASTInfo *info, const ObjectAndAccessChain &object)
258 {
259 if (info->preciseObjectsVisited.count(object) > 0)
260 {
261 return;
262 }
263
264 info->preciseObjectsToProcess.insert(object);
265 info->preciseObjectsVisited.insert(object);
266 }
267
268 void AddPreciseSubObjects(ASTInfo *info, const ObjectAndAccessChain &object);
269
AddObjectIfPrecise(ASTInfo * info,const ObjectAndAccessChain & object)270 void AddObjectIfPrecise(ASTInfo *info, const ObjectAndAccessChain &object)
271 {
272 // See if the access chain is already precise, and if so add the minimum access chain that is
273 // precise.
274 int preciseSubChainLength = GetObjectPreciseSubChainLength(object);
275 if (preciseSubChainLength == -1)
276 {
277 // If the access chain is not precise, see if there are any fields of it that are precise,
278 // and add those individually.
279 AddPreciseSubObjects(info, object);
280 return;
281 }
282
283 ObjectAndAccessChain preciseObject = object;
284 preciseObject.accessChain.reduceChain(preciseSubChainLength);
285
286 AddPreciseObject(info, preciseObject);
287 }
288
AddPreciseSubObjects(ASTInfo * info,const ObjectAndAccessChain & object)289 void AddPreciseSubObjects(ASTInfo *info, const ObjectAndAccessChain &object)
290 {
291 const TFieldListCollection *block = object.variable->getType().getInterfaceBlock();
292 if (block == nullptr)
293 {
294 block = object.variable->getType().getStruct();
295 }
296 const TVector<size_t> &accessChain = object.accessChain.getChain();
297
298 for (size_t length = 0; length < accessChain.size(); ++length)
299 {
300 block = block->fields()[accessChain[length]]->type()->getStruct();
301 }
302
303 if (block == nullptr)
304 {
305 return;
306 }
307
308 for (size_t fieldIndex = 0; fieldIndex < block->fields().size(); ++fieldIndex)
309 {
310 ObjectAndAccessChain subObject = object;
311 subObject.accessChain.push_back(fieldIndex);
312
313 // If the field is precise, add it as a precise subobject. Otherwise recurse.
314 if (block->fields()[fieldIndex]->type()->isPrecise())
315 {
316 AddPreciseObject(info, subObject);
317 }
318 else
319 {
320 AddPreciseSubObjects(info, subObject);
321 }
322 }
323 }
324
IsArithmeticOp(TOperator op)325 bool IsArithmeticOp(TOperator op)
326 {
327 switch (op)
328 {
329 case EOpNegative:
330
331 case EOpPostIncrement:
332 case EOpPostDecrement:
333 case EOpPreIncrement:
334 case EOpPreDecrement:
335
336 case EOpAdd:
337 case EOpSub:
338 case EOpMul:
339 case EOpDiv:
340 case EOpIMod:
341
342 case EOpVectorTimesScalar:
343 case EOpVectorTimesMatrix:
344 case EOpMatrixTimesVector:
345 case EOpMatrixTimesScalar:
346 case EOpMatrixTimesMatrix:
347
348 case EOpAddAssign:
349 case EOpSubAssign:
350
351 case EOpMulAssign:
352 case EOpVectorTimesMatrixAssign:
353 case EOpVectorTimesScalarAssign:
354 case EOpMatrixTimesScalarAssign:
355 case EOpMatrixTimesMatrixAssign:
356
357 case EOpDivAssign:
358 case EOpIModAssign:
359
360 case EOpDot:
361 return true;
362 default:
363 return false;
364 }
365 }
366
367 // A traverser that gathers the following information, used to kick off processing:
368 //
369 // - For each variable, the AST nodes that modify it.
370 // - The set of |precise| return AST node.
371 // - The set of |precise| access chains assigned to.
372 //
373 class InfoGatherTraverser : public TIntermTraverser
374 {
375 public:
InfoGatherTraverser(ASTInfo * info)376 InfoGatherTraverser(ASTInfo *info) : TIntermTraverser(true, false, false), mInfo(info) {}
377
visitUnary(Visit visit,TIntermUnary * node)378 bool visitUnary(Visit visit, TIntermUnary *node) override
379 {
380 // If the node is an assignment (i.e. ++ and --), store the relevant information.
381 if (!IsAssignment(node->getOp()))
382 {
383 return true;
384 }
385
386 visitLvalue(node, node->getOperand());
387 return false;
388 }
389
visitBinary(Visit visit,TIntermBinary * node)390 bool visitBinary(Visit visit, TIntermBinary *node) override
391 {
392 if (IsAssignment(node->getOp()))
393 {
394 visitLvalue(node, node->getLeft());
395
396 node->getRight()->traverse(this);
397
398 return false;
399 }
400
401 return true;
402 }
403
visitDeclaration(Visit visit,TIntermDeclaration * node)404 bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
405 {
406 const TIntermSequence &sequence = *(node->getSequence());
407 TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
408 TIntermBinary *initNode = sequence.front()->getAsBinaryNode();
409 TIntermTyped *initExpression = nullptr;
410
411 if (symbol == nullptr)
412 {
413 ASSERT(initNode->getOp() == EOpInitialize);
414
415 symbol = initNode->getLeft()->getAsSymbolNode();
416 initExpression = initNode->getRight();
417 }
418
419 ASSERT(symbol);
420 ObjectAndAccessChain object = {&symbol->variable(), {}};
421 AddObjectIfPrecise(mInfo, object);
422
423 if (initExpression)
424 {
425 mInfo->variableAssignmentNodeMap[object.variable].push_back(initNode);
426
427 // Visit the init expression, which may itself have assignments.
428 initExpression->traverse(this);
429 }
430
431 return false;
432 }
433
visitFunctionDefinition(Visit visit,TIntermFunctionDefinition * node)434 bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override
435 {
436 mCurrentFunction = node->getFunction();
437
438 for (size_t paramIndex = 0; paramIndex < mCurrentFunction->getParamCount(); ++paramIndex)
439 {
440 ObjectAndAccessChain param = {mCurrentFunction->getParam(paramIndex), {}};
441 AddObjectIfPrecise(mInfo, param);
442 }
443
444 return true;
445 }
446
visitBranch(Visit visit,TIntermBranch * node)447 bool visitBranch(Visit visit, TIntermBranch *node) override
448 {
449 if (node->getFlowOp() == EOpReturn && node->getChildCount() == 1 &&
450 mCurrentFunction->getReturnType().isPrecise())
451 {
452 mInfo->preciseReturnNodes.insert(node);
453 }
454
455 return true;
456 }
457
visitGlobalQualifierDeclaration(Visit visit,TIntermGlobalQualifierDeclaration * node)458 bool visitGlobalQualifierDeclaration(Visit visit,
459 TIntermGlobalQualifierDeclaration *node) override
460 {
461 if (node->isPrecise())
462 {
463 ObjectAndAccessChain preciseObject = {&node->getSymbol()->variable(), {}};
464 AddPreciseObject(mInfo, preciseObject);
465 }
466
467 return false;
468 }
469
470 private:
visitLvalue(TIntermOperator * assignmentNode,TIntermTyped * lvalueNode)471 void visitLvalue(TIntermOperator *assignmentNode, TIntermTyped *lvalueNode)
472 {
473 AccessChain lvalueChain;
474 const TVariable *lvalueBase = lvalueChain.build(lvalueNode);
475 if (lvalueBase != nullptr)
476 {
477 mInfo->variableAssignmentNodeMap[lvalueBase].push_back(assignmentNode);
478
479 ObjectAndAccessChain lvalue = {lvalueBase, lvalueChain};
480 AddObjectIfPrecise(mInfo, lvalue);
481 }
482
483 TraverseIndexNodesOnly(lvalueNode, this);
484 }
485
486 ASTInfo *mInfo = nullptr;
487 const TFunction *mCurrentFunction = nullptr;
488 };
489
490 // A traverser that, given an access chain, traverses an expression and marks parts of it |precise|.
491 // For example, in the expression |Struct1(a, Struct2(b, c), d)|:
492 //
493 // - Given access chain [1], both |b| and |c| are marked precise.
494 // - Given access chain [1, 0], only |b| is marked precise.
495 //
496 // When access chain is empty, arithmetic nodes are marked |precise| and any access chains found in
497 // their children is recursively added for processing.
498 //
499 // The access chain given to the traverser is derived from the left hand side of an assignment,
500 // while the traverser is run on the right hand side.
501 class PropagatePreciseTraverser : public TIntermTraverser
502 {
503 public:
PropagatePreciseTraverser(ASTInfo * info)504 PropagatePreciseTraverser(ASTInfo *info) : TIntermTraverser(true, false, false), mInfo(info) {}
505
propagatePrecise(TIntermNode * expression,const AccessChain & accessChain)506 void propagatePrecise(TIntermNode *expression, const AccessChain &accessChain)
507 {
508 mCurrentAccessChain = accessChain;
509 expression->traverse(this);
510 }
511
visitUnary(Visit visit,TIntermUnary * node)512 bool visitUnary(Visit visit, TIntermUnary *node) override
513 {
514 // Unary operations cannot be applied to structures.
515 ASSERT(mCurrentAccessChain.getChain().empty());
516
517 // Mark arithmetic nodes as |precise|.
518 if (IsArithmeticOp(node->getOp()))
519 {
520 node->setIsPrecise();
521 }
522
523 // Mark the operand itself |precise| too.
524 return true;
525 }
526
visitBinary(Visit visit,TIntermBinary * node)527 bool visitBinary(Visit visit, TIntermBinary *node) override
528 {
529 if (IsIndexOp(node->getOp()))
530 {
531 // Append the remaining access chain with that of the node, and mark that as |precise|.
532 // For example, if we are evaluating an expression and expecting to mark the access
533 // chain [1, 3] as |precise|, and the node itself has access chain [0, 2] applied to
534 // variable V, then what ends up being |precise| is V with access chain [0, 2, 1, 3].
535 AccessChain nodeAccessChain;
536 const TVariable *baseVariable = nodeAccessChain.build(node);
537 if (baseVariable != nullptr)
538 {
539 nodeAccessChain.append(mCurrentAccessChain);
540
541 ObjectAndAccessChain preciseObject = {baseVariable, nodeAccessChain};
542 AddPreciseObject(mInfo, preciseObject);
543 }
544
545 // Visit index nodes, each of which should be considered |precise| in its entirety.
546 mCurrentAccessChain.clear();
547 TraverseIndexNodesOnly(node, this);
548
549 return false;
550 }
551
552 if (node->getOp() == EOpComma)
553 {
554 // For expr1,expr2, consider only expr2 as that's the one whose calculation is relevant.
555 node->getRight()->traverse(this);
556 return false;
557 }
558
559 // Mark arithmetic nodes as |precise|.
560 if (IsArithmeticOp(node->getOp()))
561 {
562 node->setIsPrecise();
563 }
564
565 if (IsAssignment(node->getOp()) || node->getOp() == EOpInitialize)
566 {
567 // If the node itself is a[...] op= expr, consider only expr as |precise|, as that's the
568 // one whose calculation is significant.
569 node->getRight()->traverse(this);
570
571 // The indices used on the left hand side are also significant in their entirety.
572 mCurrentAccessChain.clear();
573 TraverseIndexNodesOnly(node->getLeft(), this);
574
575 return false;
576 }
577
578 // Binary operations cannot be applied to structures.
579 ASSERT(mCurrentAccessChain.getChain().empty());
580
581 // Mark the operands themselves |precise| too.
582 return true;
583 }
584
visitSymbol(TIntermSymbol * symbol)585 void visitSymbol(TIntermSymbol *symbol) override
586 {
587 // Mark the symbol together with the current access chain as |precise|.
588 ObjectAndAccessChain preciseObject = {&symbol->variable(), mCurrentAccessChain};
589 AddPreciseObject(mInfo, preciseObject);
590 }
591
visitAggregate(Visit visit,TIntermAggregate * node)592 bool visitAggregate(Visit visit, TIntermAggregate *node) override
593 {
594 // If this is a struct constructor and the access chain is not empty, only apply |precise|
595 // to the field selected by the access chain.
596 const TType &type = node->getType();
597 const bool isStructConstructor =
598 node->getOp() == EOpConstruct && type.getStruct() != nullptr && !type.isArray();
599
600 if (!mCurrentAccessChain.getChain().empty() && isStructConstructor)
601 {
602 size_t selectedFieldIndex = mCurrentAccessChain.getChain().front();
603 mCurrentAccessChain.pop_front(1);
604
605 ASSERT(selectedFieldIndex < node->getChildCount());
606
607 // Visit only said field.
608 node->getChildNode(selectedFieldIndex)->traverse(this);
609 return false;
610 }
611
612 // If this is an array constructor, each element is equally |precise| with the same access
613 // chain. Otherwise there cannot be any access chain for constructors.
614 if (node->getOp() == EOpConstruct)
615 {
616 ASSERT(type.isArray() || mCurrentAccessChain.getChain().empty());
617 return true;
618 }
619
620 // Otherwise this is a function call. The access chain is irrelevant and every (non-out)
621 // parameter of the function call should be considered |precise|.
622 mCurrentAccessChain.clear();
623
624 const TFunction *function = node->getFunction();
625 ASSERT(function);
626
627 for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex)
628 {
629 if (function->getParam(paramIndex)->getType().getQualifier() != EvqParamOut)
630 {
631 node->getChildNode(paramIndex)->traverse(this);
632 }
633 }
634
635 // Mark arithmetic nodes as |precise|.
636 if (IsArithmeticOp(node->getOp()))
637 {
638 node->setIsPrecise();
639 }
640
641 return false;
642 }
643
644 private:
645 ASTInfo *mInfo = nullptr;
646 AccessChain mCurrentAccessChain;
647 };
648 } // anonymous namespace
649
FindPreciseNodes(TCompiler * compiler,TIntermBlock * root)650 void FindPreciseNodes(TCompiler *compiler, TIntermBlock *root)
651 {
652 ASTInfo info;
653
654 InfoGatherTraverser infoGather(&info);
655 root->traverse(&infoGather);
656
657 PropagatePreciseTraverser propagator(&info);
658
659 // First, get return expressions out of the way by propagating |precise|.
660 for (TIntermBranch *returnNode : info.preciseReturnNodes)
661 {
662 ASSERT(returnNode->getChildCount() == 1);
663 propagator.propagatePrecise(returnNode->getChildNode(0), {});
664 }
665
666 // Now take |precise| access chains one by one, and propagate their |precise|-ness to the right
667 // hand side of all assignments in which they are on the left hand side, as well as the
668 // arithmetic expression that assigns to them.
669
670 while (!info.preciseObjectsToProcess.empty())
671 {
672 // Get one |precise| object to process.
673 auto first = info.preciseObjectsToProcess.begin();
674 const ObjectAndAccessChain toProcess = *first;
675 info.preciseObjectsToProcess.erase(first);
676
677 // Propagate |precise| to every node where it's assigned to.
678 const TVector<TIntermOperator *> &assignmentNodes =
679 info.variableAssignmentNodeMap[toProcess.variable];
680 for (TIntermOperator *assignmentNode : assignmentNodes)
681 {
682 AccessChain assignmentAccessChain = GetAssignmentAccessChain(assignmentNode);
683
684 // There are two possibilities:
685 //
686 // - The assignment is to a bigger access chain than that which is being processed, in
687 // which case the entire right hand side is marked |precise|,
688 // - The assignment is to a smaller access chain, in which case only the subobject of
689 // the right hand side that corresponds to the remaining part of the access chain must
690 // be marked |precise|.
691 //
692 // For example, if processing |a.b.c| as a |precise| access chain:
693 //
694 // - If the assignment is to |a.b.c.d|, then the entire right hand side must be
695 // |precise|.
696 // - If the assignment is to |a.b|, only the |.c| part of the right hand side expression
697 // must be |precise|.
698 // - If the assignment is to |a.e|, there is nothing to do.
699 //
700 AccessChain remainingAccessChain = toProcess.accessChain;
701 if (!remainingAccessChain.removePrefix(assignmentAccessChain))
702 {
703 continue;
704 }
705
706 propagator.propagatePrecise(assignmentNode, remainingAccessChain);
707 }
708 }
709
710 // The AST nodes now contain information gathered by this post-processing step, and so the tree
711 // must no longer be transformed.
712 compiler->enableValidateNoMoreTransformations();
713 }
714
715 } // namespace sh
716