1 /*
2 * Copyright 2021 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 "src/sksl/ir/SkSLForStatement.h"
9
10 #include "include/core/SkTypes.h"
11 #include "src/sksl/SkSLAnalysis.h"
12 #include "src/sksl/SkSLBuiltinTypes.h"
13 #include "src/sksl/SkSLContext.h"
14 #include "src/sksl/SkSLDefines.h"
15 #include "src/sksl/SkSLErrorReporter.h"
16 #include "src/sksl/SkSLProgramSettings.h"
17 #include "src/sksl/analysis/SkSLProgramVisitor.h"
18 #include "src/sksl/ir/SkSLBlock.h"
19 #include "src/sksl/ir/SkSLExpressionStatement.h"
20 #include "src/sksl/ir/SkSLNop.h"
21 #include "src/sksl/ir/SkSLType.h"
22 #include "src/sksl/ir/SkSLVarDeclarations.h"
23 #include "src/sksl/ir/SkSLVariable.h"
24
25 namespace SkSL {
26
is_vardecl_block_initializer(const Statement * stmt)27 static bool is_vardecl_block_initializer(const Statement* stmt) {
28 if (!stmt) {
29 return false;
30 }
31 if (!stmt->is<SkSL::Block>()) {
32 return false;
33 }
34 const SkSL::Block& b = stmt->as<SkSL::Block>();
35 if (b.isScope()) {
36 return false;
37 }
38 for (const auto& child : b.children()) {
39 if (!child->is<SkSL::VarDeclaration>()) {
40 return false;
41 }
42 }
43 return true;
44 }
45
is_simple_initializer(const Statement * stmt)46 static bool is_simple_initializer(const Statement* stmt) {
47 return !stmt || stmt->isEmpty() || stmt->is<SkSL::VarDeclaration>() ||
48 stmt->is<SkSL::ExpressionStatement>();
49 }
50
description() const51 std::string ForStatement::description() const {
52 std::string result("for (");
53 if (this->initializer()) {
54 result += this->initializer()->description();
55 } else {
56 result += ";";
57 }
58 result += " ";
59 if (this->test()) {
60 result += this->test()->description();
61 }
62 result += "; ";
63 if (this->next()) {
64 result += this->next()->description();
65 }
66 result += ") " + this->statement()->description();
67 return result;
68 }
69
hoist_vardecl_symbols_into_outer_scope(const Context & context,const Block & initBlock,SymbolTable * innerSymbols,SymbolTable * hoistedSymbols)70 static void hoist_vardecl_symbols_into_outer_scope(const Context& context,
71 const Block& initBlock,
72 SymbolTable* innerSymbols,
73 SymbolTable* hoistedSymbols) {
74 class SymbolHoister : public ProgramVisitor {
75 public:
76 SymbolHoister(const Context& ctx, SymbolTable* innerSym, SymbolTable* hoistSym)
77 : fContext(ctx)
78 , fInnerSymbols(innerSym)
79 , fHoistedSymbols(hoistSym) {}
80
81 bool visitStatement(const Statement& stmt) override {
82 if (stmt.is<VarDeclaration>()) {
83 // Hoist the variable's symbol outside of the initializer block's symbol table, and
84 // into the outer symbol table. If the initializer's symbol table originally had
85 // ownership, transfer it. (If the variable was owned elsewhere, it can keep its
86 // current owner.)
87 Variable* var = stmt.as<VarDeclaration>().var();
88 fInnerSymbols->moveSymbolTo(fHoistedSymbols, var, fContext);
89 return false;
90 }
91 return ProgramVisitor::visitStatement(stmt);
92 }
93
94 const Context& fContext;
95 SymbolTable* fInnerSymbols;
96 SymbolTable* fHoistedSymbols;
97 };
98
99 SymbolHoister{context, innerSymbols, hoistedSymbols}.visitStatement(initBlock);
100 }
101
Convert(const Context & context,Position pos,ForLoopPositions positions,std::unique_ptr<Statement> initializer,std::unique_ptr<Expression> test,std::unique_ptr<Expression> next,std::unique_ptr<Statement> statement,std::unique_ptr<SymbolTable> symbolTable)102 std::unique_ptr<Statement> ForStatement::Convert(const Context& context,
103 Position pos,
104 ForLoopPositions positions,
105 std::unique_ptr<Statement> initializer,
106 std::unique_ptr<Expression> test,
107 std::unique_ptr<Expression> next,
108 std::unique_ptr<Statement> statement,
109 std::unique_ptr<SymbolTable> symbolTable) {
110 bool isSimpleInitializer = is_simple_initializer(initializer.get());
111 bool isVardeclBlockInitializer = !isSimpleInitializer &&
112 is_vardecl_block_initializer(initializer.get());
113
114 if (!isSimpleInitializer && !isVardeclBlockInitializer) {
115 context.fErrors->error(initializer->fPosition, "invalid for loop initializer");
116 return nullptr;
117 }
118
119 if (test) {
120 test = context.fTypes.fBool->coerceExpression(std::move(test), context);
121 if (!test) {
122 return nullptr;
123 }
124 }
125
126 // The type of the next-expression doesn't matter, but it needs to be a complete expression.
127 // Report an error on intermediate expressions like FunctionReference or TypeReference.
128 if (next && next->isIncomplete(context)) {
129 return nullptr;
130 }
131
132 std::unique_ptr<LoopUnrollInfo> unrollInfo;
133 if (context.fConfig->strictES2Mode()) {
134 // In strict-ES2, loops must be unrollable or it's an error.
135 unrollInfo = Analysis::GetLoopUnrollInfo(context, pos, positions, initializer.get(), &test,
136 next.get(), statement.get(), context.fErrors);
137 if (!unrollInfo) {
138 return nullptr;
139 }
140 } else {
141 // In ES3, loops don't have to be unrollable, but we can use the unroll information for
142 // optimization purposes.
143 unrollInfo = Analysis::GetLoopUnrollInfo(context, pos, positions, initializer.get(), &test,
144 next.get(), statement.get(), /*errors=*/nullptr);
145 }
146
147 if (Analysis::DetectVarDeclarationWithoutScope(*statement, context.fErrors)) {
148 return nullptr;
149 }
150
151 if (isVardeclBlockInitializer) {
152 // If the initializer statement of a for loop contains multiple variables, this causes
153 // difficulties for several of our backends; e.g. Metal doesn't have a way to express arrays
154 // of different size in the same decl-stmt, because the array-size is part of the type. It's
155 // conceptually equivalent to synthesize a scope, declare the variables, and then emit a for
156 // statement with an empty init-stmt. (Note that we can't just do this transformation
157 // unilaterally for all for-statements, because the resulting for loop isn't ES2-compliant.)
158 std::unique_ptr<SymbolTable> hoistedSymbols = symbolTable->insertNewParent();
159 hoist_vardecl_symbols_into_outer_scope(context, initializer->as<Block>(),
160 symbolTable.get(), hoistedSymbols.get());
161 StatementArray scope;
162 scope.push_back(std::move(initializer));
163 scope.push_back(ForStatement::Make(context,
164 pos,
165 positions,
166 /*initializer=*/nullptr,
167 std::move(test),
168 std::move(next),
169 std::move(statement),
170 std::move(unrollInfo),
171 std::move(symbolTable)));
172 return Block::Make(pos,
173 std::move(scope),
174 Block::Kind::kBracedScope,
175 std::move(hoistedSymbols));
176 }
177
178 return ForStatement::Make(context,
179 pos,
180 positions,
181 std::move(initializer),
182 std::move(test),
183 std::move(next),
184 std::move(statement),
185 std::move(unrollInfo),
186 std::move(symbolTable));
187 }
188
ConvertWhile(const Context & context,Position pos,std::unique_ptr<Expression> test,std::unique_ptr<Statement> statement)189 std::unique_ptr<Statement> ForStatement::ConvertWhile(const Context& context,
190 Position pos,
191 std::unique_ptr<Expression> test,
192 std::unique_ptr<Statement> statement) {
193 if (context.fConfig->strictES2Mode()) {
194 context.fErrors->error(pos, "while loops are not supported");
195 return nullptr;
196 }
197 return ForStatement::Convert(context,
198 pos,
199 ForLoopPositions(),
200 /*initializer=*/nullptr,
201 std::move(test),
202 /*next=*/nullptr,
203 std::move(statement),
204 /*symbolTable=*/nullptr);
205 }
206
Make(const Context & context,Position pos,ForLoopPositions positions,std::unique_ptr<Statement> initializer,std::unique_ptr<Expression> test,std::unique_ptr<Expression> next,std::unique_ptr<Statement> statement,std::unique_ptr<LoopUnrollInfo> unrollInfo,std::unique_ptr<SymbolTable> symbolTable)207 std::unique_ptr<Statement> ForStatement::Make(const Context& context,
208 Position pos,
209 ForLoopPositions positions,
210 std::unique_ptr<Statement> initializer,
211 std::unique_ptr<Expression> test,
212 std::unique_ptr<Expression> next,
213 std::unique_ptr<Statement> statement,
214 std::unique_ptr<LoopUnrollInfo> unrollInfo,
215 std::unique_ptr<SymbolTable> symbolTable) {
216 SkASSERT(is_simple_initializer(initializer.get()) ||
217 is_vardecl_block_initializer(initializer.get()));
218 SkASSERT(!test || test->type().matches(*context.fTypes.fBool));
219 SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*statement));
220 SkASSERT(unrollInfo || !context.fConfig->strictES2Mode());
221
222 // Unrollable loops are easy to optimize because we know initializer, test and next don't have
223 // interesting side effects.
224 if (unrollInfo) {
225 // A zero-iteration unrollable loop can be replaced with Nop.
226 // An unrollable loop with an empty body can be replaced with Nop.
227 if (unrollInfo->fCount <= 0 || statement->isEmpty()) {
228 return Nop::Make();
229 }
230 }
231
232 return std::make_unique<ForStatement>(pos,
233 positions,
234 std::move(initializer),
235 std::move(test),
236 std::move(next),
237 std::move(statement),
238 std::move(unrollInfo),
239 std::move(symbolTable));
240 }
241
242 } // namespace SkSL
243