1 /*
2 * Copyright 2022 Google LLC
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 "include/core/SkTypes.h"
9 #include "include/private/SkSLIRNode.h"
10 #include "include/private/SkSLModifiers.h"
11 #include "include/private/SkSLProgramElement.h"
12 #include "include/private/SkSLStatement.h"
13 #include "include/private/SkSLSymbol.h"
14 #include "src/base/SkStringView.h"
15 #include "src/sksl/SkSLAnalysis.h"
16 #include "src/sksl/SkSLCompiler.h"
17 #include "src/sksl/SkSLContext.h"
18 #include "src/sksl/SkSLModifiersPool.h"
19 #include "src/sksl/SkSLProgramSettings.h"
20 #include "src/sksl/ir/SkSLFunctionDeclaration.h"
21 #include "src/sksl/ir/SkSLFunctionDefinition.h"
22 #include "src/sksl/ir/SkSLFunctionPrototype.h"
23 #include "src/sksl/ir/SkSLSymbolTable.h"
24 #include "src/sksl/ir/SkSLVarDeclarations.h"
25 #include "src/sksl/ir/SkSLVariable.h"
26 #include "src/sksl/transform/SkSLProgramWriter.h"
27 #include "src/sksl/transform/SkSLTransform.h"
28
29 #include <cstdint>
30 #include <memory>
31 #include <string>
32 #include <string_view>
33 #include <utility>
34 #include <vector>
35
36 namespace SkSL {
37
38 class ProgramUsage;
39 enum class ProgramKind : int8_t;
40
strip_export_flag(Context & context,const FunctionDeclaration * funcDecl,SymbolTable * symbols)41 static void strip_export_flag(Context& context,
42 const FunctionDeclaration* funcDecl,
43 SymbolTable* symbols) {
44 // Remove `$export` from every overload of this function.
45 Symbol* mutableSym = symbols->findMutable(funcDecl->name());
46 while (mutableSym) {
47 FunctionDeclaration* mutableDecl = &mutableSym->as<FunctionDeclaration>();
48
49 Modifiers modifiers = mutableDecl->modifiers();
50 modifiers.fFlags &= ~Modifiers::kExport_Flag;
51 mutableDecl->setModifiers(context.fModifiersPool->add(modifiers));
52
53 mutableSym = mutableDecl->mutableNextOverload();
54 }
55 }
56
RenamePrivateSymbols(Context & context,Module & module,ProgramUsage * usage,ProgramKind kind)57 void Transform::RenamePrivateSymbols(Context& context,
58 Module& module,
59 ProgramUsage* usage,
60 ProgramKind kind) {
61 class SymbolRenamer : public ProgramWriter {
62 public:
63 SymbolRenamer(Context& context,
64 ProgramUsage* usage,
65 std::shared_ptr<SymbolTable> symbolBase,
66 ProgramKind kind)
67 : fContext(context)
68 , fUsage(usage)
69 , fSymbolTableStack({std::move(symbolBase)})
70 , fKind(kind) {}
71
72 static std::string FindShortNameForSymbol(const Symbol* sym,
73 const SymbolTable* symbolTable,
74 std::string namePrefix) {
75 static constexpr std::string_view kLetters[] = {
76 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
77 "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
78 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
79 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
80
81 // Try any single-letter option.
82 for (std::string_view letter : kLetters) {
83 std::string name = namePrefix + std::string(letter);
84 if (symbolTable->find(name) == nullptr) {
85 return name;
86 }
87 }
88
89 // Try every two-letter option.
90 for (std::string_view letterA : kLetters) {
91 for (std::string_view letterB : kLetters) {
92 std::string name = namePrefix + std::string(letterA) + std::string(letterB);
93 if (symbolTable->find(name) == nullptr) {
94 return name;
95 }
96 }
97 }
98
99 // We struck out. Somehow, all 2700 two-letter names have been claimed.
100 SkDEBUGFAILF("Unable to find unique name for '%s'", std::string(sym->name()).c_str());
101 return std::string(sym->name());
102 }
103
104 void minifyVariableName(const Variable* var) {
105 // Some variables are associated with anonymous parameters--these don't have names and
106 // aren't present in the symbol table. Their names are already empty so there's no way
107 // to shrink them further.
108 if (var->name().empty()) {
109 return;
110 }
111
112 // Ensure that this variable is properly set up in the symbol table.
113 SymbolTable* symbols = fSymbolTableStack.back().get();
114 Symbol* mutableSym = symbols->findMutable(var->name());
115 SkASSERTF(mutableSym != nullptr,
116 "symbol table missing '%.*s'", (int)var->name().size(), var->name().data());
117 SkASSERTF(mutableSym == var,
118 "wrong symbol found for '%.*s'", (int)var->name().size(), var->name().data());
119
120 // Look for a new name for this symbol.
121 // Note: we always rename _every_ variable, even ones with single-letter names. This is
122 // a safeguard: if we claimed a name like `i`, and then the program itself contained an
123 // `i` later on, in a nested SymbolTable, the two names would clash. By always renaming
124 // everything, we can ignore that problem.
125 std::string shortName = FindShortNameForSymbol(var, symbols, "");
126 SkASSERT(symbols->findMutable(shortName) == nullptr);
127
128 // Update the symbol's name.
129 const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName));
130 symbols->renameSymbol(mutableSym, *ownedName);
131 }
132
133 void minifyFunctionName(const FunctionDeclaration* funcDecl) {
134 // Look for a new name for this function.
135 std::string namePrefix = ProgramConfig::IsRuntimeEffect(fKind) ? "" : "$";
136 SymbolTable* symbols = fSymbolTableStack.back().get();
137 std::string shortName = FindShortNameForSymbol(funcDecl, symbols,
138 std::move(namePrefix));
139 SkASSERT(symbols->findMutable(shortName) == nullptr);
140
141 if (shortName.size() < funcDecl->name().size()) {
142 // Update the function's name. (If the function has overloads, this will rename all
143 // of them at once.)
144 Symbol* mutableSym = symbols->findMutable(funcDecl->name());
145 const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName));
146 symbols->renameSymbol(mutableSym, *ownedName);
147 }
148 }
149
150 bool functionNameCanBeMinifiedSafely(const FunctionDeclaration& funcDecl) const {
151 if (ProgramConfig::IsRuntimeEffect(fKind)) {
152 // The only externally-accessible function in a runtime effect is main().
153 return !funcDecl.isMain();
154 } else {
155 // We will only minify $private_functions, and only ones not marked as $export.
156 return skstd::starts_with(funcDecl.name(), '$') &&
157 !(funcDecl.modifiers().fFlags & Modifiers::kExport_Flag);
158 }
159 }
160
161 void minifyFunction(FunctionDefinition& def) {
162 // If the function is private, minify its name.
163 const FunctionDeclaration* funcDecl = &def.declaration();
164 if (this->functionNameCanBeMinifiedSafely(*funcDecl)) {
165 this->minifyFunctionName(funcDecl);
166 }
167
168 // Minify the names of each function parameter.
169 Analysis::SymbolTableStackBuilder symbolTableStackBuilder(def.body().get(),
170 &fSymbolTableStack);
171 for (Variable* param : funcDecl->parameters()) {
172 this->minifyVariableName(param);
173 }
174 }
175
176 void minifyPrototype(FunctionPrototype& proto) {
177 const FunctionDeclaration* funcDecl = &proto.declaration();
178 if (funcDecl->definition()) {
179 // This function is defined somewhere; this isn't just a loose prototype.
180 return;
181 }
182
183 // Eliminate the names of each function parameter.
184 // The parameter names aren't in the symbol table's name lookup map at all.
185 // All we need to do is blank out their names.
186 for (Variable* param : funcDecl->parameters()) {
187 param->setName("");
188 }
189 }
190
191 bool visitProgramElement(ProgramElement& elem) override {
192 switch (elem.kind()) {
193 case ProgramElement::Kind::kFunction:
194 this->minifyFunction(elem.as<FunctionDefinition>());
195 return INHERITED::visitProgramElement(elem);
196
197 case ProgramElement::Kind::kFunctionPrototype:
198 this->minifyPrototype(elem.as<FunctionPrototype>());
199 return INHERITED::visitProgramElement(elem);
200
201 default:
202 return false;
203 }
204 }
205
206 bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
207 Analysis::SymbolTableStackBuilder symbolTableStackBuilder(stmt.get(),
208 &fSymbolTableStack);
209 if (stmt->is<VarDeclaration>()) {
210 // Minify the variable's name.
211 VarDeclaration& decl = stmt->as<VarDeclaration>();
212 this->minifyVariableName(decl.var());
213 }
214
215 return INHERITED::visitStatementPtr(stmt);
216 }
217
218 Context& fContext;
219 ProgramUsage* fUsage;
220 std::vector<std::shared_ptr<SymbolTable>> fSymbolTableStack;
221 ProgramKind fKind;
222 using INHERITED = ProgramWriter;
223 };
224
225 // Rename local variables and private functions.
226 SymbolRenamer renamer{context, usage, module.fSymbols, kind};
227 for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
228 renamer.visitProgramElement(*pe);
229 }
230
231 // Strip off modifier `$export` from every function. (Only the minifier checks this flag, so we
232 // can remove it without affecting the meaning of the code.)
233 for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
234 if (pe->is<FunctionDefinition>()) {
235 const FunctionDeclaration* funcDecl = &pe->as<FunctionDefinition>().declaration();
236 if (funcDecl->modifiers().fFlags & Modifiers::kExport_Flag) {
237 strip_export_flag(context, funcDecl, module.fSymbols.get());
238 }
239 }
240 }
241 }
242
243 } // namespace SkSL
244