• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 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 #include "include/private/base/SkAssert.h"
9 #include "include/private/base/SkTArray.h"
10 #include "src/sksl/SkSLAnalysis.h"
11 #include "src/sksl/SkSLDefines.h"
12 #include "src/sksl/SkSLPosition.h"
13 #include "src/sksl/ir/SkSLBlock.h"
14 #include "src/sksl/ir/SkSLExpression.h"
15 #include "src/sksl/ir/SkSLIRHelpers.h"
16 #include "src/sksl/ir/SkSLIRNode.h"
17 #include "src/sksl/ir/SkSLModifierFlags.h"
18 #include "src/sksl/ir/SkSLNop.h"
19 #include "src/sksl/ir/SkSLStatement.h"
20 #include "src/sksl/ir/SkSLSwitchStatement.h"
21 #include "src/sksl/ir/SkSLSymbolTable.h"
22 #include "src/sksl/ir/SkSLVarDeclarations.h"
23 #include "src/sksl/ir/SkSLVariable.h"
24 #include "src/sksl/transform/SkSLProgramWriter.h"
25 #include "src/sksl/transform/SkSLTransform.h"
26 
27 #include <memory>
28 #include <utility>
29 
30 using namespace skia_private;
31 
32 namespace SkSL {
33 
34 class Context;
35 
HoistSwitchVarDeclarationsAtTopLevel(const Context & context,std::unique_ptr<SwitchStatement> stmt)36 std::unique_ptr<Statement> Transform::HoistSwitchVarDeclarationsAtTopLevel(
37         const Context& context,
38         std::unique_ptr<SwitchStatement> stmt) {
39     struct HoistSwitchVarDeclsVisitor : public ProgramWriter {
40         HoistSwitchVarDeclsVisitor(const Context& c) : fContext(c) {}
41 
42         bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
43             // We don't need to recurse into expressions.
44             return false;
45         }
46 
47         bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
48             switch (stmt->kind()) {
49                 case StatementKind::kSwitchCase:
50                     // Recurse inward from the switch and its inner switch-cases.
51                     return INHERITED::visitStatementPtr(stmt);
52 
53                 case StatementKind::kBlock:
54                     if (!stmt->as<Block>().isScope()) {
55                         // Recurse inward from unscoped blocks.
56                         return INHERITED::visitStatementPtr(stmt);
57                     }
58                     break;
59 
60                 case StatementKind::kVarDeclaration:
61                     // Keep track of variable declarations.
62                     fVarDeclarations.push_back(&stmt);
63                     break;
64 
65                 default:
66                     break;
67             }
68 
69             // We don't need to recurse into other statement types; we're only interested in the top
70             // level of the switch statement.
71             return false;
72         }
73 
74         const Context& fContext;
75         TArray<std::unique_ptr<Statement>*> fVarDeclarations;
76 
77         using INHERITED = ProgramWriter;
78     };
79 
80     // Visit every switch-case in the switch, looking for hoistable var-declarations.
81     HoistSwitchVarDeclsVisitor visitor(context);
82     for (std::unique_ptr<Statement>& sc : stmt->as<SwitchStatement>().cases()) {
83         visitor.visitStatementPtr(sc);
84     }
85 
86     // If no declarations were found, return the switch as-is.
87     if (visitor.fVarDeclarations.empty()) {
88         return stmt;
89     }
90 
91     // Move all of the var-declaration statements into a separate block.
92     SymbolTable* switchSymbols = stmt->caseBlock()->as<Block>().symbolTable();
93     std::unique_ptr<SymbolTable> blockSymbols = switchSymbols->insertNewParent();
94 
95     StatementArray blockStmts;
96     blockStmts.reserve_exact(visitor.fVarDeclarations.size() + 1);
97     for (std::unique_ptr<Statement>* innerDeclaration : visitor.fVarDeclarations) {
98         VarDeclaration& decl = (*innerDeclaration)->as<VarDeclaration>();
99         Variable* var = decl.var();
100         bool isConst = var->modifierFlags().isConst();
101 
102         std::unique_ptr<Statement> replacementStmt;
103         if (decl.value() && !isConst) {
104             // The inner variable-declaration has an initial-value; we must replace the declaration
105             // with an assignment to the variable. This also has the helpful effect of stripping off
106             // the initial-value from the declaration.
107             struct AssignmentHelper : public IRHelpers {
108                 using IRHelpers::IRHelpers;
109 
110                 std::unique_ptr<Statement> makeAssignmentStmt(VarDeclaration& decl) const {
111                     return Assign(Ref(decl.var()), std::move(decl.value()));
112                 }
113             };
114 
115             AssignmentHelper helper(context);
116             replacementStmt = helper.makeAssignmentStmt(decl);
117         } else {
118             // The inner variable-declaration has no initial-value, or it's const and has a constant
119             // value; we can move it upwards as-is and replace its statement with a no-op.
120             SkASSERT(!isConst || Analysis::IsConstantExpression(*decl.value()));
121 
122             replacementStmt = Nop::Make();
123         }
124 
125         // Move the var-declaration above the switch, and replace the existing statement with either
126         // an assignment (if there was an initial-value) or a no-op (if there wasn't one).
127         blockStmts.push_back(std::move(*innerDeclaration));
128         *innerDeclaration = std::move(replacementStmt);
129 
130         // Hoist the variable's symbol outside of the switch's symbol table, and into the enclosing
131         // block's symbol table.
132         switchSymbols->moveSymbolTo(blockSymbols.get(), var, context);
133     }
134 
135     // Return a scoped Block holding the switch.
136     Position pos = stmt->fPosition;
137     blockStmts.push_back(std::move(stmt));
138     return Block::MakeBlock(pos, std::move(blockStmts), Block::Kind::kBracedScope,
139                             std::move(blockSymbols));
140 }
141 
142 }  // namespace SkSL
143