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