/* * Copyright 2022 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkSpan.h" #include "include/core/SkTypes.h" #include "src/base/SkEnumBitMask.h" #include "src/base/SkStringView.h" #include "src/sksl/SkSLAnalysis.h" #include "src/sksl/SkSLModule.h" #include "src/sksl/SkSLProgramSettings.h" #include "src/sksl/ir/SkSLFunctionDeclaration.h" #include "src/sksl/ir/SkSLFunctionDefinition.h" #include "src/sksl/ir/SkSLFunctionPrototype.h" #include "src/sksl/ir/SkSLIRNode.h" #include "src/sksl/ir/SkSLModifierFlags.h" #include "src/sksl/ir/SkSLProgramElement.h" #include "src/sksl/ir/SkSLStatement.h" #include "src/sksl/ir/SkSLSymbol.h" #include "src/sksl/ir/SkSLSymbolTable.h" #include "src/sksl/ir/SkSLVarDeclarations.h" #include "src/sksl/ir/SkSLVariable.h" #include "src/sksl/transform/SkSLProgramWriter.h" #include "src/sksl/transform/SkSLTransform.h" #include #include #include #include #include #include namespace SkSL { class Context; class ProgramUsage; enum class ProgramKind : int8_t; static void strip_export_flag(Context& context, const FunctionDeclaration* funcDecl, SymbolTable* symbols) { // Remove `$export` from every overload of this function. Symbol* mutableSym = symbols->findMutable(funcDecl->name()); while (mutableSym) { FunctionDeclaration* mutableDecl = &mutableSym->as(); ModifierFlags flags = mutableDecl->modifierFlags(); flags &= ~ModifierFlag::kExport; mutableDecl->setModifierFlags(flags); mutableSym = mutableDecl->mutableNextOverload(); } } void Transform::RenamePrivateSymbols(Context& context, Module& module, ProgramUsage* usage, ProgramKind kind) { class SymbolRenamer : public ProgramWriter { public: SymbolRenamer(Context& context, ProgramUsage* usage, SymbolTable* symbolBase, ProgramKind kind) : fContext(context) , fUsage(usage) , fSymbolTableStack({symbolBase}) , fKind(kind) {} static std::string FindShortNameForSymbol(const Symbol* sym, const SymbolTable* symbolTable, const std::string& namePrefix) { static constexpr std::string_view kLetters[] = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; // Try any single-letter option. for (std::string_view letter : kLetters) { std::string name = namePrefix + std::string(letter); if (symbolTable->find(name) == nullptr) { return name; } } // Try every two-letter option. for (std::string_view letterA : kLetters) { for (std::string_view letterB : kLetters) { std::string name = namePrefix + std::string(letterA) + std::string(letterB); if (symbolTable->find(name) == nullptr) { return name; } } } // We struck out. Somehow, all 2700 two-letter names have been claimed. SkDEBUGFAILF("Unable to find unique name for '%s'", std::string(sym->name()).c_str()); return std::string(sym->name()); } void minifyVariableName(const Variable* var) { // Some variables are associated with anonymous parameters--these don't have names and // aren't present in the symbol table. Their names are already empty so there's no way // to shrink them further. if (var->name().empty()) { return; } // Ensure that this variable is properly set up in the symbol table. SymbolTable* symbols = fSymbolTableStack.back(); Symbol* mutableSym = symbols->findMutable(var->name()); SkASSERTF(mutableSym != nullptr, "symbol table missing '%.*s'", (int)var->name().size(), var->name().data()); SkASSERTF(mutableSym == var, "wrong symbol found for '%.*s'", (int)var->name().size(), var->name().data()); // Look for a new name for this symbol. // Note: we always rename _every_ variable, even ones with single-letter names. This is // a safeguard: if we claimed a name like `i`, and then the program itself contained an // `i` later on, in a nested SymbolTable, the two names would clash. By always renaming // everything, we can ignore that problem. std::string shortName = FindShortNameForSymbol(var, symbols, ""); SkASSERT(symbols->findMutable(shortName) == nullptr); // Update the symbol's name. const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName)); symbols->renameSymbol(fContext, mutableSym, *ownedName); } void minifyFunctionName(const FunctionDeclaration* funcDecl) { // Look for a new name for this function. std::string namePrefix = ProgramConfig::IsRuntimeEffect(fKind) ? "" : "$"; SymbolTable* symbols = fSymbolTableStack.back(); std::string shortName = FindShortNameForSymbol(funcDecl, symbols, std::move(namePrefix)); SkASSERT(symbols->findMutable(shortName) == nullptr); if (shortName.size() < funcDecl->name().size()) { // Update the function's name. (If the function has overloads, this will rename all // of them at once.) Symbol* mutableSym = symbols->findMutable(funcDecl->name()); const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName)); symbols->renameSymbol(fContext, mutableSym, *ownedName); } } bool functionNameCanBeMinifiedSafely(const FunctionDeclaration& funcDecl) const { if (ProgramConfig::IsRuntimeEffect(fKind)) { // The only externally-accessible function in a runtime effect is main(). return !funcDecl.isMain(); } else { // We will only minify $private_functions, and only ones not marked as $export. return skstd::starts_with(funcDecl.name(), '$') && !funcDecl.modifierFlags().isExport(); } } void minifyFunction(FunctionDefinition& def) { // If the function is private, minify its name. const FunctionDeclaration* funcDecl = &def.declaration(); if (this->functionNameCanBeMinifiedSafely(*funcDecl)) { this->minifyFunctionName(funcDecl); } // Minify the names of each function parameter. Analysis::SymbolTableStackBuilder symbolTableStackBuilder(def.body().get(), &fSymbolTableStack); for (Variable* param : funcDecl->parameters()) { this->minifyVariableName(param); } } void minifyPrototype(FunctionPrototype& proto) { const FunctionDeclaration* funcDecl = &proto.declaration(); if (funcDecl->definition()) { // This function is defined somewhere; this isn't just a loose prototype. return; } // Eliminate the names of each function parameter. // The parameter names aren't in the symbol table's name lookup map at all. // All we need to do is blank out their names. for (Variable* param : funcDecl->parameters()) { param->setName(""); } } bool visitProgramElement(ProgramElement& elem) override { switch (elem.kind()) { case ProgramElement::Kind::kFunction: this->minifyFunction(elem.as()); return INHERITED::visitProgramElement(elem); case ProgramElement::Kind::kFunctionPrototype: this->minifyPrototype(elem.as()); return INHERITED::visitProgramElement(elem); default: return false; } } bool visitStatementPtr(std::unique_ptr& stmt) override { Analysis::SymbolTableStackBuilder symbolTableStackBuilder(stmt.get(), &fSymbolTableStack); if (stmt->is()) { // Minify the variable's name. VarDeclaration& decl = stmt->as(); this->minifyVariableName(decl.var()); } return INHERITED::visitStatementPtr(stmt); } Context& fContext; ProgramUsage* fUsage; std::vector fSymbolTableStack; ProgramKind fKind; using INHERITED = ProgramWriter; }; // Rename local variables and private functions. SymbolRenamer renamer{context, usage, module.fSymbols.get(), kind}; for (std::unique_ptr& pe : module.fElements) { renamer.visitProgramElement(*pe); } // Strip off modifier `$export` from every function. (Only the minifier checks this flag, so we // can remove it without affecting the meaning of the code.) for (std::unique_ptr& pe : module.fElements) { if (pe->is()) { const FunctionDeclaration* funcDecl = &pe->as().declaration(); if (funcDecl->modifierFlags().isExport()) { strip_export_flag(context, funcDecl, module.fSymbols.get()); } } } } } // namespace SkSL