Flutter Engine
The Flutter Engine
SkSLCompiler.cpp
Go to the documentation of this file.
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
9
17#include "src/sksl/SkSLParser.h"
18#include "src/sksl/SkSLPool.h"
23#include "src/sksl/ir/SkSLSymbolTable.h" // IWYU pragma: keep
25
26#include <cstdint>
27#include <memory>
28#include <utility>
29
30#if defined(SKSL_STANDALONE)
31#include <fstream>
32#endif
33
34namespace SkSL {
35
36// These flags allow tools like Viewer or Nanobench to override the compiler's ProgramSettings.
39
41public:
43 : fContext(context)
44 , fOldConfig(context.fConfig) {
45 fContext.fConfig = config;
46 }
47
50 }
51
54};
55
56Compiler::Compiler() : fErrorReporter(this) {
57 auto moduleLoader = ModuleLoader::Get();
58 fContext = std::make_shared<Context>(moduleLoader.builtinTypes(), fErrorReporter);
59}
60
62
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);
80 case ProgramKind::kMeshFragment: return m.loadPublicModule(this);
81 }
83}
84
85void Compiler::FinalizeSettings(ProgramSettings* settings, ProgramKind kind) {
86 // Honor our optimization-override flags.
87 switch (sOptimizer) {
89 break;
91 settings->fOptimize = false;
92 break;
94 settings->fOptimize = true;
95 break;
96 }
97
98 switch (sInliner) {
100 break;
102 settings->fInlineThreshold = 0;
103 break;
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.
118 settings->fAllowNarrowingConversions = true;
119 }
120}
121
122void 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
159void 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
175std::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).
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
207std::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
227std::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
247bool 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.
267 }
268
269 // Remove any unreachable code.
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.
296
297 // We can eliminate `{}` around single-statement blocks.
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
306bool 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
332bool 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)
350
351 while (Transform::EliminateDeadFunctions(program)) {
352 // Removing dead functions may cause more functions to become unreferenced. Try again.
353 }
355 // Removing dead variables may cause more variables to become unreferenced. Try again.
356 }
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.
365 }
366
367 return this->errorCount() == 0;
368}
369
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
379bool Compiler::runInliner(Inliner* inliner,
380 const std::vector<std::unique_ptr<ProgramElement>>& elements,
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
398bool Compiler::finalize(Program& program) {
399 // Copy all referenced built-in functions into the Program.
401
402 // Variables defined in modules need their declaring elements added to the program.
404
405 // Structs from module code need to be added to the program's shared elements.
407
408 // Do one last correctness-check pass. This looks for dangling FunctionReference/TypeReference
409 // expressions, and reports them as errors.
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) {
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.
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
433void 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
512std::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
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
AutoreleasePool pool
int count
Definition: FontMgrTest.cpp:50
SkPoint pos
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkASSERT(cond)
Definition: SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
AutoProgramConfig(Context &context, ProgramConfig *config)
ProgramConfig * fOldConfig
std::string errorText(bool showCount=true)
const Module * moduleForProgramKind(ProgramKind kind)
Context & context() const
Definition: SkSLCompiler.h:143
void writeErrorCount()
std::unique_ptr< Module > compileModule(ProgramKind kind, const char *moduleName, std::string moduleSource, const Module *parentModule, bool shouldInline)
bool optimizeModuleBeforeMinifying(ProgramKind kind, Module &module, bool shrinkSymbols)
void runInliner(Program &program)
void resetErrors()
Definition: SkSLCompiler.h:138
void handleError(std::string_view msg, Position pos)
int errorCount() const
Definition: SkSLCompiler.h:134
ErrorReporter & errorReporter()
Definition: SkSLCompiler.h:132
std::unique_ptr< Program > convertProgram(ProgramKind kind, std::string programSource, const ProgramSettings &settings)
ProgramConfig * fConfig
Definition: SkSLContext.h:33
std::string_view source() const
bool analyze(const std::vector< std::unique_ptr< ProgramElement > > &elements, SymbolTable *symbols, ProgramUsage *usage)
static ModuleLoader Get()
std::unique_ptr< Program > programInheritingFrom(const Module *module)
Definition: SkSLParser.cpp:407
std::unique_ptr< Module > moduleInheritingFrom(const Module *parentModule)
Definition: SkSLParser.cpp:418
static std::unique_ptr< Pool > Create()
Definition: SkSLPool.cpp:37
bool valid() const
Definition: SkSLPosition.h:34
SkBitmap source
Definition: examples.cpp:28
if(end==-1)
GAsyncResult * result
void ValidateIndexingForES2(const ProgramElement &pe, ErrorReporter &errors)
std::unique_ptr< ProgramUsage > GetUsage(const Program &program)
void CheckSymbolTableCorrectness(const Program &program)
bool CheckProgramStructure(const Program &program, bool enforceSizeLimit)
void DoFinalizationChecks(const Program &program)
void ReplaceConstVarsWithLiterals(Module &module, ProgramUsage *usage)
bool EliminateDeadLocalVariables(const Context &context, Module &module, ProgramUsage *usage)
void EliminateUnreachableCode(Module &module, ProgramUsage *usage)
bool EliminateDeadFunctions(const Context &context, Module &module, ProgramUsage *usage)
void FindAndDeclareBuiltinFunctions(Program &program)
void EliminateEmptyStatements(Module &module)
void EliminateUnnecessaryBraces(Module &module)
void FindAndDeclareBuiltinVariables(Program &program)
void FindAndDeclareBuiltinStructs(Program &program)
bool EliminateDeadGlobalVariables(const Context &context, Module &module, ProgramUsage *usage, bool onlyPrivateGlobals)
void RenamePrivateSymbols(Context &context, Module &module, ProgramUsage *usage, ProgramKind kind)
static constexpr int kDefaultInlineThreshold
Definition: SkSLDefines.h:38
static SkString to_string(int n)
Definition: nanobench.cpp:119
static void usage(char *argv0)
std::vector< std::unique_ptr< ProgramElement > > fElements
Definition: SkSLCompiler.h:59
std::unique_ptr< SymbolTable > fSymbols
Definition: SkSLCompiler.h:58
static bool IsRuntimeEffect(ProgramKind kind)
std::vector< std::unique_ptr< ProgramElement > > fOwnedElements
Definition: SkSLProgram.h:161
std::unique_ptr< ProgramUsage > fUsage
Definition: SkSLProgram.h:155
std::unique_ptr< SymbolTable > fSymbols
Definition: SkSLProgram.h:158
std::unique_ptr< ProgramConfig > fConfig
Definition: SkSLProgram.h:153
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:131