blob: 754eeaa2283ec6e5dfa73a8824585dd5a6d8bd6d [file] [log] [blame]
/*
* Copyright 2021 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SKSL_TRANSFORM
#define SKSL_TRANSFORM
#include "include/core/SkSpan.h"
#include "src/sksl/ir/SkSLModifierFlags.h"
#include <memory>
#include <vector>
namespace SkSL {
class Context;
class Expression;
class IndexExpression;
struct Module;
struct Program;
class ProgramElement;
class ProgramUsage;
class Statement;
class SwitchStatement;
class Variable;
enum class ProgramKind : int8_t;
namespace Transform {
/**
* Checks to see if it would be safe to add `const` to the modifier flags of a variable. If so,
* returns the modifiers with `const` applied; if not, returns the existing modifiers as-is. Adding
* `const` allows the inliner to fold away more values and generate tighter code.
*/
ModifierFlags AddConstToVarModifiers(const Variable& var,
const Expression* initialValue,
const ProgramUsage* usage);
/**
* Rewrites indexed swizzles of the form `myVec.zyx[i]` by replacing the swizzle with a lookup into
* a constant vector. e.g., the above expression would be rewritten as `myVec[vec3(2, 1, 0)[i]]`.
* This roughly matches glslang's handling of the code.
*/
std::unique_ptr<Expression> RewriteIndexedSwizzle(const Context& context,
const IndexExpression& swizzle);
/**
* Copies built-in functions from modules into the program. Relies on ProgramUsage to determine
* which functions are necessary.
*/
void FindAndDeclareBuiltinFunctions(Program& program);
/**
* Copies built-in structs from modules into the program. Relies on ProgramUsage to determine
* which structs are necessary.
*/
void FindAndDeclareBuiltinStructs(Program& program);
/**
* Scans the finished program for built-in variables like `sk_FragColor` and adds them to the
* program's shared elements.
*/
void FindAndDeclareBuiltinVariables(Program& program);
/**
* Eliminates statements in a block which cannot be reached; for example, a statement
* immediately after a `return` or `continue` can safely be eliminated.
*/
void EliminateUnreachableCode(Module& module, ProgramUsage* usage);
void EliminateUnreachableCode(Program& program);
/**
* Eliminates empty statements in a module (Nops, or blocks holding only Nops). Not implemented for
* Programs because Nops are harmless, but they waste space in long-lived module IR.
*/
void EliminateEmptyStatements(Module& module);
/**
* Eliminates unnecessary braces in a module (e.g., single-statement child blocks). Not implemented
* for Programs because extra braces are harmless, but they waste space in long-lived module IR.
*/
void EliminateUnnecessaryBraces(Module& module);
/**
* Eliminates functions in a program which are never called. Returns true if any changes were made.
*/
bool EliminateDeadFunctions(const Context& context, Module& module, ProgramUsage* usage);
bool EliminateDeadFunctions(Program& program);
/**
* Eliminates variables in a program which are never read or written (past their initializer).
* Preserves side effects from initializers, if any. Returns true if any changes were made.
*/
bool EliminateDeadLocalVariables(const Context& context,
Module& module,
ProgramUsage* usage);
bool EliminateDeadLocalVariables(Program& program);
bool EliminateDeadGlobalVariables(const Context& context,
Module& module,
ProgramUsage* usage,
bool onlyPrivateGlobals);
bool EliminateDeadGlobalVariables(Program& program);
/** Renames private functions and function-local variables to minimize code size. */
void RenamePrivateSymbols(Context& context, Module& module, ProgramUsage* usage, ProgramKind kind);
/** Replaces constant variables in a program with their equivalent values. */
void ReplaceConstVarsWithLiterals(Module& module, ProgramUsage* usage);
/**
* Looks for variables inside of the top-level of a switch body, such as:
*
* switch (x) {
* case 1: int i; // `i` is at top-level
* case 2: float f = 5.0; // `f` is at top-level, and has an initial-value assignment
* case 3: { bool b; } // `b` is not at top-level; it has an additional scope
* }
*
* If any top-level variables are found, a scoped block is created around the switch, and the
* variable declarations are moved out of the switch body and into the outer scope. (Variables with
* additional scoping are left as-is.) Then, we replace the declarations with assignment statements:
*
* {
* int i;
* float f;
* switch (a) {
* case 1: // `i` is declared above and does not need initialization
* case 2: f = 5.0; // `f` is declared above and initialized here
* case 3: { bool b; } // `b` is left as-is because it has a block-scope
* }
* }
*
* This doesn't change the meaning or correctness of the code. If the switch needs to be rewriten
* (e.g. due to the restrictions of ES2 or WGSL), this transformation prevents scoping issues with
* variables falling out of scope between switch-cases when we fall through.
*
* If there are no variables at the top-level, the switch statement is returned as-is.
*/
std::unique_ptr<Statement> HoistSwitchVarDeclarationsAtTopLevel(const Context&,
std::unique_ptr<SwitchStatement>);
} // namespace Transform
} // namespace SkSL
#endif