/* * Copyright 2023 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/private/base/SkAssert.h" #include "include/private/base/SkTArray.h" #include "src/sksl/SkSLAnalysis.h" #include "src/sksl/SkSLDefines.h" #include "src/sksl/SkSLPosition.h" #include "src/sksl/ir/SkSLBlock.h" #include "src/sksl/ir/SkSLExpression.h" #include "src/sksl/ir/SkSLIRHelpers.h" #include "src/sksl/ir/SkSLIRNode.h" #include "src/sksl/ir/SkSLModifierFlags.h" #include "src/sksl/ir/SkSLNop.h" #include "src/sksl/ir/SkSLStatement.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 using namespace skia_private; namespace SkSL { class Context; std::unique_ptr Transform::HoistSwitchVarDeclarationsAtTopLevel(const Context& context, StatementArray& cases, SymbolTable& switchSymbols, Position pos) { struct HoistSwitchVarDeclsVisitor : public ProgramWriter { HoistSwitchVarDeclsVisitor(const Context& c) : fContext(c) {} bool visitExpressionPtr(std::unique_ptr& expr) override { // We don't need to recurse into expressions. return false; } bool visitStatementPtr(std::unique_ptr& stmt) override { switch (stmt->kind()) { case StatementKind::kSwitchCase: // Recurse inward from the switch and its inner switch-cases. return INHERITED::visitStatementPtr(stmt); case StatementKind::kBlock: if (!stmt->as().isScope()) { // Recurse inward from unscoped blocks. return INHERITED::visitStatementPtr(stmt); } break; case StatementKind::kVarDeclaration: // Keep track of variable declarations. fVarDeclarations.push_back(&stmt); break; default: break; } // We don't need to recurse into other statement types; we're only interested in the top // level of the switch statement. return false; } const Context& fContext; TArray*> fVarDeclarations; using INHERITED = ProgramWriter; }; // Visit every switch-case in the switch, looking for hoistable var-declarations. HoistSwitchVarDeclsVisitor visitor(context); for (std::unique_ptr& sc : cases) { visitor.visitStatementPtr(sc); } // If no declarations were found, the switch can stay as-is. if (visitor.fVarDeclarations.empty()) { return nullptr; } // Move all of the var-declaration statements into a separate block. std::unique_ptr blockSymbols = switchSymbols.insertNewParent(); StatementArray blockStmts; blockStmts.reserve_exact(visitor.fVarDeclarations.size() + 1); for (std::unique_ptr* innerDeclaration : visitor.fVarDeclarations) { VarDeclaration& decl = (*innerDeclaration)->as(); Variable* var = decl.var(); bool isConst = var->modifierFlags().isConst(); std::unique_ptr replacementStmt; if (decl.value() && !isConst) { // The inner variable-declaration has an initial-value; we must replace the declaration // with an assignment to the variable. This also has the helpful effect of stripping off // the initial-value from the declaration. struct AssignmentHelper : public IRHelpers { using IRHelpers::IRHelpers; std::unique_ptr makeAssignmentStmt(VarDeclaration& decl) const { return Assign(Ref(decl.var()), std::move(decl.value())); } }; AssignmentHelper helper(context); replacementStmt = helper.makeAssignmentStmt(decl); } else { // The inner variable-declaration has no initial-value, or it's const and has a constant // value; we can move it upwards as-is and replace its statement with a no-op. SkASSERT(!isConst || Analysis::IsConstantExpression(*decl.value())); replacementStmt = Nop::Make(); } // Move the var-declaration into its own block, and replace the existing statement with // either an assignment (if there was an initial-value) or a no-op (if there wasn't one). blockStmts.push_back(std::move(*innerDeclaration)); *innerDeclaration = std::move(replacementStmt); // Hoist the variable's symbol outside of the switch's symbol table, and into the enclosing // block's symbol table. switchSymbols.moveSymbolTo(blockSymbols.get(), var, context); } // Return a scoped Block holding the variable declarations. return Block::MakeBlock(pos, std::move(blockStmts), Block::Kind::kBracedScope, std::move(blockSymbols)); } } // namespace SkSL