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