xref: /aosp_15_r20/external/skia/src/sksl/SkSLCompiler.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/sksl/SkSLCompiler.h"
9 
10 #include "include/private/base/SkDebug.h"
11 #include "src/core/SkTraceEvent.h"
12 #include "src/sksl/SkSLAnalysis.h"
13 #include "src/sksl/SkSLContext.h"
14 #include "src/sksl/SkSLDefines.h"
15 #include "src/sksl/SkSLInliner.h"
16 #include "src/sksl/SkSLModule.h"
17 #include "src/sksl/SkSLModuleLoader.h"
18 #include "src/sksl/SkSLParser.h"
19 #include "src/sksl/SkSLPool.h"
20 #include "src/sksl/SkSLProgramKind.h"
21 #include "src/sksl/SkSLProgramSettings.h"
22 #include "src/sksl/analysis/SkSLProgramUsage.h"
23 #include "src/sksl/ir/SkSLProgram.h"
24 #include "src/sksl/ir/SkSLProgramElement.h"  // IWYU pragma: keep
25 #include "src/sksl/ir/SkSLSymbolTable.h"     // IWYU pragma: keep
26 #include "src/sksl/transform/SkSLTransform.h"
27 
28 #include <cstdint>
29 #include <memory>
30 #include <utility>
31 
32 #if defined(SKSL_STANDALONE)
33 #include <fstream>
34 #endif
35 
36 namespace SkSL {
37 
38 // These flags allow tools like Viewer or Nanobench to override the compiler's ProgramSettings.
39 Compiler::OverrideFlag Compiler::sOptimizer = OverrideFlag::kDefault;
40 Compiler::OverrideFlag Compiler::sInliner = OverrideFlag::kDefault;
41 
42 class AutoProgramConfig {
43 public:
AutoProgramConfig(Context & context,ProgramConfig * config)44     AutoProgramConfig(Context& context, ProgramConfig* config)
45             : fContext(context)
46             , fOldConfig(context.fConfig) {
47         fContext.fConfig = config;
48     }
49 
~AutoProgramConfig()50     ~AutoProgramConfig() {
51         fContext.fConfig = fOldConfig;
52     }
53 
54     Context& fContext;
55     ProgramConfig* fOldConfig;
56 };
57 
Compiler()58 Compiler::Compiler() : fErrorReporter(this) {
59     auto moduleLoader = ModuleLoader::Get();
60     fContext = std::make_shared<Context>(moduleLoader.builtinTypes(), fErrorReporter);
61 }
62 
~Compiler()63 Compiler::~Compiler() {}
64 
moduleForProgramKind(ProgramKind kind)65 const Module* Compiler::moduleForProgramKind(ProgramKind kind) {
66     auto m = ModuleLoader::Get();
67     switch (kind) {
68         case ProgramKind::kFragment:              return m.loadFragmentModule(this);
69         case ProgramKind::kVertex:                return m.loadVertexModule(this);
70         case ProgramKind::kCompute:               return m.loadComputeModule(this);
71         case ProgramKind::kGraphiteFragment:      return m.loadGraphiteFragmentModule(this);
72         case ProgramKind::kGraphiteVertex:        return m.loadGraphiteVertexModule(this);
73         case ProgramKind::kGraphiteFragmentES2:   return m.loadGraphiteFragmentES2Module(this);
74         case ProgramKind::kGraphiteVertexES2:     return m.loadGraphiteVertexES2Module(this);
75         case ProgramKind::kPrivateRuntimeBlender:
76         case ProgramKind::kPrivateRuntimeColorFilter:
77         case ProgramKind::kPrivateRuntimeShader:  return m.loadPrivateRTShaderModule(this);
78         case ProgramKind::kRuntimeColorFilter:
79         case ProgramKind::kRuntimeShader:
80         case ProgramKind::kRuntimeBlender:
81         case ProgramKind::kMeshVertex:
82         case ProgramKind::kMeshFragment:          return m.loadPublicModule(this);
83     }
84     SkUNREACHABLE;
85 }
86 
FinalizeSettings(ProgramSettings * settings,ProgramKind kind)87 void Compiler::FinalizeSettings(ProgramSettings* settings, ProgramKind kind) {
88     // Honor our optimization-override flags.
89     switch (sOptimizer) {
90         case OverrideFlag::kDefault:
91             break;
92         case OverrideFlag::kOff:
93             settings->fOptimize = false;
94             break;
95         case OverrideFlag::kOn:
96             settings->fOptimize = true;
97             break;
98     }
99 
100     switch (sInliner) {
101         case OverrideFlag::kDefault:
102             break;
103         case OverrideFlag::kOff:
104             settings->fInlineThreshold = 0;
105             break;
106         case OverrideFlag::kOn:
107             if (settings->fInlineThreshold == 0) {
108                 settings->fInlineThreshold = kDefaultInlineThreshold;
109             }
110             break;
111     }
112 
113     // Disable optimization settings that depend on a parent setting which has been disabled.
114     settings->fInlineThreshold *= (int)settings->fOptimize;
115     settings->fRemoveDeadFunctions &= settings->fOptimize;
116     settings->fRemoveDeadVariables &= settings->fOptimize;
117 
118     // Runtime effects always allow narrowing conversions.
119     if (ProgramConfig::IsRuntimeEffect(kind)) {
120         settings->fAllowNarrowingConversions = true;
121     }
122 }
123 
initializeContext(const SkSL::Module * module,ProgramKind kind,ProgramSettings settings,std::string_view source,ModuleType moduleType)124 void Compiler::initializeContext(const SkSL::Module* module,
125                                  ProgramKind kind,
126                                  ProgramSettings settings,
127                                  std::string_view source,
128                                  ModuleType moduleType) {
129     SkASSERT(!fPool);
130     SkASSERT(!fConfig);
131     SkASSERT(!fContext->fSymbolTable);
132     SkASSERT(!fContext->fConfig);
133     SkASSERT(!fContext->fModule);
134 
135     // Start the ErrorReporter with a clean slate.
136     this->resetErrors();
137 
138     fConfig = std::make_unique<ProgramConfig>();
139     fConfig->fModuleType = moduleType;
140     fConfig->fSettings = settings;
141     fConfig->fKind = kind;
142 
143     // Make sure the passed-in settings are valid.
144     FinalizeSettings(&fConfig->fSettings, kind);
145 
146     if (settings.fUseMemoryPool) {
147         fPool = Pool::Create();
148         fPool->attachToThread();
149     }
150 
151     fContext->fConfig = fConfig.get();
152     fContext->fModule = module;
153     fContext->fErrors->setSource(source);
154 
155     // Set up a clean symbol table atop the parent module's symbols.
156     fGlobalSymbols = std::make_unique<SymbolTable>(module->fSymbols.get(),
157                                                    moduleType != ModuleType::program);
158     fGlobalSymbols->markModuleBoundary();
159     fContext->fSymbolTable = fGlobalSymbols.get();
160 }
161 
cleanupContext()162 void Compiler::cleanupContext() {
163     // Clear out the fields we initialized above.
164     fContext->fConfig = nullptr;
165     fContext->fModule = nullptr;
166     fContext->fErrors->setSource(std::string_view());
167     fContext->fSymbolTable = nullptr;
168 
169     fConfig = nullptr;
170     fGlobalSymbols = nullptr;
171 
172     if (fPool) {
173         fPool->detachFromThread();
174         fPool = nullptr;
175     }
176 }
177 
compileModule(ProgramKind kind,ModuleType moduleType,std::string moduleSource,const Module * parentModule,bool shouldInline)178 std::unique_ptr<Module> Compiler::compileModule(ProgramKind kind,
179                                                 ModuleType moduleType,
180                                                 std::string moduleSource,
181                                                 const Module* parentModule,
182                                                 bool shouldInline) {
183     SkASSERT(parentModule);
184     SkASSERT(this->errorCount() == 0);
185 
186     // Wrap the program source in a pointer so it is guaranteed to be stable across moves.
187     auto sourcePtr = std::make_unique<std::string>(std::move(moduleSource));
188 
189     // Compile the module from source, using default program settings (but no memory pooling).
190     ProgramSettings settings;
191     settings.fUseMemoryPool = false;
192     this->initializeContext(parentModule, kind, settings, *sourcePtr, moduleType);
193 
194     std::unique_ptr<Module> module = SkSL::Parser(this, settings, kind, std::move(sourcePtr))
195                                              .moduleInheritingFrom(parentModule);
196 
197     this->cleanupContext();
198 
199     if (this->errorCount() != 0) {
200         SkDebugf("Unexpected errors compiling %s:\n\n%s\n",
201                  ModuleTypeToString(moduleType),
202                  this->errorText().c_str());
203         return nullptr;
204     }
205     if (shouldInline) {
206         this->optimizeModuleAfterLoading(kind, *module);
207     }
208     return module;
209 }
210 
convertProgram(ProgramKind kind,std::string programSource,const ProgramSettings & settings)211 std::unique_ptr<Program> Compiler::convertProgram(ProgramKind kind,
212                                                   std::string programSource,
213                                                   const ProgramSettings& settings) {
214     TRACE_EVENT0("skia.shaders", "SkSL::Compiler::convertProgram");
215 
216     // Wrap the program source in a pointer so it is guaranteed to be stable across moves.
217     auto sourcePtr = std::make_unique<std::string>(std::move(programSource));
218 
219     // Load the module used by this ProgramKind.
220     const SkSL::Module* module = this->moduleForProgramKind(kind);
221 
222     this->initializeContext(module, kind, settings, *sourcePtr, ModuleType::program);
223 
224     std::unique_ptr<Program> program = SkSL::Parser(this, settings, kind, std::move(sourcePtr))
225                                                .programInheritingFrom(module);
226 
227     this->cleanupContext();
228     return program;
229 }
230 
releaseProgram(std::unique_ptr<std::string> source,std::vector<std::unique_ptr<SkSL::ProgramElement>> programElements)231 std::unique_ptr<SkSL::Program> Compiler::releaseProgram(
232         std::unique_ptr<std::string> source,
233         std::vector<std::unique_ptr<SkSL::ProgramElement>> programElements) {
234     Pool* pool = fPool.get();
235     auto result = std::make_unique<SkSL::Program>(std::move(source),
236                                                   std::move(fConfig),
237                                                   fContext,
238                                                   std::move(programElements),
239                                                   std::move(fGlobalSymbols),
240                                                   std::move(fPool));
241     fContext->fSymbolTable = nullptr;
242 
243     bool success = this->finalize(*result) &&
244                    this->optimize(*result);
245     if (pool) {
246         pool->detachFromThread();
247     }
248     return success ? std::move(result) : nullptr;
249 }
250 
optimizeModuleBeforeMinifying(ProgramKind kind,Module & module,bool shrinkSymbols)251 bool Compiler::optimizeModuleBeforeMinifying(ProgramKind kind, Module& module, bool shrinkSymbols) {
252     SkASSERT(this->errorCount() == 0);
253 
254     auto m = SkSL::ModuleLoader::Get();
255 
256     // Create a temporary program configuration with default settings.
257     ProgramConfig config;
258     config.fModuleType = module.fModuleType;
259     config.fKind = kind;
260     AutoProgramConfig autoConfig(this->context(), &config);
261 
262     std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module);
263 
264     if (shrinkSymbols) {
265         // Assign shorter names to symbols as long as it won't change the external meaning of the
266         // code.
267         Transform::RenamePrivateSymbols(this->context(), module, usage.get(), kind);
268 
269         // Replace constant variables with their literal values to save space.
270         Transform::ReplaceConstVarsWithLiterals(module, usage.get());
271     }
272 
273     // Remove any unreachable code.
274     Transform::EliminateUnreachableCode(module, usage.get());
275 
276     // We can only remove dead functions from runtime shaders, since runtime-effect helper functions
277     // are isolated from other parts of the program. In a module, an unreferenced function is
278     // intended to be called by the code that includes the module.
279     if (kind == ProgramKind::kRuntimeShader) {
280         while (Transform::EliminateDeadFunctions(this->context(), module, usage.get())) {
281             // Removing dead functions may cause more functions to become unreferenced. Try again.
282         }
283     }
284 
285     while (Transform::EliminateDeadLocalVariables(this->context(), module, usage.get())) {
286         // Removing dead variables may cause more variables to become unreferenced. Try again.
287     }
288 
289     // Runtime shaders are isolated from other parts of the program via name mangling, so we can
290     // eliminate public globals if they aren't referenced. Otherwise, we only eliminate private
291     // globals (prefixed with `$`) to avoid changing the meaning of the module code.
292     bool onlyPrivateGlobals = !ProgramConfig::IsRuntimeEffect(kind);
293     while (Transform::EliminateDeadGlobalVariables(this->context(), module, usage.get(),
294                                                    onlyPrivateGlobals)) {
295         // Repeat until no changes occur.
296     }
297 
298     // We eliminate empty statements to avoid runs of `;;;;;;` caused by the previous passes.
299     SkSL::Transform::EliminateEmptyStatements(module);
300 
301     // We can eliminate `{}` around single-statement blocks.
302     SkSL::Transform::EliminateUnnecessaryBraces(this->context(), module);
303 
304     // We can convert `float4(myFloat)` with `myFloat.xxxx` to save a few characters.
305     SkSL::Transform::ReplaceSplatCastsWithSwizzles(this->context(), module);
306 
307     // Make sure that program usage is still correct after the optimization pass is complete.
308     SkASSERT(*usage == *Analysis::GetUsage(module));
309 
310     return this->errorCount() == 0;
311 }
312 
optimizeModuleAfterLoading(ProgramKind kind,Module & module)313 bool Compiler::optimizeModuleAfterLoading(ProgramKind kind, Module& module) {
314     SkASSERT(this->errorCount() == 0);
315 
316 #ifndef SK_ENABLE_OPTIMIZE_SIZE
317     // Create a temporary program configuration with default settings.
318     ProgramConfig config;
319     config.fModuleType = module.fModuleType;
320     config.fKind = kind;
321     AutoProgramConfig autoConfig(this->context(), &config);
322 
323     std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module);
324 
325     // Perform inline-candidate analysis and inline any functions deemed suitable.
326     Inliner inliner(fContext.get());
327     while (this->errorCount() == 0) {
328         if (!this->runInliner(&inliner, module.fElements, module.fSymbols.get(), usage.get())) {
329             break;
330         }
331     }
332     // Make sure that program usage is still correct after the optimization pass is complete.
333     SkASSERT(*usage == *Analysis::GetUsage(module));
334 #endif
335 
336     return this->errorCount() == 0;
337 }
338 
optimize(Program & program)339 bool Compiler::optimize(Program& program) {
340     // The optimizer only needs to run when it is enabled.
341     if (!program.fConfig->fSettings.fOptimize) {
342         return true;
343     }
344 
345     SkASSERT(!this->errorCount());
346     if (this->errorCount() == 0) {
347 #ifndef SK_ENABLE_OPTIMIZE_SIZE
348         // Run the inliner only once; it is expensive! Multiple passes can occasionally shake out
349         // more wins, but it's diminishing returns.
350         Inliner inliner(fContext.get());
351         this->runInliner(&inliner, program.fOwnedElements, program.fSymbols.get(),
352                          program.fUsage.get());
353 #endif
354 
355         // Unreachable code can confuse some drivers, so it's worth removing. (skia:12012)
356         Transform::EliminateUnreachableCode(program);
357 
358         while (Transform::EliminateDeadFunctions(program)) {
359             // Removing dead functions may cause more functions to become unreferenced. Try again.
360         }
361         while (Transform::EliminateDeadLocalVariables(program)) {
362             // Removing dead variables may cause more variables to become unreferenced. Try again.
363         }
364         while (Transform::EliminateDeadGlobalVariables(program)) {
365             // Repeat until no changes occur.
366         }
367         // Make sure that program usage is still correct after the optimization pass is complete.
368         SkASSERT(*program.usage() == *Analysis::GetUsage(program));
369 
370         // Make sure that variables are still declared in the correct symbol tables.
371         SkDEBUGCODE(Analysis::CheckSymbolTableCorrectness(program));
372     }
373 
374     return this->errorCount() == 0;
375 }
376 
runInliner(Program & program)377 void Compiler::runInliner(Program& program) {
378 #ifndef SK_ENABLE_OPTIMIZE_SIZE
379     AutoProgramConfig autoConfig(this->context(), program.fConfig.get());
380     Inliner inliner(fContext.get());
381     this->runInliner(&inliner, program.fOwnedElements, program.fSymbols.get(),
382                      program.fUsage.get());
383 #endif
384 }
385 
runInliner(Inliner * inliner,const std::vector<std::unique_ptr<ProgramElement>> & elements,SymbolTable * symbols,ProgramUsage * usage)386 bool Compiler::runInliner(Inliner* inliner,
387                           const std::vector<std::unique_ptr<ProgramElement>>& elements,
388                           SymbolTable* symbols,
389                           ProgramUsage* usage) {
390 #ifdef SK_ENABLE_OPTIMIZE_SIZE
391     return true;
392 #else
393     // The program's SymbolTable was taken out of the context when the program was bundled, but
394     // the inliner creates IR objects which may expect the context to hold a valid SymbolTable.
395     SkASSERT(!fContext->fSymbolTable);
396     fContext->fSymbolTable = symbols;
397 
398     bool result = inliner->analyze(elements, symbols, usage);
399 
400     fContext->fSymbolTable = nullptr;
401     return result;
402 #endif
403 }
404 
finalize(Program & program)405 bool Compiler::finalize(Program& program) {
406     // Copy all referenced built-in functions into the Program.
407     Transform::FindAndDeclareBuiltinFunctions(program);
408 
409     // Variables defined in modules need their declaring elements added to the program.
410     Transform::FindAndDeclareBuiltinVariables(program);
411 
412     // Structs from module code need to be added to the program's shared elements.
413     Transform::FindAndDeclareBuiltinStructs(program);
414 
415     // Do one last correctness-check pass. This looks for dangling FunctionReference/TypeReference
416     // expressions, and reports them as errors.
417     Analysis::DoFinalizationChecks(program);
418 
419     if (fContext->fConfig->strictES2Mode() && this->errorCount() == 0) {
420         // Enforce Appendix A, Section 5 of the GLSL ES 1.00 spec -- Indexing. This logic assumes
421         // that all loops meet the criteria of Section 4, and if they don't, could crash.
422         for (const auto& pe : program.fOwnedElements) {
423             Analysis::ValidateIndexingForES2(*pe, this->errorReporter());
424         }
425     }
426     if (this->errorCount() == 0) {
427         Analysis::CheckProgramStructure(program);
428 
429         // Make sure that variables are declared in the symbol tables that immediately enclose them.
430         SkDEBUGCODE(Analysis::CheckSymbolTableCorrectness(program));
431     }
432 
433     // Make sure that program usage is still correct after finalization is complete.
434     SkASSERT(*program.usage() == *Analysis::GetUsage(program));
435 
436     return this->errorCount() == 0;
437 }
438 
handleError(std::string_view msg,Position pos)439 void Compiler::handleError(std::string_view msg, Position pos) {
440     fErrorText += "error: ";
441     bool printLocation = false;
442     std::string_view src = this->errorReporter().source();
443     int line = -1;
444     if (pos.valid()) {
445         line = pos.line(src);
446         printLocation = pos.startOffset() < (int)src.length();
447         fErrorText += std::to_string(line) + ": ";
448     }
449     fErrorText += std::string(msg) + "\n";
450     if (printLocation) {
451         const int kMaxSurroundingChars = 100;
452 
453         // Find the beginning of the line.
454         int lineStart = pos.startOffset();
455         while (lineStart > 0) {
456             if (src[lineStart - 1] == '\n') {
457                 break;
458             }
459             --lineStart;
460         }
461 
462         // We don't want to show more than 100 characters surrounding the error, so push the line
463         // start forward and add a leading ellipsis if there would be more than this.
464         std::string lineText;
465         std::string caretText;
466         if ((pos.startOffset() - lineStart) > kMaxSurroundingChars) {
467             lineStart = pos.startOffset() - kMaxSurroundingChars;
468             lineText = "...";
469             caretText = "   ";
470         }
471 
472         // Echo the line. Again, we don't want to show more than 100 characters after the end of the
473         // error, so truncate with a trailing ellipsis if needed.
474         const char* lineSuffix = "...\n";
475         int lineStop = pos.endOffset() + kMaxSurroundingChars;
476         if (lineStop >= (int)src.length()) {
477             lineStop = src.length() - 1;
478             lineSuffix = "\n";  // no ellipsis if we reach end-of-file
479         }
480         for (int i = lineStart; i < lineStop; ++i) {
481             char c = src[i];
482             if (c == '\n') {
483                 lineSuffix = "\n";  // no ellipsis if we reach end-of-line
484                 break;
485             }
486             switch (c) {
487                 case '\t': lineText += "    "; break;
488                 case '\0': lineText += " ";    break;
489                 default:   lineText += src[i]; break;
490             }
491         }
492         fErrorText += lineText + lineSuffix;
493 
494         // print the carets underneath it, pointing to the range in question
495         for (int i = lineStart; i < (int)src.length(); i++) {
496             if (i >= pos.endOffset()) {
497                 break;
498             }
499             switch (src[i]) {
500                 case '\t':
501                    caretText += (i >= pos.startOffset()) ? "^^^^" : "    ";
502                    break;
503                 case '\n':
504                     SkASSERT(i >= pos.startOffset());
505                     // use an ellipsis if the error continues past the end of the line
506                     caretText += (pos.endOffset() > i + 1) ? "..." : "^";
507                     i = src.length();
508                     break;
509                 default:
510                     caretText += (i >= pos.startOffset()) ? '^' : ' ';
511                     break;
512             }
513         }
514         fErrorText += caretText + '\n';
515     }
516 }
517 
errorText(bool showCount)518 std::string Compiler::errorText(bool showCount) {
519     if (showCount) {
520         this->writeErrorCount();
521     }
522     std::string result = fErrorText;
523     this->resetErrors();
524     return result;
525 }
526 
writeErrorCount()527 void Compiler::writeErrorCount() {
528     int count = this->errorCount();
529     if (count) {
530         fErrorText += std::to_string(count) +
531                       ((count == 1) ? " error\n" : " errors\n");
532     }
533 }
534 
535 }  // namespace SkSL
536