xref: /aosp_15_r20/external/skia/src/sksl/ir/SkSLForStatement.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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