/*
 * 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 "src/sksl/SkSLDefines.h"
#include "src/sksl/ir/SkSLModifierFlags.h"

#include <cstdint>
#include <memory>

namespace SkSL {

class Block;
class Context;
class Expression;
class IndexExpression;
class Position;
class ProgramUsage;
class SymbolTable;
class Variable;
enum class ProgramKind : int8_t;
struct Module;
struct Program;

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(const Context& context, Module& module);

/**
 * Replaces splat-casts like `float4(myFloat)` with `myFloat.xxxx`. This should be slightly smaller
 * in textual form, and will be optimized back to the splat-cast at load time.
 */
void ReplaceSplatCastsWithSwizzles(const Context& context, 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 switch-cases, such as:
 *
 *    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 and returned which holds the
 * variable declarations from the switch-cases and into the outer scope. (Variables with additional
 * scoping are left as-is.) Then, we replace the declarations with nops or assignment statements.
 * That is, we would return a Block like this:
 *
 *    {
 *        int i;
 *        float f;
 *    }
 *
 * And we would also mutate the passed-in case statements to eliminate the variable decarations:
 *
 *    case 1: Nop;         // `i` is declared in the returned block and needs no initialization
 *    case 2: f = 5.0;     // `f` is declared in the returned block 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, null is returned.
 */
std::unique_ptr<Block> HoistSwitchVarDeclarationsAtTopLevel(const Context&, StatementArray& cases,
                                                            SymbolTable& symbolTable, Position pos);

} // namespace Transform
} // namespace SkSL

#endif
