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