xref: /aosp_15_r20/external/angle/src/compiler/translator/tree_util/FindPreciseNodes.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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