• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/SkSLAnalysis.h"
9 #include "src/sksl/SkSLContext.h"
10 #include "src/sksl/SkSLProgramSettings.h"
11 #include "src/sksl/ir/SkSLBlock.h"
12 #include "src/sksl/ir/SkSLExpressionStatement.h"
13 #include "src/sksl/ir/SkSLForStatement.h"
14 #include "src/sksl/ir/SkSLNop.h"
15 #include "src/sksl/ir/SkSLSymbolTable.h"
16 #include "src/sksl/ir/SkSLType.h"
17 #include "src/sksl/ir/SkSLVarDeclarations.h"
18 
19 namespace SkSL {
20 
is_vardecl_block_initializer(const Statement * stmt)21 static bool is_vardecl_block_initializer(const Statement* stmt) {
22     if (!stmt) {
23         return false;
24     }
25     if (!stmt->is<SkSL::Block>()) {
26         return false;
27     }
28     const SkSL::Block& b = stmt->as<SkSL::Block>();
29     if (b.isScope()) {
30         return false;
31     }
32     for (const auto& child : b.children()) {
33         if (!child->is<SkSL::VarDeclaration>()) {
34             return false;
35         }
36     }
37     return true;
38 }
39 
is_simple_initializer(const Statement * stmt)40 static bool is_simple_initializer(const Statement* stmt) {
41     return !stmt || stmt->isEmpty() || stmt->is<SkSL::VarDeclaration>() ||
42            stmt->is<SkSL::ExpressionStatement>();
43 }
44 
clone() const45 std::unique_ptr<Statement> ForStatement::clone() const {
46     std::unique_ptr<LoopUnrollInfo> unrollInfo;
47     if (fUnrollInfo) {
48         unrollInfo = std::make_unique<LoopUnrollInfo>(*fUnrollInfo);
49     }
50 
51     return std::make_unique<ForStatement>(
52             fLine,
53             this->initializer() ? this->initializer()->clone() : nullptr,
54             this->test() ? this->test()->clone() : nullptr,
55             this->next() ? this->next()->clone() : nullptr,
56             this->statement()->clone(),
57             std::move(unrollInfo),
58             SymbolTable::WrapIfBuiltin(this->symbols()));
59 }
60 
description() const61 String ForStatement::description() const {
62     String result("for (");
63     if (this->initializer()) {
64         result += this->initializer()->description();
65     } else {
66         result += ";";
67     }
68     result += " ";
69     if (this->test()) {
70         result += this->test()->description();
71     }
72     result += "; ";
73     if (this->next()) {
74         result += this->next()->description();
75     }
76     result += ") " + this->statement()->description();
77     return result;
78 }
79 
Convert(const Context & context,int line,std::unique_ptr<Statement> initializer,std::unique_ptr<Expression> test,std::unique_ptr<Expression> next,std::unique_ptr<Statement> statement,std::shared_ptr<SymbolTable> symbolTable)80 std::unique_ptr<Statement> ForStatement::Convert(const Context& context, int line,
81                                                  std::unique_ptr<Statement> initializer,
82                                                  std::unique_ptr<Expression> test,
83                                                  std::unique_ptr<Expression> next,
84                                                  std::unique_ptr<Statement> statement,
85                                                  std::shared_ptr<SymbolTable> symbolTable) {
86     bool isSimpleInitializer = is_simple_initializer(initializer.get());
87     bool isVardeclBlockInitializer =
88             !isSimpleInitializer && is_vardecl_block_initializer(initializer.get());
89 
90     if (!isSimpleInitializer && !isVardeclBlockInitializer) {
91         context.fErrors->error(initializer->fLine, "invalid for loop initializer");
92         return nullptr;
93     }
94 
95     if (test) {
96         test = context.fTypes.fBool->coerceExpression(std::move(test), context);
97         if (!test) {
98             return nullptr;
99         }
100     }
101 
102     // The type of the next-expression doesn't matter, but it needs to be a complete expression.
103     // Report an error on intermediate expressions like FunctionReference or TypeReference.
104     if (next && next->isIncomplete(context)) {
105         return nullptr;
106     }
107 
108     std::unique_ptr<LoopUnrollInfo> unrollInfo;
109     if (context.fConfig->strictES2Mode()) {
110         // In strict-ES2, loops must be unrollable or it's an error.
111         unrollInfo = Analysis::GetLoopUnrollInfo(line, initializer.get(), test.get(),
112                                                  next.get(), statement.get(), context.fErrors);
113         if (!unrollInfo) {
114             return nullptr;
115         }
116     } else {
117         // In ES3, loops don't have to be unrollable, but we can use the unroll information for
118         // optimization purposes.
119         unrollInfo = Analysis::GetLoopUnrollInfo(line, initializer.get(), test.get(),
120                                                  next.get(), statement.get(), /*errors=*/nullptr);
121     }
122 
123     if (Analysis::DetectVarDeclarationWithoutScope(*statement, context.fErrors)) {
124         return nullptr;
125     }
126 
127     if (isVardeclBlockInitializer) {
128         // If the initializer statement of a for loop contains multiple variables, this causes
129         // difficulties for several of our backends; e.g. Metal doesn't have a way to express arrays
130         // of different size in the same decl-stmt, because the array-size is part of the type. It's
131         // conceptually equivalent to synthesize a scope, declare the variables, and then emit a for
132         // statement with an empty init-stmt. (Note that we can't just do this transformation
133         // unilaterally for all for-statements, because the resulting for loop isn't ES2-compliant.)
134         StatementArray scope;
135         scope.push_back(std::move(initializer));
136         scope.push_back(ForStatement::Make(context, line, /*initializer=*/nullptr,
137                                            std::move(test), std::move(next), std::move(statement),
138                                            std::move(unrollInfo), std::move(symbolTable)));
139         return Block::Make(line, std::move(scope));
140     }
141 
142     return ForStatement::Make(context, line, std::move(initializer), std::move(test),
143                               std::move(next), std::move(statement), std::move(unrollInfo),
144                               std::move(symbolTable));
145 }
146 
ConvertWhile(const Context & context,int line,std::unique_ptr<Expression> test,std::unique_ptr<Statement> statement,std::shared_ptr<SymbolTable> symbolTable)147 std::unique_ptr<Statement> ForStatement::ConvertWhile(const Context& context, int line,
148                                                       std::unique_ptr<Expression> test,
149                                                       std::unique_ptr<Statement> statement,
150                                                       std::shared_ptr<SymbolTable> symbolTable) {
151     if (context.fConfig->strictES2Mode()) {
152         context.fErrors->error(line, "while loops are not supported");
153         return nullptr;
154     }
155     return ForStatement::Convert(context, line, /*initializer=*/nullptr, std::move(test),
156                                  /*next=*/nullptr, std::move(statement), std::move(symbolTable));
157 }
158 
Make(const Context & context,int line,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::shared_ptr<SymbolTable> symbolTable)159 std::unique_ptr<Statement> ForStatement::Make(const Context& context, int line,
160                                               std::unique_ptr<Statement> initializer,
161                                               std::unique_ptr<Expression> test,
162                                               std::unique_ptr<Expression> next,
163                                               std::unique_ptr<Statement> statement,
164                                               std::unique_ptr<LoopUnrollInfo> unrollInfo,
165                                               std::shared_ptr<SymbolTable> symbolTable) {
166     SkASSERT(is_simple_initializer(initializer.get()) ||
167              is_vardecl_block_initializer(initializer.get()));
168     SkASSERT(!test || test->type() == *context.fTypes.fBool);
169     SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*statement));
170     SkASSERT(unrollInfo || !context.fConfig->strictES2Mode());
171 
172     // Unrollable loops are easy to optimize because we know initializer, test and next don't have
173     // interesting side effects.
174     if (unrollInfo) {
175         // A zero-iteration unrollable loop can be replaced with Nop.
176         // An unrollable loop with an empty body can be replaced with Nop.
177         if (unrollInfo->fCount <= 0 || statement->isEmpty()) {
178             return Nop::Make();
179         }
180     }
181 
182     return std::make_unique<ForStatement>(line, std::move(initializer), std::move(test),
183                                           std::move(next), std::move(statement),
184                                           std::move(unrollInfo), std::move(symbolTable));
185 }
186 
187 }  // namespace SkSL
188