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