1 /* 2 * Copyright 2021 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 #ifndef SKSL_TRANSFORM 9 #define SKSL_TRANSFORM 10 11 #include "include/core/SkSpan.h" 12 #include "src/sksl/ir/SkSLModifierFlags.h" 13 14 #include <memory> 15 #include <vector> 16 17 namespace SkSL { 18 19 class Context; 20 class Expression; 21 class IndexExpression; 22 struct Module; 23 struct Program; 24 class ProgramElement; 25 class ProgramUsage; 26 class Statement; 27 class SwitchStatement; 28 class Variable; 29 enum class ProgramKind : int8_t; 30 31 namespace Transform { 32 33 /** 34 * Checks to see if it would be safe to add `const` to the modifier flags of a variable. If so, 35 * returns the modifiers with `const` applied; if not, returns the existing modifiers as-is. Adding 36 * `const` allows the inliner to fold away more values and generate tighter code. 37 */ 38 ModifierFlags AddConstToVarModifiers(const Variable& var, 39 const Expression* initialValue, 40 const ProgramUsage* usage); 41 42 /** 43 * Rewrites indexed swizzles of the form `myVec.zyx[i]` by replacing the swizzle with a lookup into 44 * a constant vector. e.g., the above expression would be rewritten as `myVec[vec3(2, 1, 0)[i]]`. 45 * This roughly matches glslang's handling of the code. 46 */ 47 std::unique_ptr<Expression> RewriteIndexedSwizzle(const Context& context, 48 const IndexExpression& swizzle); 49 50 /** 51 * Copies built-in functions from modules into the program. Relies on ProgramUsage to determine 52 * which functions are necessary. 53 */ 54 void FindAndDeclareBuiltinFunctions(Program& program); 55 56 /** 57 * Copies built-in structs from modules into the program. Relies on ProgramUsage to determine 58 * which structs are necessary. 59 */ 60 void FindAndDeclareBuiltinStructs(Program& program); 61 62 /** 63 * Scans the finished program for built-in variables like `sk_FragColor` and adds them to the 64 * program's shared elements. 65 */ 66 void FindAndDeclareBuiltinVariables(Program& program); 67 68 /** 69 * Eliminates statements in a block which cannot be reached; for example, a statement 70 * immediately after a `return` or `continue` can safely be eliminated. 71 */ 72 void EliminateUnreachableCode(Module& module, ProgramUsage* usage); 73 void EliminateUnreachableCode(Program& program); 74 75 /** 76 * Eliminates empty statements in a module (Nops, or blocks holding only Nops). Not implemented for 77 * Programs because Nops are harmless, but they waste space in long-lived module IR. 78 */ 79 void EliminateEmptyStatements(Module& module); 80 81 /** 82 * Eliminates unnecessary braces in a module (e.g., single-statement child blocks). Not implemented 83 * for Programs because extra braces are harmless, but they waste space in long-lived module IR. 84 */ 85 void EliminateUnnecessaryBraces(Module& module); 86 87 /** 88 * Eliminates functions in a program which are never called. Returns true if any changes were made. 89 */ 90 bool EliminateDeadFunctions(const Context& context, Module& module, ProgramUsage* usage); 91 bool EliminateDeadFunctions(Program& program); 92 93 /** 94 * Eliminates variables in a program which are never read or written (past their initializer). 95 * Preserves side effects from initializers, if any. Returns true if any changes were made. 96 */ 97 bool EliminateDeadLocalVariables(const Context& context, 98 Module& module, 99 ProgramUsage* usage); 100 bool EliminateDeadLocalVariables(Program& program); 101 bool EliminateDeadGlobalVariables(const Context& context, 102 Module& module, 103 ProgramUsage* usage, 104 bool onlyPrivateGlobals); 105 bool EliminateDeadGlobalVariables(Program& program); 106 107 /** Renames private functions and function-local variables to minimize code size. */ 108 void RenamePrivateSymbols(Context& context, Module& module, ProgramUsage* usage, ProgramKind kind); 109 110 /** Replaces constant variables in a program with their equivalent values. */ 111 void ReplaceConstVarsWithLiterals(Module& module, ProgramUsage* usage); 112 113 /** 114 * Looks for variables inside of the top-level of a switch body, such as: 115 * 116 * switch (x) { 117 * case 1: int i; // `i` is at top-level 118 * case 2: float f = 5.0; // `f` is at top-level, and has an initial-value assignment 119 * case 3: { bool b; } // `b` is not at top-level; it has an additional scope 120 * } 121 * 122 * If any top-level variables are found, a scoped block is created around the switch, and the 123 * variable declarations are moved out of the switch body and into the outer scope. (Variables with 124 * additional scoping are left as-is.) Then, we replace the declarations with assignment statements: 125 * 126 * { 127 * int i; 128 * float f; 129 * switch (a) { 130 * case 1: // `i` is declared above and does not need initialization 131 * case 2: f = 5.0; // `f` is declared above and initialized here 132 * case 3: { bool b; } // `b` is left as-is because it has a block-scope 133 * } 134 * } 135 * 136 * This doesn't change the meaning or correctness of the code. If the switch needs to be rewriten 137 * (e.g. due to the restrictions of ES2 or WGSL), this transformation prevents scoping issues with 138 * variables falling out of scope between switch-cases when we fall through. 139 * 140 * If there are no variables at the top-level, the switch statement is returned as-is. 141 */ 142 std::unique_ptr<Statement> HoistSwitchVarDeclarationsAtTopLevel(const Context&, 143 std::unique_ptr<SwitchStatement>); 144 145 } // namespace Transform 146 } // namespace SkSL 147 148 #endif 149