1 //
2 // Copyright 2024 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
7 #include "compiler/translator/wgsl/RewritePipelineVariables.h"
8
9 #include <string>
10 #include <utility>
11
12 #include "GLES2/gl2.h"
13 #include "GLSLANG/ShaderLang.h"
14 #include "GLSLANG/ShaderVars.h"
15 #include "anglebase/no_destructor.h"
16 #include "common/angleutils.h"
17 #include "common/log_utils.h"
18 #include "compiler/translator/Common.h"
19 #include "compiler/translator/ImmutableString.h"
20 #include "compiler/translator/ImmutableStringBuilder.h"
21 #include "compiler/translator/IntermNode.h"
22 #include "compiler/translator/OutputTree.h"
23 #include "compiler/translator/Symbol.h"
24 #include "compiler/translator/SymbolUniqueId.h"
25 #include "compiler/translator/Types.h"
26 #include "compiler/translator/tree_util/BuiltIn_autogen.h"
27 #include "compiler/translator/tree_util/FindMain.h"
28 #include "compiler/translator/tree_util/ReplaceVariable.h"
29 #include "compiler/translator/util.h"
30 #include "compiler/translator/wgsl/Utils.h"
31
32 namespace sh
33 {
34
35 namespace
36 {
37
38 const bool kOutputVariableUses = false;
39
40 struct LocationAnnotation
41 {
42 // Most variables will not be assigned a location until link time, but some variables (like
43 // gl_FragColor) imply an output location.
44 int location = -1;
45 };
46 struct BuiltinAnnotation
47 {
48 ImmutableString wgslBuiltinName;
49 };
50
51 using PipelineAnnotation = std::variant<LocationAnnotation, BuiltinAnnotation>;
52
53 enum class IOType
54 {
55 Input,
56 Output
57 };
58
59 struct GlslToWgslBuiltinMapping
60 {
61 ImmutableString glslBuiltinName{nullptr};
62 PipelineAnnotation wgslPipelineAnnotation;
63 IOType ioType;
64 const TVariable *builtinVar;
65 // The type from the WGSL spec that corresponds to `wgslPipelineAnnotation`.
66 ImmutableString wgslBuiltinType{nullptr};
67 // The type that is expected by the shader in the AST, i.e. the type of `builtinVar`. If
68 // nullptr, is the same as `wgslBuiltinType`.
69 // TODO(anglebug.com/42267100): delete this and convert `builtinVar`'s type to a WGSL type.
70 ImmutableString wgslTypeExpectedByShader{nullptr};
71 // A function to apply that does one of two thing:
72 // 1. for an input builtin: converts the builtin, as supplied by WGPU, into the variable that
73 // the GLSL shader expects.
74 // 2. for an output builtin: converts the output variable from the GLSL shader into the
75 // builtin supplied back to WGPU.
76 // Can be nullptr for no conversion.
77 ImmutableString conversionFunc{nullptr};
78 };
79
GetWgslBuiltinName(std::string glslBuiltinName,GLenum shaderType,GlslToWgslBuiltinMapping * outMapping)80 bool GetWgslBuiltinName(std::string glslBuiltinName,
81 GLenum shaderType,
82 GlslToWgslBuiltinMapping *outMapping)
83 {
84 static const angle::base::NoDestructor<angle::HashMap<std::string, GlslToWgslBuiltinMapping>>
85 kGlslBuiltinToWgslBuiltinVertex(
86 {{"gl_VertexID",
87 GlslToWgslBuiltinMapping{ImmutableString("gl_VertexID"),
88 BuiltinAnnotation{ImmutableString("vertex_index")},
89 IOType::Input, BuiltInVariable::gl_VertexID(),
90 ImmutableString("u32"), ImmutableString("i32"),
91 ImmutableString("i32")}},
92 {"gl_InstanceID",
93 GlslToWgslBuiltinMapping{ImmutableString("gl_InstanceID"),
94 BuiltinAnnotation{ImmutableString("instance_index")},
95 IOType::Input, BuiltInVariable::gl_InstanceID(),
96 ImmutableString("u32"), ImmutableString("i32"),
97 ImmutableString("i32")}},
98 {"gl_Position",
99 GlslToWgslBuiltinMapping{
100 ImmutableString("gl_Position"), BuiltinAnnotation{ImmutableString("position")},
101 IOType::Output, BuiltInVariable::gl_Position(), ImmutableString("vec4<f32>"),
102 ImmutableString(nullptr), ImmutableString(nullptr)}},
103 // TODO(anglebug.com/42267100): might have to emulate clip_distances, see
104 // Metal's
105 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/angle/src/compiler/translator/msl/TranslatorMSL.cpp?q=symbol%3A%5Cbsh%3A%3AEmulateClipDistanceVaryings%5Cb%20case%3Ayes
106 {"gl_ClipDistance",
107 GlslToWgslBuiltinMapping{ImmutableString("gl_ClipDistance"),
108 BuiltinAnnotation{ImmutableString("clip_distances")},
109 IOType::Output, nullptr, ImmutableString("TODO"),
110 ImmutableString(nullptr), ImmutableString(nullptr)}}});
111 static const angle::base::NoDestructor<angle::HashMap<std::string, GlslToWgslBuiltinMapping>>
112 kGlslBuiltinToWgslBuiltinFragment({
113 {"gl_FragCoord",
114 GlslToWgslBuiltinMapping{ImmutableString("gl_FragCoord"),
115 BuiltinAnnotation{ImmutableString("position")}, IOType::Input,
116 BuiltInVariable::gl_FragCoord(), ImmutableString("vec4<f32>"),
117 ImmutableString(nullptr), ImmutableString(nullptr)}},
118 {"gl_FrontFacing",
119 GlslToWgslBuiltinMapping{ImmutableString("gl_FrontFacing"),
120 BuiltinAnnotation{ImmutableString("front_facing")},
121 IOType::Input, BuiltInVariable::gl_FrontFacing(),
122 ImmutableString("bool"), ImmutableString(nullptr),
123 ImmutableString(nullptr)}},
124 {"gl_SampleID",
125 GlslToWgslBuiltinMapping{
126 ImmutableString("gl_SampleID"), BuiltinAnnotation{ImmutableString("sample_index")},
127 IOType::Input, BuiltInVariable::gl_SampleID(), ImmutableString("u32"),
128 ImmutableString("i32"), ImmutableString("i32")}},
129 // TODO(anglebug.com/42267100): gl_SampleMask is GLSL 4.00 or ARB_sample_shading and
130 // requires some special handling (see Metal).
131 {"gl_SampleMaskIn",
132 GlslToWgslBuiltinMapping{ImmutableString("gl_SampleMaskIn"),
133 BuiltinAnnotation{ImmutableString("sample_mask")},
134 IOType::Input, nullptr, ImmutableString("u32"),
135 ImmutableString("i32"), ImmutableString("i32")}},
136 // Just translate FragColor into a location = 0 out variable.
137 // TODO(anglebug.com/42267100): maybe ASSERT that there are no user-defined output
138 // variables? Is it possible for there to be other output variables when using
139 // FragColor?
140 {"gl_FragColor",
141 GlslToWgslBuiltinMapping{ImmutableString("gl_FragColor"), LocationAnnotation{0},
142 IOType::Output, BuiltInVariable::gl_FragColor(),
143 ImmutableString("vec4<f32>"), ImmutableString(nullptr),
144 ImmutableString(nullptr)}},
145 {"gl_SampleMask",
146 GlslToWgslBuiltinMapping{ImmutableString("gl_SampleMask"),
147 BuiltinAnnotation{ImmutableString("sample_mask")},
148 IOType::Output, nullptr, ImmutableString("u32"),
149 ImmutableString("i32"), ImmutableString("i32")}},
150 {"gl_FragDepth",
151 GlslToWgslBuiltinMapping{
152 ImmutableString("gl_FragDepth"), BuiltinAnnotation{ImmutableString("frag_depth")},
153 IOType::Output, BuiltInVariable::gl_FragDepth(), ImmutableString("f32"),
154 ImmutableString(nullptr), ImmutableString(nullptr)}},
155 });
156 // TODO(anglebug.com/42267100): gl_FragData needs to be emulated. Need something
157 // like spir-v's
158 // third_party/angle/src/compiler/translator/tree_ops/spirv/EmulateFragColorData.h.
159
160 if (shaderType == GL_VERTEX_SHADER)
161 {
162 auto it = kGlslBuiltinToWgslBuiltinVertex->find(glslBuiltinName);
163 if (it == kGlslBuiltinToWgslBuiltinVertex->end())
164 {
165 return false;
166 }
167 *outMapping = it->second;
168 return true;
169 }
170 else if (shaderType == GL_FRAGMENT_SHADER)
171 {
172 auto it = kGlslBuiltinToWgslBuiltinFragment->find(glslBuiltinName);
173 if (it == kGlslBuiltinToWgslBuiltinFragment->end())
174 {
175 return false;
176 }
177 *outMapping = it->second;
178 return true;
179 }
180 else
181 {
182 UNREACHABLE();
183 return false;
184 }
185 }
186
CreateNameToReplaceBuiltin(ImmutableString glslBuiltinName)187 ImmutableString CreateNameToReplaceBuiltin(ImmutableString glslBuiltinName)
188 {
189 ImmutableStringBuilder newName(glslBuiltinName.length() + 1);
190 newName << glslBuiltinName << '_';
191 return newName;
192 }
193
194 } // namespace
195
196 // Friended by RewritePipelineVarOutput
197 class RewritePipelineVarOutputBuilder
198 {
199 public:
200 static bool GenerateMainFunctionAndIOStructs(TCompiler &compiler,
201 TIntermBlock &root,
202 RewritePipelineVarOutput &outVarReplacements);
203
204 private:
205 static bool GeneratePipelineStructStrings(
206 RewritePipelineVarOutput::WgslIOBlock *ioblock,
207 RewritePipelineVarOutput::RewrittenVarSet *varsToReplace,
208 ImmutableString toStruct,
209 ImmutableString fromStruct,
210 const std::vector<ShaderVariable> &shaderVars,
211 const GlobalVars &globalVars,
212 TCompiler &compiler,
213 IOType ioType,
214 std::string debugString);
215 };
216
217 // Given a list of `shaderVars` (as well as `compiler` and a list of global variables in the GLSL
218 // source, `globalVars`), computes the fields that should appear in the input/output pipeline
219 // structs and the annotations that should appear in the WGSL source.
220 //
221 // `ioblock` will be filled with strings that make up the resulting structs, and with the strings
222 // indicated by `fromStruct` and `toStruct`. `varsToReplace` will be filled with the symbols that
223 // should be replaced in the final WGSL source wtih struct accesses.
224 //
225 // Finally, `debugString` should describe `shaderVars` (e.g. "input varyings"), and `ioType`
226 // indicates whether `shaderVars` is meant to be an input or output variable, which is useful for
227 // debugging asserts.
GeneratePipelineStructStrings(RewritePipelineVarOutput::WgslIOBlock * ioblock,RewritePipelineVarOutput::RewrittenVarSet * varsToReplace,ImmutableString toStruct,ImmutableString fromStruct,const std::vector<ShaderVariable> & shaderVars,const GlobalVars & globalVars,TCompiler & compiler,IOType ioType,std::string debugString)228 [[nodiscard]] bool RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings(
229 RewritePipelineVarOutput::WgslIOBlock *ioblock,
230 RewritePipelineVarOutput::RewrittenVarSet *varsToReplace,
231 ImmutableString toStruct,
232 ImmutableString fromStruct,
233 const std::vector<ShaderVariable> &shaderVars,
234 const GlobalVars &globalVars,
235 TCompiler &compiler,
236 IOType ioType,
237 std::string debugString)
238 {
239 for (const ShaderVariable &shaderVar : shaderVars)
240 {
241 if (shaderVar.name == "gl_FragData" || shaderVar.name == "gl_SecondaryFragColorEXT" ||
242 shaderVar.name == "gl_SecondaryFragDataEXT")
243 {
244 // TODO(anglebug.com/42267100): declare gl_FragData as multiple variables.
245 UNIMPLEMENTED();
246 return false;
247 }
248
249 if (kOutputVariableUses)
250 {
251 std::cout << "Use of " << (shaderVar.isBuiltIn() ? "builtin " : "") << debugString
252 << ": " << shaderVar.name << std::endl;
253 }
254
255 if (shaderVar.isBuiltIn())
256 {
257 GlslToWgslBuiltinMapping wgslName;
258 if (!GetWgslBuiltinName(shaderVar.name, compiler.getShaderType(), &wgslName))
259 {
260 return false;
261 }
262
263 const TVariable *varToReplace = wgslName.builtinVar;
264
265 if (varToReplace == nullptr)
266 {
267 // Should be declared somewhere as a symbol.
268 // TODO(anglebug.com/42267100): Not sure if this ever actually occurs. Will this
269 // TVariable also have a declaration? Are there any gl_ variable that require or
270 // even allow declaration?
271 varToReplace = static_cast<const TVariable *>(compiler.getSymbolTable().findBuiltIn(
272 ImmutableString(wgslName.glslBuiltinName), compiler.getShaderVersion()));
273 if (kOutputVariableUses)
274 {
275 std::cout
276 << "Var " << shaderVar.name
277 << " did not have a BuiltIn var but does have a builtin in the symbol "
278 "table"
279 << std::endl;
280 }
281 }
282
283 ASSERT(ioType == wgslName.ioType);
284
285 varsToReplace->insert(varToReplace->uniqueId().get());
286
287 ImmutableString builtinReplacement =
288 CreateNameToReplaceBuiltin(wgslName.glslBuiltinName);
289
290 // E.g. `gl_VertexID_ : i32`.
291 ImmutableString globalType = wgslName.wgslTypeExpectedByShader.empty()
292 ? wgslName.wgslBuiltinType
293 : wgslName.wgslTypeExpectedByShader;
294 ImmutableString globalStructVar =
295 BuildConcatenatedImmutableString(builtinReplacement, " : ", globalType, ",");
296 ioblock->angleGlobalMembers.push_back(globalStructVar);
297
298 if (auto *builtinAnnotation =
299 std::get_if<BuiltinAnnotation>(&wgslName.wgslPipelineAnnotation))
300 {
301 // E.g. `@builtin(vertex_index) gl_VertexID_ : u32,`.
302 const char *builtinAnnotationStart = "@builtin(";
303 const char *builtinAnnotationEnd = ") ";
304 ImmutableString annotatedStructVar = BuildConcatenatedImmutableString(
305 builtinAnnotationStart, builtinAnnotation->wgslBuiltinName,
306 builtinAnnotationEnd, builtinReplacement, " : ", wgslName.wgslBuiltinType, ",");
307 ioblock->angleAnnotatedMembers.push_back(annotatedStructVar);
308 }
309 else
310 {
311 auto &locationAnnotation =
312 std::get<LocationAnnotation>(wgslName.wgslPipelineAnnotation);
313 ASSERT(locationAnnotation.location == 0);
314 // E.g. `@location(0) gl_FragColor_ : vec4<f32>,`.
315 const char *locationAnnotationStr = "@location(0) ";
316 ImmutableString annotatedStructVar =
317 BuildConcatenatedImmutableString(locationAnnotationStr, builtinReplacement,
318 " : ", wgslName.wgslBuiltinType, ",");
319 ioblock->angleAnnotatedMembers.push_back(annotatedStructVar);
320 }
321
322 // E.g. `ANGLE_input_global.gl_VertexID_ = u32(ANGLE_input_annotated.gl_VertexID_);`
323 ImmutableString conversion(nullptr);
324 if (wgslName.conversionFunc.empty())
325 {
326 conversion =
327 BuildConcatenatedImmutableString(toStruct, ".", builtinReplacement, " = ",
328 fromStruct, ".", builtinReplacement, ";");
329 }
330 else
331 {
332 conversion = BuildConcatenatedImmutableString(
333 toStruct, ".", builtinReplacement, " = ", wgslName.conversionFunc, "(",
334 fromStruct, ".", builtinReplacement, ");");
335 }
336 ioblock->angleConversionFuncs.push_back(conversion);
337 }
338 else
339 {
340 if (!shaderVar.active)
341 {
342 // Skip any inactive attributes as they won't be assigned a location anyway.
343 continue;
344 }
345
346 TIntermDeclaration *declNode = globalVars.find(shaderVar.name)->second;
347 const TVariable *astVar = &ViewDeclaration(*declNode).symbol.variable();
348
349 const ImmutableString &userVarName = astVar->name();
350
351 varsToReplace->insert(astVar->uniqueId().get());
352
353 // E.g. `_uuserVar : i32,`.
354 TStringStream typeStream;
355 WriteWgslType(typeStream, astVar->getType());
356 TString type = typeStream.str();
357 ImmutableString globalStructVar =
358 BuildConcatenatedImmutableString(userVarName, " : ", type.c_str(), ",");
359 ioblock->angleGlobalMembers.push_back(globalStructVar);
360
361 // E.g. `@location(@@@@@@) _uuserVar : i32,`.
362 const char *locationAnnotationStr = "@location(@@@@@@) ";
363 ImmutableString annotatedStructVar =
364 BuildConcatenatedImmutableString(locationAnnotationStr, globalStructVar);
365 ioblock->angleAnnotatedMembers.push_back(annotatedStructVar);
366
367 // E.g. `ANGLE_input_global._uuserVar = ANGLE_input_annotated._uuserVar;`
368 ImmutableString conversion = BuildConcatenatedImmutableString(
369 toStruct, ".", userVarName, " = ", fromStruct, ".", userVarName, ";");
370 ioblock->angleConversionFuncs.push_back(conversion);
371 }
372 }
373
374 return true;
375 }
376
GenerateMainFunctionAndIOStructs(TCompiler & compiler,TIntermBlock & root,RewritePipelineVarOutput & outVarReplacements)377 bool RewritePipelineVarOutputBuilder::GenerateMainFunctionAndIOStructs(
378 TCompiler &compiler,
379 TIntermBlock &root,
380 RewritePipelineVarOutput &outVarReplacements)
381 {
382 GlobalVars globalVars = FindGlobalVars(&root);
383
384 if (!RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings(
385 &outVarReplacements.mInputBlock, &outVarReplacements.mAngleInputVars,
386 /*toStruct=*/ImmutableString(kBuiltinInputStructName),
387 /*fromStruct=*/ImmutableString(kBuiltinInputAnnotatedStructName),
388 compiler.getInputVaryings(), globalVars, compiler, IOType::Input, "input varyings") ||
389 !RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings(
390 &outVarReplacements.mInputBlock, &outVarReplacements.mAngleInputVars,
391 /*toStruct=*/ImmutableString(kBuiltinInputStructName),
392 /*fromStruct=*/ImmutableString(kBuiltinInputAnnotatedStructName),
393 compiler.getAttributes(), globalVars, compiler, IOType::Input, "input attributes") ||
394 !RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings(
395 &outVarReplacements.mOutputBlock, &outVarReplacements.mAngleOutputVars,
396 /*toStruct=*/ImmutableString(kBuiltinOutputAnnotatedStructName),
397 /*fromStruct=*/ImmutableString(kBuiltinOutputStructName), compiler.getOutputVaryings(),
398 globalVars, compiler, IOType::Output, "output varyings") ||
399 !RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings(
400 &outVarReplacements.mOutputBlock, &outVarReplacements.mAngleOutputVars,
401 /*toStruct=*/ImmutableString(kBuiltinOutputAnnotatedStructName),
402 /*fromStruct=*/ImmutableString(kBuiltinOutputStructName), compiler.getOutputVariables(),
403 globalVars, compiler, IOType::Output, "output variables"))
404 {
405 return false;
406 }
407
408 return true;
409 }
410
RewritePipelineVarOutput(sh::GLenum shaderType)411 RewritePipelineVarOutput::RewritePipelineVarOutput(sh::GLenum shaderType) : mShaderType(shaderType)
412 {}
413
IsInputVar(TSymbolUniqueId angleInputVar)414 bool RewritePipelineVarOutput::IsInputVar(TSymbolUniqueId angleInputVar)
415 {
416 return mAngleInputVars.count(angleInputVar.get()) > 0;
417 }
IsOutputVar(TSymbolUniqueId angleOutputVar)418 bool RewritePipelineVarOutput::IsOutputVar(TSymbolUniqueId angleOutputVar)
419 {
420 return mAngleOutputVars.count(angleOutputVar.get()) > 0;
421 }
422
423 // static
OutputIOStruct(TInfoSinkBase & output,WgslIOBlock & block,ImmutableString builtinStructType,ImmutableString builtinStructName,ImmutableString builtinAnnotatedStructType)424 bool RewritePipelineVarOutput::OutputIOStruct(TInfoSinkBase &output,
425 WgslIOBlock &block,
426 ImmutableString builtinStructType,
427 ImmutableString builtinStructName,
428 ImmutableString builtinAnnotatedStructType)
429 {
430
431 if (!block.angleGlobalMembers.empty())
432 {
433 // Output global struct definition.
434 ASSERT(block.angleGlobalMembers.size() == block.angleAnnotatedMembers.size());
435 ASSERT(block.angleGlobalMembers.size() == block.angleConversionFuncs.size());
436 output << "struct " << builtinStructType << " {\n";
437 for (const ImmutableString &globalMember : block.angleGlobalMembers)
438 {
439 output << " " << globalMember << "\n";
440 }
441 output << "};\n\n";
442 // Output decl of global struct.
443 output << "var<private> " << builtinStructName << " : " << builtinStructType << ";\n\n";
444 // Output annotated struct definition.
445 output << "struct " << builtinAnnotatedStructType << " {\n";
446 for (const ImmutableString &annotatedMember : block.angleAnnotatedMembers)
447 {
448 output << " " << annotatedMember << "\n";
449 }
450 output << "};\n\n";
451 }
452
453 return true;
454 }
455
OutputStructs(TInfoSinkBase & output)456 bool RewritePipelineVarOutput::OutputStructs(TInfoSinkBase &output)
457 {
458 if (!OutputIOStruct(output, mInputBlock, ImmutableString(kBuiltinInputStructType),
459 ImmutableString(kBuiltinInputStructName),
460 ImmutableString(kBuiltinInputAnnotatedStructType)) ||
461 !OutputIOStruct(output, mOutputBlock, ImmutableString(kBuiltinOutputStructType),
462 ImmutableString(kBuiltinOutputStructName),
463 ImmutableString(kBuiltinOutputAnnotatedStructType)))
464 {
465 return false;
466 }
467
468 return true;
469 }
470
471 // Could split OutputMainFunction() into the different parts of the main function.
OutputMainFunction(TInfoSinkBase & output)472 bool RewritePipelineVarOutput::OutputMainFunction(TInfoSinkBase &output)
473 {
474 if (mShaderType == GL_VERTEX_SHADER)
475 {
476 output << "@vertex\n";
477 }
478 else
479 {
480 ASSERT(mShaderType == GL_FRAGMENT_SHADER);
481 output << "@fragment\n";
482 }
483 output << "fn wgslMain(";
484 if (!mInputBlock.angleGlobalMembers.empty())
485 {
486 output << kBuiltinInputAnnotatedStructName << " : " << kBuiltinInputAnnotatedStructType;
487 }
488 output << ")";
489 if (!mOutputBlock.angleGlobalMembers.empty())
490 {
491 output << " -> " << kBuiltinOutputAnnotatedStructType;
492 }
493 output << "\n{\n";
494 for (const ImmutableString &conversionFunc : mInputBlock.angleConversionFuncs)
495 {
496 output << " " << conversionFunc << "\n";
497 }
498 output << " " << kUserDefinedNamePrefix << "main()" << ";\n";
499
500 if (!mOutputBlock.angleGlobalMembers.empty())
501 {
502 output << " var " << kBuiltinOutputAnnotatedStructName << " : "
503 << kBuiltinOutputAnnotatedStructType << ";\n";
504 for (const ImmutableString &conversionFunc : mOutputBlock.angleConversionFuncs)
505 {
506 output << " " << conversionFunc << "\n";
507 }
508 output << " return " << kBuiltinOutputAnnotatedStructName << ";\n";
509 }
510 output << "}\n";
511 return true;
512 }
513
GenerateMainFunctionAndIOStructs(TCompiler & compiler,TIntermBlock & root,RewritePipelineVarOutput & outVarReplacements)514 bool GenerateMainFunctionAndIOStructs(TCompiler &compiler,
515 TIntermBlock &root,
516 RewritePipelineVarOutput &outVarReplacements)
517 {
518 return RewritePipelineVarOutputBuilder::GenerateMainFunctionAndIOStructs(compiler, root,
519 outVarReplacements);
520 }
521 } // namespace sh
522