1 //
2 // Copyright 2018 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 // RewriteStructSamplers: Extract samplers from structs.
7 //
8
9 #include "compiler/translator/tree_ops/RewriteStructSamplers.h"
10
11 #include "GLSLANG/ShaderVars.h"
12 #include "common/hash_containers.h"
13 #include "common/span.h"
14 #include "compiler/translator/Compiler.h"
15 #include "compiler/translator/ImmutableString.h"
16 #include "compiler/translator/ImmutableStringBuilder.h"
17 #include "compiler/translator/SymbolTable.h"
18 #include "compiler/translator/tree_util/IntermNode_util.h"
19 #include "compiler/translator/tree_util/IntermTraverse.h"
20
21 namespace sh
22 {
23 namespace
24 {
25
26 // Used to map one structure type to another (one where the samplers are removed).
27 struct StructureData
28 {
29 // The structure this was replaced with. If nullptr, it means the structure is removed (because
30 // it had all samplers).
31 //
32 // ParseContext reorders the samplers to the end of the struct, so the EOpIndexDirectStruct
33 // expressions that select non-sampler members don't have to change when they are moved out of
34 // the struct.
35 const TStructure *modified;
36 };
37
38 using StructureMap = angle::HashMap<const TStructure *, StructureData>;
39 using StructureUniformMap = angle::HashMap<const TVariable *, const TVariable *>;
40 using ExtractedSamplerMap = angle::HashMap<std::string, const TVariable *>;
41
42 TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
43 TCompiler *compiler,
44 TIntermBinary *node,
45 const StructureMap &structureMap,
46 const StructureUniformMap &structureUniformMap,
47 const ExtractedSamplerMap &extractedSamplers);
48
RewriteExpressionVisitBinaryHelper(TCompiler * compiler,TIntermBinary * node,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)49 TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
50 TIntermBinary *node,
51 const StructureMap &structureMap,
52 const StructureUniformMap &structureUniformMap,
53 const ExtractedSamplerMap &extractedSamplers)
54 {
55 // Only interested in EOpIndex* binary nodes.
56 switch (node->getOp())
57 {
58 case EOpIndexDirectInterfaceBlock:
59 case EOpIndexIndirect:
60 case EOpIndexDirect:
61 case EOpIndexDirectStruct:
62 break;
63 default:
64 return nullptr;
65 }
66
67 const TStructure *structure = node->getLeft()->getType().getStruct();
68 if (structure == nullptr)
69 {
70 return nullptr;
71 }
72
73 // If the result of the index is not a sampler and the struct is not replaced, there's nothing
74 // to do.
75 if (!node->getType().isSampler() && structureMap.find(structure) == structureMap.end())
76 {
77 return nullptr;
78 }
79
80 // Otherwise, replace the whole expression such that:
81 //
82 // - if sampler, it's indexed with whatever indices the parent structs were indexed with,
83 // - otherwise, the chain of field selections is rewritten by modifying the base uniform so all
84 // the intermediate nodes would have the correct type (and therefore fields).
85 ASSERT(structureMap.find(structure) != structureMap.end());
86
87 return RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap,
88 structureUniformMap, extractedSamplers);
89 }
90
91 // Given an expression, this traverser calculates a new expression where sampler-in-structs are
92 // replaced with their extracted ones, and field indices are adjusted for the rest of the fields.
93 // In particular, this is run on the right node of EOpIndexIndirect binary nodes, so that the
94 // expression in the index gets a chance to go through this transformation.
95 class RewriteExpressionTraverser final : public TIntermTraverser
96 {
97 public:
RewriteExpressionTraverser(TCompiler * compiler,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)98 explicit RewriteExpressionTraverser(TCompiler *compiler,
99 const StructureMap &structureMap,
100 const StructureUniformMap &structureUniformMap,
101 const ExtractedSamplerMap &extractedSamplers)
102 : TIntermTraverser(true, false, false),
103 mCompiler(compiler),
104 mStructureMap(structureMap),
105 mStructureUniformMap(structureUniformMap),
106 mExtractedSamplers(extractedSamplers)
107 {}
108
visitBinary(Visit visit,TIntermBinary * node)109 bool visitBinary(Visit visit, TIntermBinary *node) override
110 {
111 TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
112 mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
113
114 if (rewritten == nullptr)
115 {
116 return true;
117 }
118
119 queueReplacement(rewritten, OriginalNode::IS_DROPPED);
120
121 // Don't iterate as the expression is rewritten.
122 return false;
123 }
124
visitSymbol(TIntermSymbol * node)125 void visitSymbol(TIntermSymbol *node) override
126 {
127 // It's impossible to reach here with a symbol that needs replacement.
128 // MonomorphizeUnsupportedFunctions makes sure that whole structs containing
129 // samplers are not passed to functions, so any instance of the struct uniform is
130 // necessarily indexed right away. visitBinary should have already taken care of it.
131 ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
132 }
133
134 private:
135 TCompiler *mCompiler;
136
137 // See RewriteStructSamplersTraverser.
138 const StructureMap &mStructureMap;
139 const StructureUniformMap &mStructureUniformMap;
140 const ExtractedSamplerMap &mExtractedSamplers;
141 };
142
143 // Rewrite the index of an EOpIndexIndirect expression. The root can never need replacing, because
144 // it cannot be a sampler itself or of a struct type.
RewriteIndexExpression(TCompiler * compiler,TIntermTyped * expression,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)145 void RewriteIndexExpression(TCompiler *compiler,
146 TIntermTyped *expression,
147 const StructureMap &structureMap,
148 const StructureUniformMap &structureUniformMap,
149 const ExtractedSamplerMap &extractedSamplers)
150 {
151 RewriteExpressionTraverser traverser(compiler, structureMap, structureUniformMap,
152 extractedSamplers);
153 expression->traverse(&traverser);
154 bool valid = traverser.updateTree(compiler, expression);
155 ASSERT(valid);
156 }
157
158 // Given an expression such as the following:
159 //
160 // EOpIndexDirectStruct (sampler)
161 // / \
162 // EOpIndex* field index
163 // / \
164 // EOpIndexDirectStruct index 2
165 // / \
166 // EOpIndex* field index
167 // / \
168 // EOpIndexDirectStruct index 1
169 // / \
170 // Uniform Struct field index
171 //
172 // produces:
173 //
174 // EOpIndex*
175 // / \
176 // EOpIndex* index 2
177 // / \
178 // sampler index 1
179 //
180 // If the expression is not a sampler, it only replaces the struct with the modified one, while
181 // still processing the EOpIndexIndirect expressions (which may contain more structs to map).
RewriteModifiedStructFieldSelectionExpression(TCompiler * compiler,TIntermBinary * node,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)182 TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
183 TCompiler *compiler,
184 TIntermBinary *node,
185 const StructureMap &structureMap,
186 const StructureUniformMap &structureUniformMap,
187 const ExtractedSamplerMap &extractedSamplers)
188 {
189 const bool isSampler = node->getType().isSampler();
190
191 TIntermSymbol *baseUniform = nullptr;
192 std::string samplerName;
193
194 TVector<TIntermBinary *> indexNodeStack;
195
196 // Iterate once and build the name of the sampler.
197 TIntermBinary *iter = node;
198 while (baseUniform == nullptr)
199 {
200 indexNodeStack.push_back(iter);
201 baseUniform = iter->getLeft()->getAsSymbolNode();
202
203 if (isSampler)
204 {
205 if (iter->getOp() == EOpIndexDirectStruct)
206 {
207 // When indexed into a struct, get the field name instead and construct the sampler
208 // name.
209 samplerName.insert(0, iter->getIndexStructFieldName().data());
210 samplerName.insert(0, "_");
211 }
212
213 if (baseUniform)
214 {
215 // If left is a symbol, we have reached the end of the chain. Use the struct name
216 // to finish building the name of the sampler.
217 samplerName.insert(0, baseUniform->variable().name().data());
218 }
219 }
220
221 iter = iter->getLeft()->getAsBinaryNode();
222 }
223
224 TIntermTyped *rewritten = nullptr;
225
226 if (isSampler)
227 {
228 ASSERT(extractedSamplers.find(samplerName) != extractedSamplers.end());
229 rewritten = new TIntermSymbol(extractedSamplers.at(samplerName));
230 }
231 else
232 {
233 const TVariable *baseUniformVar = &baseUniform->variable();
234 ASSERT(structureUniformMap.find(baseUniformVar) != structureUniformMap.end());
235 rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar));
236 }
237
238 // Iterate again and build the expression from bottom up.
239 for (auto it = indexNodeStack.rbegin(); it != indexNodeStack.rend(); ++it)
240 {
241 TIntermBinary *indexNode = *it;
242
243 switch (indexNode->getOp())
244 {
245 case EOpIndexDirectStruct:
246 if (!isSampler)
247 {
248 rewritten =
249 new TIntermBinary(EOpIndexDirectStruct, rewritten, indexNode->getRight());
250 }
251 break;
252
253 case EOpIndexDirect:
254 rewritten = new TIntermBinary(EOpIndexDirect, rewritten, indexNode->getRight());
255 break;
256
257 case EOpIndexIndirect:
258 {
259 // Run RewriteExpressionTraverser on the right node. It may itself be an expression
260 // with a sampler inside that needs to be rewritten, or simply use a field of a
261 // struct that's remapped.
262 TIntermTyped *indexExpression = indexNode->getRight();
263 RewriteIndexExpression(compiler, indexExpression, structureMap, structureUniformMap,
264 extractedSamplers);
265 rewritten = new TIntermBinary(EOpIndexIndirect, rewritten, indexExpression);
266 break;
267 }
268
269 default:
270 UNREACHABLE();
271 break;
272 }
273 }
274
275 return rewritten;
276 }
277
278 class RewriteStructSamplersTraverser final : public TIntermTraverser
279 {
280 public:
RewriteStructSamplersTraverser(TCompiler * compiler,TSymbolTable * symbolTable)281 explicit RewriteStructSamplersTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
282 : TIntermTraverser(true, false, false, symbolTable),
283 mCompiler(compiler),
284 mRemovedUniformsCount(0)
285 {}
286
removedUniformsCount() const287 int removedUniformsCount() const { return mRemovedUniformsCount; }
288
289 // Each struct sampler declaration is stripped of its samplers. New uniforms are added for each
290 // stripped struct sampler.
visitDeclaration(Visit visit,TIntermDeclaration * decl)291 bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override
292 {
293 if (!mInGlobalScope)
294 {
295 return true;
296 }
297
298 const TIntermSequence &sequence = *(decl->getSequence());
299 TIntermTyped *declarator = sequence.front()->getAsTyped();
300 const TType &type = declarator->getType();
301
302 if (!type.isStructureContainingSamplers())
303 {
304 return false;
305 }
306
307 TIntermSequence newSequence;
308
309 if (type.isStructSpecifier())
310 {
311 // If this is just a struct definition (not a uniform variable declaration of a
312 // struct type), just remove the samplers. They are not instantiated yet.
313 const TStructure *structure = type.getStruct();
314 ASSERT(structure && mStructureMap.find(structure) == mStructureMap.end());
315
316 stripStructSpecifierSamplers(structure, &newSequence);
317 }
318 else
319 {
320 const TStructure *structure = type.getStruct();
321
322 // If the structure is defined at the same time, create the mapping to the stripped
323 // version first.
324 if (mStructureMap.find(structure) == mStructureMap.end())
325 {
326 stripStructSpecifierSamplers(structure, &newSequence);
327 }
328
329 // Then, extract the samplers from the struct and create global-scope variables instead.
330 TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
331 ASSERT(asSymbol);
332 const TVariable &variable = asSymbol->variable();
333 ASSERT(variable.symbolType() != SymbolType::Empty);
334
335 extractStructSamplerUniforms(variable, structure, &newSequence);
336 }
337
338 mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl,
339 std::move(newSequence));
340
341 return false;
342 }
343
344 // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root.
visitBinary(Visit visit,TIntermBinary * node)345 bool visitBinary(Visit visit, TIntermBinary *node) override
346 {
347 TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
348 mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
349
350 if (rewritten == nullptr)
351 {
352 return true;
353 }
354
355 queueReplacement(rewritten, OriginalNode::IS_DROPPED);
356
357 // Don't iterate as the expression is rewritten.
358 return false;
359 }
360
361 // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root.
visitSymbol(TIntermSymbol * node)362 void visitSymbol(TIntermSymbol *node) override
363 {
364 auto replacement = mStructureUniformMap.find(&node->variable());
365 if (replacement != mStructureUniformMap.end())
366 {
367 // This is a reference to the whole struct, just replace it with its replacement.
368 queueReplacement(new TIntermSymbol(replacement->second), OriginalNode::IS_DROPPED);
369 }
370 }
371
372 private:
isActiveUniform(const ImmutableString & rootStructureName)373 bool isActiveUniform(const ImmutableString &rootStructureName)
374 {
375 if (!mActiveUniforms)
376 {
377 mActiveUniforms = new TSet<ImmutableString>();
378 for (const ShaderVariable &uniform : mCompiler->getUniforms())
379 {
380 if (uniform.active)
381 {
382 mActiveUniforms->insert(uniform.name);
383 }
384 }
385 }
386
387 return mActiveUniforms->count(rootStructureName) > 0;
388 }
389
390 // Removes all samplers from a struct specifier.
stripStructSpecifierSamplers(const TStructure * structure,TIntermSequence * newSequence)391 void stripStructSpecifierSamplers(const TStructure *structure, TIntermSequence *newSequence)
392 {
393 TFieldList *newFieldList = new TFieldList;
394 ASSERT(structure->containsSamplers());
395
396 // Add this struct to the struct map
397 ASSERT(mStructureMap.find(structure) == mStructureMap.end());
398 StructureData *modifiedData = &mStructureMap[structure];
399
400 modifiedData->modified = nullptr;
401
402 for (size_t fieldIndex = 0; fieldIndex < structure->fields().size(); ++fieldIndex)
403 {
404 const TField *field = structure->fields()[fieldIndex];
405 const TType &fieldType = *field->type();
406
407 // If the field is a sampler, or a struct that's entirely removed, skip it.
408 if (!fieldType.isSampler() && !isRemovedStructType(fieldType))
409 {
410 TType *newType = nullptr;
411
412 // Otherwise, if it's a struct that's replaced, create a new field of the replaced
413 // type.
414 if (fieldType.isStructureContainingSamplers())
415 {
416 const TStructure *fieldStruct = fieldType.getStruct();
417 ASSERT(mStructureMap.find(fieldStruct) != mStructureMap.end());
418
419 const TStructure *modifiedStruct = mStructureMap[fieldStruct].modified;
420 ASSERT(modifiedStruct);
421
422 newType = new TType(modifiedStruct, true);
423 if (fieldType.isArray())
424 {
425 newType->makeArrays(fieldType.getArraySizes());
426 }
427 }
428 else
429 {
430 // If not, duplicate the field as is.
431 newType = new TType(fieldType);
432 }
433
434 TField *newField =
435 new TField(newType, field->name(), field->line(), field->symbolType());
436 newFieldList->push_back(newField);
437 }
438 }
439
440 // Prune empty structs.
441 if (newFieldList->empty())
442 {
443 return;
444 }
445
446 // Declare a new struct with the same name and the new fields.
447 modifiedData->modified =
448 new TStructure(mSymbolTable,
449 structure->symbolType() == SymbolType::Empty ? kEmptyImmutableString
450 : structure->name(),
451 newFieldList, structure->symbolType());
452 TType *newStructType = new TType(modifiedData->modified, true);
453 TVariable *newStructVar =
454 new TVariable(mSymbolTable, kEmptyImmutableString, newStructType, SymbolType::Empty);
455 TIntermSymbol *newStructRef = new TIntermSymbol(newStructVar);
456
457 TIntermDeclaration *structDecl = new TIntermDeclaration;
458 structDecl->appendDeclarator(newStructRef);
459
460 newSequence->push_back(structDecl);
461 }
462
463 // Returns true if the type is a struct that was removed because we extracted all the members.
isRemovedStructType(const TType & type) const464 bool isRemovedStructType(const TType &type) const
465 {
466 const TStructure *structure = type.getStruct();
467 if (structure == nullptr)
468 {
469 // Not a struct
470 return false;
471 }
472
473 // A struct is removed if it is in the map, but doesn't have a replacement struct.
474 auto iter = mStructureMap.find(structure);
475 return iter != mStructureMap.end() && iter->second.modified == nullptr;
476 }
477
478 // Removes samplers from struct uniforms. For each sampler removed also adds a new globally
479 // defined sampler uniform.
extractStructSamplerUniforms(const TVariable & variable,const TStructure * structure,TIntermSequence * newSequence)480 void extractStructSamplerUniforms(const TVariable &variable,
481 const TStructure *structure,
482 TIntermSequence *newSequence)
483 {
484 ASSERT(structure->containsSamplers());
485 ASSERT(mStructureMap.find(structure) != mStructureMap.end());
486
487 const TType &type = variable.getType();
488 enterArray(type);
489
490 for (const TField *field : structure->fields())
491 {
492 extractFieldSamplers(isActiveUniform(variable.name()), variable.name().data(), field,
493 newSequence);
494 }
495
496 // If there's a replacement structure (because there are non-sampler fields in the struct),
497 // add a declaration with that type.
498 const TStructure *modified = mStructureMap[structure].modified;
499 if (modified != nullptr)
500 {
501 TType *newType = new TType(modified, false);
502 if (type.isArray())
503 {
504 newType->makeArrays(type.getArraySizes());
505 }
506 newType->setQualifier(EvqUniform);
507 const TVariable *newVariable =
508 new TVariable(mSymbolTable, variable.name(), newType, variable.symbolType());
509
510 TIntermDeclaration *newDecl = new TIntermDeclaration();
511 newDecl->appendDeclarator(new TIntermSymbol(newVariable));
512
513 newSequence->push_back(newDecl);
514
515 ASSERT(mStructureUniformMap.find(&variable) == mStructureUniformMap.end());
516 mStructureUniformMap[&variable] = newVariable;
517 }
518 else
519 {
520 mRemovedUniformsCount++;
521 }
522
523 exitArray(type);
524 }
525
526 // Extracts samplers from a field of a struct. Works with nested structs and arrays.
extractFieldSamplers(bool inActiveUniform,const std::string & prefix,const TField * field,TIntermSequence * newSequence)527 void extractFieldSamplers(bool inActiveUniform,
528 const std::string &prefix,
529 const TField *field,
530 TIntermSequence *newSequence)
531 {
532 const TType &fieldType = *field->type();
533 if (fieldType.isSampler() || fieldType.isStructureContainingSamplers())
534 {
535 std::string newPrefix = prefix + "_" + field->name().data();
536
537 if (fieldType.isSampler())
538 {
539 if (inActiveUniform)
540 {
541 extractSampler(newPrefix, fieldType, newSequence);
542 }
543 }
544 else
545 {
546 enterArray(fieldType);
547 const TStructure *structure = fieldType.getStruct();
548 for (const TField *nestedField : structure->fields())
549 {
550 extractFieldSamplers(inActiveUniform, newPrefix, nestedField, newSequence);
551 }
552 exitArray(fieldType);
553 }
554 }
555 }
556
GenerateArraySizesFromStack(TVector<unsigned int> * sizesOut)557 void GenerateArraySizesFromStack(TVector<unsigned int> *sizesOut)
558 {
559 sizesOut->reserve(mArraySizeStack.size());
560
561 for (auto it = mArraySizeStack.rbegin(); it != mArraySizeStack.rend(); ++it)
562 {
563 sizesOut->push_back(*it);
564 }
565 }
566
567 // Extracts a sampler from a struct. Declares the new extracted sampler.
extractSampler(const std::string & newName,const TType & fieldType,TIntermSequence * newSequence)568 void extractSampler(const std::string &newName,
569 const TType &fieldType,
570 TIntermSequence *newSequence)
571 {
572 ASSERT(fieldType.isSampler());
573
574 TType *newType = new TType(fieldType);
575
576 // Add array dimensions accumulated so far due to struct arrays. Note that to support
577 // nested arrays, mArraySizeStack has the outermost size in the front. |makeArrays| thus
578 // expects this in reverse order.
579 TVector<unsigned int> parentArraySizes;
580 GenerateArraySizesFromStack(&parentArraySizes);
581 newType->makeArrays(parentArraySizes);
582
583 ImmutableStringBuilder nameBuilder(newName.size() + 1);
584 nameBuilder << newName;
585
586 newType->setQualifier(EvqUniform);
587 TVariable *newVariable =
588 new TVariable(mSymbolTable, nameBuilder, newType, SymbolType::AngleInternal);
589 TIntermSymbol *newSymbol = new TIntermSymbol(newVariable);
590
591 TIntermDeclaration *samplerDecl = new TIntermDeclaration;
592 samplerDecl->appendDeclarator(newSymbol);
593
594 newSequence->push_back(samplerDecl);
595
596 // TODO: Use a temp name instead of generating a name as currently done. There is no
597 // guarantee that these generated names cannot clash. Create a mapping from the previous
598 // name to the name assigned to the temp variable so ShaderVariable::mappedName can be
599 // updated post-transformation. http://anglebug.com/42262930
600 ASSERT(mExtractedSamplers.find(newName) == mExtractedSamplers.end());
601 mExtractedSamplers[newName] = newVariable;
602 }
603
enterArray(const TType & arrayType)604 void enterArray(const TType &arrayType)
605 {
606 const angle::Span<const unsigned int> &arraySizes = arrayType.getArraySizes();
607 for (auto it = arraySizes.rbegin(); it != arraySizes.rend(); ++it)
608 {
609 unsigned int arraySize = *it;
610 mArraySizeStack.push_back(arraySize);
611 }
612 }
613
exitArray(const TType & arrayType)614 void exitArray(const TType &arrayType)
615 {
616 mArraySizeStack.resize(mArraySizeStack.size() - arrayType.getNumArraySizes());
617 }
618
619 TCompiler *mCompiler;
620 int mRemovedUniformsCount;
621
622 // Map structures with samplers to ones that have their samplers removed.
623 StructureMap mStructureMap;
624
625 // Map uniform variables of structure type that are replaced with another variable.
626 StructureUniformMap mStructureUniformMap;
627
628 // Map a constructed sampler name to its variable. Used to replace an expression that uses this
629 // sampler with the extracted one.
630 ExtractedSamplerMap mExtractedSamplers;
631
632 // A stack of array sizes. Used to figure out the array dimensions of the extracted sampler,
633 // for example when it's nested in an array of structs in an array of structs.
634 TVector<unsigned int> mArraySizeStack;
635
636 // Caches the names of all inactive uniforms.
637 TSet<ImmutableString> *mActiveUniforms = nullptr;
638 };
639 } // anonymous namespace
640
RewriteStructSamplers(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable,int * removedUniformsCountOut)641 bool RewriteStructSamplers(TCompiler *compiler,
642 TIntermBlock *root,
643 TSymbolTable *symbolTable,
644 int *removedUniformsCountOut)
645 {
646 RewriteStructSamplersTraverser traverser(compiler, symbolTable);
647 root->traverse(&traverser);
648 *removedUniformsCountOut = traverser.removedUniformsCount();
649 return traverser.updateTree(compiler, root);
650 }
651 } // namespace sh
652