| /* |
| * Copyright 2016 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/sksl/SkSLCompiler.h" |
| |
| #include "include/private/base/SkDebug.h" |
| #include "src/core/SkTraceEvent.h" |
| #include "src/sksl/SkSLAnalysis.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/SkSLDefines.h" |
| #include "src/sksl/SkSLInliner.h" |
| #include "src/sksl/SkSLModuleLoader.h" |
| #include "src/sksl/SkSLParser.h" |
| #include "src/sksl/SkSLPool.h" |
| #include "src/sksl/SkSLProgramKind.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/analysis/SkSLProgramUsage.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| #include "src/sksl/ir/SkSLSymbolTable.h" // IWYU pragma: keep |
| #include "src/sksl/transform/SkSLTransform.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <utility> |
| |
| #if defined(SKSL_STANDALONE) |
| #include <fstream> |
| #endif |
| |
| namespace SkSL { |
| |
| // These flags allow tools like Viewer or Nanobench to override the compiler's ProgramSettings. |
| Compiler::OverrideFlag Compiler::sOptimizer = OverrideFlag::kDefault; |
| Compiler::OverrideFlag Compiler::sInliner = OverrideFlag::kDefault; |
| |
| class AutoProgramConfig { |
| public: |
| AutoProgramConfig(Context& context, ProgramConfig* config) |
| : fContext(context) |
| , fOldConfig(context.fConfig) { |
| fContext.fConfig = config; |
| } |
| |
| ~AutoProgramConfig() { |
| fContext.fConfig = fOldConfig; |
| } |
| |
| Context& fContext; |
| ProgramConfig* fOldConfig; |
| }; |
| |
| Compiler::Compiler() : fErrorReporter(this) { |
| auto moduleLoader = ModuleLoader::Get(); |
| fContext = std::make_shared<Context>(moduleLoader.builtinTypes(), fErrorReporter); |
| } |
| |
| Compiler::~Compiler() {} |
| |
| const Module* Compiler::moduleForProgramKind(ProgramKind kind) { |
| auto m = ModuleLoader::Get(); |
| switch (kind) { |
| case ProgramKind::kFragment: return m.loadFragmentModule(this); |
| case ProgramKind::kVertex: return m.loadVertexModule(this); |
| case ProgramKind::kCompute: return m.loadComputeModule(this); |
| case ProgramKind::kGraphiteFragment: return m.loadGraphiteFragmentModule(this); |
| case ProgramKind::kGraphiteVertex: return m.loadGraphiteVertexModule(this); |
| case ProgramKind::kGraphiteFragmentES2: return m.loadGraphiteFragmentES2Module(this); |
| case ProgramKind::kGraphiteVertexES2: return m.loadGraphiteVertexES2Module(this); |
| case ProgramKind::kPrivateRuntimeShader: return m.loadPrivateRTShaderModule(this); |
| case ProgramKind::kRuntimeColorFilter: |
| case ProgramKind::kRuntimeShader: |
| case ProgramKind::kRuntimeBlender: |
| case ProgramKind::kPrivateRuntimeColorFilter: |
| case ProgramKind::kPrivateRuntimeBlender: |
| case ProgramKind::kMeshVertex: |
| case ProgramKind::kMeshFragment: return m.loadPublicModule(this); |
| } |
| SkUNREACHABLE; |
| } |
| |
| void Compiler::FinalizeSettings(ProgramSettings* settings, ProgramKind kind) { |
| // Honor our optimization-override flags. |
| switch (sOptimizer) { |
| case OverrideFlag::kDefault: |
| break; |
| case OverrideFlag::kOff: |
| settings->fOptimize = false; |
| break; |
| case OverrideFlag::kOn: |
| settings->fOptimize = true; |
| break; |
| } |
| |
| switch (sInliner) { |
| case OverrideFlag::kDefault: |
| break; |
| case OverrideFlag::kOff: |
| settings->fInlineThreshold = 0; |
| break; |
| case OverrideFlag::kOn: |
| if (settings->fInlineThreshold == 0) { |
| settings->fInlineThreshold = kDefaultInlineThreshold; |
| } |
| break; |
| } |
| |
| // Disable optimization settings that depend on a parent setting which has been disabled. |
| settings->fInlineThreshold *= (int)settings->fOptimize; |
| settings->fRemoveDeadFunctions &= settings->fOptimize; |
| settings->fRemoveDeadVariables &= settings->fOptimize; |
| |
| // Runtime effects always allow narrowing conversions. |
| if (ProgramConfig::IsRuntimeEffect(kind)) { |
| settings->fAllowNarrowingConversions = true; |
| } |
| } |
| |
| void Compiler::initializeContext(const SkSL::Module* module, |
| ProgramKind kind, |
| ProgramSettings settings, |
| std::string_view source, |
| bool isModule) { |
| SkASSERT(!fPool); |
| SkASSERT(!fConfig); |
| SkASSERT(!fContext->fSymbolTable); |
| SkASSERT(!fContext->fConfig); |
| SkASSERT(!fContext->fModule); |
| |
| // Start the ErrorReporter with a clean slate. |
| this->resetErrors(); |
| |
| fConfig = std::make_unique<ProgramConfig>(); |
| fConfig->fIsBuiltinCode = isModule; |
| fConfig->fSettings = settings; |
| fConfig->fKind = kind; |
| |
| // Make sure the passed-in settings are valid. |
| FinalizeSettings(&fConfig->fSettings, kind); |
| |
| if (settings.fUseMemoryPool) { |
| fPool = Pool::Create(); |
| fPool->attachToThread(); |
| } |
| |
| fContext->fConfig = fConfig.get(); |
| fContext->fModule = module; |
| fContext->fErrors->setSource(source); |
| |
| // Set up a clean symbol table atop the parent module's symbols. |
| fGlobalSymbols = std::make_unique<SymbolTable>(module->fSymbols.get(), isModule); |
| fGlobalSymbols->markModuleBoundary(); |
| fContext->fSymbolTable = fGlobalSymbols.get(); |
| } |
| |
| void Compiler::cleanupContext() { |
| // Clear out the fields we initialized above. |
| fContext->fConfig = nullptr; |
| fContext->fModule = nullptr; |
| fContext->fErrors->setSource(std::string_view()); |
| fContext->fSymbolTable = nullptr; |
| |
| fConfig = nullptr; |
| fGlobalSymbols = nullptr; |
| |
| if (fPool) { |
| fPool->detachFromThread(); |
| fPool = nullptr; |
| } |
| } |
| |
| std::unique_ptr<Module> Compiler::compileModule(ProgramKind kind, |
| const char* moduleName, |
| std::string moduleSource, |
| const Module* parentModule, |
| bool shouldInline) { |
| SkASSERT(parentModule); |
| SkASSERT(!moduleSource.empty()); |
| SkASSERT(this->errorCount() == 0); |
| |
| // Wrap the program source in a pointer so it is guaranteed to be stable across moves. |
| auto sourcePtr = std::make_unique<std::string>(std::move(moduleSource)); |
| |
| // Compile the module from source, using default program settings (but no memory pooling). |
| ProgramSettings settings; |
| settings.fUseMemoryPool = false; |
| this->initializeContext(parentModule, kind, settings, *sourcePtr, /*isModule=*/true); |
| |
| std::unique_ptr<Module> module = SkSL::Parser(this, settings, kind, std::move(sourcePtr)) |
| .moduleInheritingFrom(parentModule); |
| |
| this->cleanupContext(); |
| |
| if (this->errorCount() != 0) { |
| SkDebugf("Unexpected errors compiling %s:\n\n%s\n", moduleName, this->errorText().c_str()); |
| return nullptr; |
| } |
| if (shouldInline) { |
| this->optimizeModuleAfterLoading(kind, *module); |
| } |
| return module; |
| } |
| |
| std::unique_ptr<Program> Compiler::convertProgram(ProgramKind kind, |
| std::string programSource, |
| const ProgramSettings& settings) { |
| TRACE_EVENT0("skia.shaders", "SkSL::Compiler::convertProgram"); |
| |
| // Wrap the program source in a pointer so it is guaranteed to be stable across moves. |
| auto sourcePtr = std::make_unique<std::string>(std::move(programSource)); |
| |
| // Load the module used by this ProgramKind. |
| const SkSL::Module* module = this->moduleForProgramKind(kind); |
| |
| this->initializeContext(module, kind, settings, *sourcePtr, /*isModule=*/false); |
| |
| std::unique_ptr<Program> program = SkSL::Parser(this, settings, kind, std::move(sourcePtr)) |
| .programInheritingFrom(module); |
| |
| this->cleanupContext(); |
| return program; |
| } |
| |
| std::unique_ptr<SkSL::Program> Compiler::releaseProgram( |
| std::unique_ptr<std::string> source, |
| std::vector<std::unique_ptr<SkSL::ProgramElement>> programElements) { |
| Pool* pool = fPool.get(); |
| auto result = std::make_unique<SkSL::Program>(std::move(source), |
| std::move(fConfig), |
| fContext, |
| std::move(programElements), |
| std::move(fGlobalSymbols), |
| std::move(fPool)); |
| fContext->fSymbolTable = nullptr; |
| |
| bool success = this->finalize(*result) && |
| this->optimize(*result); |
| if (pool) { |
| pool->detachFromThread(); |
| } |
| return success ? std::move(result) : nullptr; |
| } |
| |
| bool Compiler::optimizeModuleBeforeMinifying(ProgramKind kind, Module& module, bool shrinkSymbols) { |
| SkASSERT(this->errorCount() == 0); |
| |
| auto m = SkSL::ModuleLoader::Get(); |
| |
| // Create a temporary program configuration with default settings. |
| ProgramConfig config; |
| config.fIsBuiltinCode = true; |
| config.fKind = kind; |
| AutoProgramConfig autoConfig(this->context(), &config); |
| |
| std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module); |
| |
| if (shrinkSymbols) { |
| // Assign shorter names to symbols as long as it won't change the external meaning of the |
| // code. |
| Transform::RenamePrivateSymbols(this->context(), module, usage.get(), kind); |
| |
| // Replace constant variables with their literal values to save space. |
| Transform::ReplaceConstVarsWithLiterals(module, usage.get()); |
| } |
| |
| // Remove any unreachable code. |
| Transform::EliminateUnreachableCode(module, usage.get()); |
| |
| // We can only remove dead functions from runtime shaders, since runtime-effect helper functions |
| // are isolated from other parts of the program. In a module, an unreferenced function is |
| // intended to be called by the code that includes the module. |
| if (kind == ProgramKind::kRuntimeShader) { |
| while (Transform::EliminateDeadFunctions(this->context(), module, usage.get())) { |
| // Removing dead functions may cause more functions to become unreferenced. Try again. |
| } |
| } |
| |
| while (Transform::EliminateDeadLocalVariables(this->context(), module, usage.get())) { |
| // Removing dead variables may cause more variables to become unreferenced. Try again. |
| } |
| |
| // Runtime shaders are isolated from other parts of the program via name mangling, so we can |
| // eliminate public globals if they aren't referenced. Otherwise, we only eliminate private |
| // globals (prefixed with `$`) to avoid changing the meaning of the module code. |
| bool onlyPrivateGlobals = !ProgramConfig::IsRuntimeEffect(kind); |
| while (Transform::EliminateDeadGlobalVariables(this->context(), module, usage.get(), |
| onlyPrivateGlobals)) { |
| // Repeat until no changes occur. |
| } |
| |
| // We eliminate empty statements to avoid runs of `;;;;;;` caused by the previous passes. |
| SkSL::Transform::EliminateEmptyStatements(module); |
| |
| // We can eliminate `{}` around single-statement blocks. |
| SkSL::Transform::EliminateUnnecessaryBraces(module); |
| |
| // Make sure that program usage is still correct after the optimization pass is complete. |
| SkASSERT(*usage == *Analysis::GetUsage(module)); |
| |
| return this->errorCount() == 0; |
| } |
| |
| bool Compiler::optimizeModuleAfterLoading(ProgramKind kind, Module& module) { |
| SkASSERT(this->errorCount() == 0); |
| |
| #ifndef SK_ENABLE_OPTIMIZE_SIZE |
| // Create a temporary program configuration with default settings. |
| ProgramConfig config; |
| config.fIsBuiltinCode = true; |
| config.fKind = kind; |
| AutoProgramConfig autoConfig(this->context(), &config); |
| |
| std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module); |
| |
| // Perform inline-candidate analysis and inline any functions deemed suitable. |
| Inliner inliner(fContext.get()); |
| while (this->errorCount() == 0) { |
| if (!this->runInliner(&inliner, module.fElements, module.fSymbols.get(), usage.get())) { |
| break; |
| } |
| } |
| // Make sure that program usage is still correct after the optimization pass is complete. |
| SkASSERT(*usage == *Analysis::GetUsage(module)); |
| #endif |
| |
| return this->errorCount() == 0; |
| } |
| |
| bool Compiler::optimize(Program& program) { |
| // The optimizer only needs to run when it is enabled. |
| if (!program.fConfig->fSettings.fOptimize) { |
| return true; |
| } |
| |
| SkASSERT(!this->errorCount()); |
| if (this->errorCount() == 0) { |
| #ifndef SK_ENABLE_OPTIMIZE_SIZE |
| // Run the inliner only once; it is expensive! Multiple passes can occasionally shake out |
| // more wins, but it's diminishing returns. |
| Inliner inliner(fContext.get()); |
| this->runInliner(&inliner, program.fOwnedElements, program.fSymbols.get(), |
| program.fUsage.get()); |
| #endif |
| |
| // Unreachable code can confuse some drivers, so it's worth removing. (skia:12012) |
| Transform::EliminateUnreachableCode(program); |
| |
| while (Transform::EliminateDeadFunctions(program)) { |
| // Removing dead functions may cause more functions to become unreferenced. Try again. |
| } |
| while (Transform::EliminateDeadLocalVariables(program)) { |
| // Removing dead variables may cause more variables to become unreferenced. Try again. |
| } |
| while (Transform::EliminateDeadGlobalVariables(program)) { |
| // Repeat until no changes occur. |
| } |
| // Make sure that program usage is still correct after the optimization pass is complete. |
| SkASSERT(*program.usage() == *Analysis::GetUsage(program)); |
| |
| // Make sure that variables are still declared in the correct symbol tables. |
| SkDEBUGCODE(Analysis::CheckSymbolTableCorrectness(program)); |
| } |
| |
| return this->errorCount() == 0; |
| } |
| |
| void Compiler::runInliner(Program& program) { |
| #ifndef SK_ENABLE_OPTIMIZE_SIZE |
| AutoProgramConfig autoConfig(this->context(), program.fConfig.get()); |
| Inliner inliner(fContext.get()); |
| this->runInliner(&inliner, program.fOwnedElements, program.fSymbols.get(), |
| program.fUsage.get()); |
| #endif |
| } |
| |
| bool Compiler::runInliner(Inliner* inliner, |
| const std::vector<std::unique_ptr<ProgramElement>>& elements, |
| SymbolTable* symbols, |
| ProgramUsage* usage) { |
| #ifdef SK_ENABLE_OPTIMIZE_SIZE |
| return true; |
| #else |
| // The program's SymbolTable was taken out of the context when the program was bundled, but |
| // the inliner creates IR objects which may expect the context to hold a valid SymbolTable. |
| SkASSERT(!fContext->fSymbolTable); |
| fContext->fSymbolTable = symbols; |
| |
| bool result = inliner->analyze(elements, symbols, usage); |
| |
| fContext->fSymbolTable = nullptr; |
| return result; |
| #endif |
| } |
| |
| bool Compiler::finalize(Program& program) { |
| // Copy all referenced built-in functions into the Program. |
| Transform::FindAndDeclareBuiltinFunctions(program); |
| |
| // Variables defined in modules need their declaring elements added to the program. |
| Transform::FindAndDeclareBuiltinVariables(program); |
| |
| // Structs from module code need to be added to the program's shared elements. |
| Transform::FindAndDeclareBuiltinStructs(program); |
| |
| // Do one last correctness-check pass. This looks for dangling FunctionReference/TypeReference |
| // expressions, and reports them as errors. |
| Analysis::DoFinalizationChecks(program); |
| |
| if (fContext->fConfig->strictES2Mode() && this->errorCount() == 0) { |
| // Enforce Appendix A, Section 5 of the GLSL ES 1.00 spec -- Indexing. This logic assumes |
| // that all loops meet the criteria of Section 4, and if they don't, could crash. |
| for (const auto& pe : program.fOwnedElements) { |
| Analysis::ValidateIndexingForES2(*pe, this->errorReporter()); |
| } |
| } |
| if (this->errorCount() == 0) { |
| bool enforceSizeLimit = ProgramConfig::IsRuntimeEffect(program.fConfig->fKind); |
| Analysis::CheckProgramStructure(program, enforceSizeLimit); |
| |
| // Make sure that variables are declared in the symbol tables that immediately enclose them. |
| SkDEBUGCODE(Analysis::CheckSymbolTableCorrectness(program)); |
| } |
| |
| // Make sure that program usage is still correct after finalization is complete. |
| SkASSERT(*program.usage() == *Analysis::GetUsage(program)); |
| |
| return this->errorCount() == 0; |
| } |
| |
| void Compiler::handleError(std::string_view msg, Position pos) { |
| fErrorText += "error: "; |
| bool printLocation = false; |
| std::string_view src = this->errorReporter().source(); |
| int line = -1; |
| if (pos.valid()) { |
| line = pos.line(src); |
| printLocation = pos.startOffset() < (int)src.length(); |
| fErrorText += std::to_string(line) + ": "; |
| } |
| fErrorText += std::string(msg) + "\n"; |
| if (printLocation) { |
| const int kMaxSurroundingChars = 100; |
| |
| // Find the beginning of the line. |
| int lineStart = pos.startOffset(); |
| while (lineStart > 0) { |
| if (src[lineStart - 1] == '\n') { |
| break; |
| } |
| --lineStart; |
| } |
| |
| // We don't want to show more than 100 characters surrounding the error, so push the line |
| // start forward and add a leading ellipsis if there would be more than this. |
| std::string lineText; |
| std::string caretText; |
| if ((pos.startOffset() - lineStart) > kMaxSurroundingChars) { |
| lineStart = pos.startOffset() - kMaxSurroundingChars; |
| lineText = "..."; |
| caretText = " "; |
| } |
| |
| // Echo the line. Again, we don't want to show more than 100 characters after the end of the |
| // error, so truncate with a trailing ellipsis if needed. |
| const char* lineSuffix = "...\n"; |
| int lineStop = pos.endOffset() + kMaxSurroundingChars; |
| if (lineStop >= (int)src.length()) { |
| lineStop = src.length() - 1; |
| lineSuffix = "\n"; // no ellipsis if we reach end-of-file |
| } |
| for (int i = lineStart; i < lineStop; ++i) { |
| char c = src[i]; |
| if (c == '\n') { |
| lineSuffix = "\n"; // no ellipsis if we reach end-of-line |
| break; |
| } |
| switch (c) { |
| case '\t': lineText += " "; break; |
| case '\0': lineText += " "; break; |
| default: lineText += src[i]; break; |
| } |
| } |
| fErrorText += lineText + lineSuffix; |
| |
| // print the carets underneath it, pointing to the range in question |
| for (int i = lineStart; i < (int)src.length(); i++) { |
| if (i >= pos.endOffset()) { |
| break; |
| } |
| switch (src[i]) { |
| case '\t': |
| caretText += (i >= pos.startOffset()) ? "^^^^" : " "; |
| break; |
| case '\n': |
| SkASSERT(i >= pos.startOffset()); |
| // use an ellipsis if the error continues past the end of the line |
| caretText += (pos.endOffset() > i + 1) ? "..." : "^"; |
| i = src.length(); |
| break; |
| default: |
| caretText += (i >= pos.startOffset()) ? '^' : ' '; |
| break; |
| } |
| } |
| fErrorText += caretText + '\n'; |
| } |
| } |
| |
| std::string Compiler::errorText(bool showCount) { |
| if (showCount) { |
| this->writeErrorCount(); |
| } |
| std::string result = fErrorText; |
| this->resetErrors(); |
| return result; |
| } |
| |
| void Compiler::writeErrorCount() { |
| int count = this->errorCount(); |
| if (count) { |
| fErrorText += std::to_string(count) + |
| ((count == 1) ? " error\n" : " errors\n"); |
| } |
| } |
| |
| } // namespace SkSL |