Flutter Engine
The Flutter Engine
SkSLForStatement.cpp
Go to the documentation of this file.
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
9
20#include "src/sksl/ir/SkSLNop.h"
24
25namespace SkSL {
26
27static 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
46static bool is_simple_initializer(const Statement* stmt) {
47 return !stmt || stmt->isEmpty() || stmt->is<SkSL::VarDeclaration>() ||
49}
50
51std::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
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 }
92 }
93
94 const Context& fContext;
95 SymbolTable* fInnerSymbols;
96 SymbolTable* fHoistedSymbols;
97 };
98
99 SymbolHoister{context, innerSymbols, hoistedSymbols}.visitStatement(initBlock);
100}
101
102std::unique_ptr<Statement> ForStatement::Convert(const Context& context,
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 &&
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
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();
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
189std::unique_ptr<Statement> ForStatement::ConvertWhile(const Context& context,
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,
200 /*initializer=*/nullptr,
201 std::move(test),
202 /*next=*/nullptr,
203 std::move(statement),
204 /*symbolTable=*/nullptr);
205}
206
207std::unique_ptr<Statement> ForStatement::Make(const Context& context,
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) {
218 SkASSERT(!test || test->type().matches(*context.fTypes.fBool));
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
static struct Initializer initializer
SkPoint pos
static float next(float f)
#define SkASSERT(cond)
Definition: SkAssert.h:116
const Context & fContext
static std::unique_ptr< Statement > Make(Position pos, StatementArray statements, Kind kind=Kind::kBracedScope, std::unique_ptr< SymbolTable > symbols=nullptr)
Definition: SkSLBlock.cpp:14
const std::unique_ptr< Type > fBool
const BuiltinTypes & fTypes
Definition: SkSLContext.h:30
ErrorReporter * fErrors
Definition: SkSLContext.h:36
ProgramConfig * fConfig
Definition: SkSLContext.h:33
void error(Position position, std::string_view msg)
static std::unique_ptr< Statement > ConvertWhile(const Context &context, Position pos, std::unique_ptr< Expression > test, std::unique_ptr< Statement > statement)
static std::unique_ptr< Statement > Convert(const Context &context, Position pos, ForLoopPositions forLoopPositions, 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)
std::unique_ptr< Statement > & statement()
std::unique_ptr< Expression > & next()
std::string description() const override
std::unique_ptr< Expression > & test()
static std::unique_ptr< Statement > Make(const Context &context, Position pos, ForLoopPositions forLoopPositions, 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)
std::unique_ptr< Statement > & initializer()
const LoopUnrollInfo * unrollInfo() const
bool is() const
Definition: SkSLIRNode.h:124
const T & as() const
Definition: SkSLIRNode.h:133
static std::unique_ptr< Statement > Make()
Definition: SkSLNop.h:26
virtual bool isEmpty() const
Definition: SkSLStatement.h:32
virtual bool visitStatement(typename T::Statement &statement)
static bool b
GAsyncResult * result
std::unique_ptr< LoopUnrollInfo > GetLoopUnrollInfo(const Context &context, Position pos, const ForLoopPositions &positions, const Statement *loopInitializer, std::unique_ptr< Expression > *loopTestPtr, const Expression *loopNext, const Statement *loopStatement, ErrorReporter *errors)
bool DetectVarDeclarationWithoutScope(const Statement &stmt, ErrorReporter *errors=nullptr)
static bool is_vardecl_block_initializer(const Statement *stmt)
static bool is_simple_initializer(const Statement *stmt)
static void hoist_vardecl_symbols_into_outer_scope(const Context &context, const Block &initBlock, SymbolTable *innerSymbols, SymbolTable *hoistedSymbols)