| /* |
| * 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/SkSLSwitchStatement.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 <memory> |
| #include <utility> |
| |
| using namespace skia_private; |
| |
| namespace SkSL { |
| |
| class Context; |
| |
| std::unique_ptr<Statement> Transform::HoistSwitchVarDeclarationsAtTopLevel( |
| const Context& context, |
| std::unique_ptr<SwitchStatement> stmt) { |
| struct HoistSwitchVarDeclsVisitor : public ProgramWriter { |
| HoistSwitchVarDeclsVisitor(const Context& c) : fContext(c) {} |
| |
| bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override { |
| // We don't need to recurse into expressions. |
| return false; |
| } |
| |
| bool visitStatementPtr(std::unique_ptr<Statement>& 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<Block>().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<std::unique_ptr<Statement>*> fVarDeclarations; |
| |
| using INHERITED = ProgramWriter; |
| }; |
| |
| // Visit every switch-case in the switch, looking for hoistable var-declarations. |
| HoistSwitchVarDeclsVisitor visitor(context); |
| for (std::unique_ptr<Statement>& sc : stmt->as<SwitchStatement>().cases()) { |
| visitor.visitStatementPtr(sc); |
| } |
| |
| // If no declarations were found, return the switch as-is. |
| if (visitor.fVarDeclarations.empty()) { |
| return stmt; |
| } |
| |
| // Move all of the var-declaration statements into a separate block. |
| SymbolTable* switchSymbols = stmt->caseBlock()->as<Block>().symbolTable(); |
| std::unique_ptr<SymbolTable> blockSymbols = switchSymbols->insertNewParent(); |
| |
| StatementArray blockStmts; |
| blockStmts.reserve_exact(visitor.fVarDeclarations.size() + 1); |
| for (std::unique_ptr<Statement>* innerDeclaration : visitor.fVarDeclarations) { |
| VarDeclaration& decl = (*innerDeclaration)->as<VarDeclaration>(); |
| Variable* var = decl.var(); |
| bool isConst = var->modifierFlags().isConst(); |
| |
| std::unique_ptr<Statement> 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<Statement> 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 above the switch, 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 switch. |
| Position pos = stmt->fPosition; |
| blockStmts.push_back(std::move(stmt)); |
| return Block::MakeBlock(pos, std::move(blockStmts), Block::Kind::kBracedScope, |
| std::move(blockSymbols)); |
| } |
| |
| } // namespace SkSL |