Flutter Engine
The Flutter Engine
SkSLSwitchStatement.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
13#include "src/core/SkTHash.h"
22#include "src/sksl/ir/SkSLNop.h"
28
29#include <algorithm>
30#include <iterator>
31
32using namespace skia_private;
33
34namespace SkSL {
35
36std::string SwitchStatement::description() const {
37 return "switch (" + this->value()->description() + ") " + this->caseBlock()->description();
38}
39
41 TArray<const SwitchCase*> duplicateCases;
42 THashSet<SKSL_INT> intValues;
43 bool foundDefault = false;
44
45 for (const std::unique_ptr<Statement>& stmt : cases) {
46 const SwitchCase* sc = &stmt->as<SwitchCase>();
47 if (sc->isDefault()) {
48 if (foundDefault) {
49 duplicateCases.push_back(sc);
50 continue;
51 }
52 foundDefault = true;
53 } else {
54 SKSL_INT value = sc->value();
55 if (intValues.contains(value)) {
56 duplicateCases.push_back(sc);
57 continue;
58 }
59 intValues.add(value);
60 }
61 }
62
63 return duplicateCases;
64}
65
66static void remove_break_statements(std::unique_ptr<Statement>& stmt) {
67 class RemoveBreaksWriter : public ProgramWriter {
68 public:
69 bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
70 if (stmt->is<BreakStatement>()) {
71 stmt = Nop::Make();
72 return false;
73 }
75 }
76
77 bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
78 return false;
79 }
80 };
81 RemoveBreaksWriter{}.visitStatementPtr(stmt);
82}
83
84static bool block_for_case(Statement* caseBlock, SwitchCase* caseToCapture) {
85 // This function reduces a switch to the matching case (or cases, if fallthrough occurs) when
86 // the switch-value is known and no conditional breaks exist. If conversion is not possible,
87 // false is returned and no changes are made. Conversion can fail if the switch contains
88 // conditional breaks.
89 //
90 // We have to be careful to not move any of the pointers until after we're sure we're going to
91 // succeed, so before we make any changes at all, we check the switch-cases to decide on a plan
92 // of action.
93 //
94 // First, we identify the code that would be run if the switch's value matches `caseToCapture`.
95 StatementArray& cases = caseBlock->as<Block>().children();
96 auto iter = cases.begin();
97 for (; iter != cases.end(); ++iter) {
98 const SwitchCase& sc = (*iter)->as<SwitchCase>();
99 if (&sc == caseToCapture) {
100 break;
101 }
102 }
103
104 // Next, walk forward through the rest of the switch. If we find a conditional break, we're
105 // stuck and can't simplify at all. If we find an unconditional break, we have a range of
106 // statements that we can use for simplification.
107 auto startIter = iter;
108 bool removeBreakStatements = false;
109 for (; iter != cases.end(); ++iter) {
110 std::unique_ptr<Statement>& stmt = (*iter)->as<SwitchCase>().statement();
112 // We can't reduce switch-cases to a block when they have conditional exits.
113 return false;
114 }
116 // We found an unconditional exit. We can use this block, but we'll need to strip
117 // out the break statement if there is one.
118 removeBreakStatements = true;
119 ++iter;
120 break;
121 }
122 }
123
124 // We fell off the bottom of the switch or encountered a break. Next, we must strip down
125 // `caseBlock` to hold only the statements needed to execute `caseToCapture`. To do this, we
126 // eliminate the SwitchCase elements. This converts each `case n: stmt;` element into just
127 // `stmt;`. While doing this, we also move the elements to the front of the array if they
128 // weren't already there.
129 int numElements = SkToInt(std::distance(startIter, iter));
130 for (int index = 0; index < numElements; ++index, ++startIter) {
131 cases[index] = std::move((*startIter)->as<SwitchCase>().statement());
132 }
133
134 // Next, we shrink the statement array to destroy the excess statements.
135 cases.pop_back_n(cases.size() - numElements);
136
137 // If we found an unconditional break at the end, we need to eliminate that break.
138 if (removeBreakStatements) {
140 }
141
142 // We've stripped down `caseBlock` to contain only the captured case. Return true.
143 return true;
144}
145
146std::unique_ptr<Statement> SwitchStatement::Convert(const Context& context,
148 std::unique_ptr<Expression> value,
149 ExpressionArray caseValues,
150 StatementArray caseStatements,
151 std::unique_ptr<SymbolTable> symbolTable) {
152 SkASSERT(caseValues.size() == caseStatements.size());
153
154 value = context.fTypes.fInt->coerceExpression(std::move(value), context);
155 if (!value) {
156 return nullptr;
157 }
158
160 for (int i = 0; i < caseValues.size(); ++i) {
161 if (caseValues[i]) {
162 Position casePos = caseValues[i]->fPosition;
163 // Case values must be constant integers of the same type as the switch value
164 std::unique_ptr<Expression> caseValue = value->type().coerceExpression(
165 std::move(caseValues[i]), context);
166 if (!caseValue) {
167 return nullptr;
168 }
169 SKSL_INT intValue;
170 if (!ConstantFolder::GetConstantInt(*caseValue, &intValue)) {
171 context.fErrors->error(casePos, "case value must be a constant integer");
172 return nullptr;
173 }
174 cases.push_back(SwitchCase::Make(casePos, intValue, std::move(caseStatements[i])));
175 } else {
176 cases.push_back(SwitchCase::MakeDefault(pos, std::move(caseStatements[i])));
177 }
178 }
179
180 // Detect duplicate `case` labels and report an error.
182 if (!duplicateCases.empty()) {
183 for (const SwitchCase* sc : duplicateCases) {
184 if (sc->isDefault()) {
185 context.fErrors->error(sc->fPosition, "duplicate default case");
186 } else {
187 context.fErrors->error(sc->fPosition, "duplicate case value '" +
188 std::to_string(sc->value()) + "'");
189 }
190 }
191 return nullptr;
192 }
193
194 return SwitchStatement::Make(context,
195 pos,
196 std::move(value),
198 std::move(cases),
199 Block::Kind::kBracedScope,
200 std::move(symbolTable)));
201}
202
203std::unique_ptr<Statement> SwitchStatement::Make(const Context& context,
205 std::unique_ptr<Expression> value,
206 std::unique_ptr<Statement> caseBlock) {
207 // Confirm that every statement in `cases` is a SwitchCase.
208 const StatementArray& cases = caseBlock->as<Block>().children();
209 SkASSERT(std::all_of(cases.begin(), cases.end(), [&](const std::unique_ptr<Statement>& stmt) {
210 return stmt->is<SwitchCase>();
211 }));
212
213 // Confirm that every switch-case value is unique.
215
216 // Flatten switch statements if we're optimizing, and the value is known
217 if (context.fConfig->fSettings.fOptimize) {
218 SKSL_INT switchValue;
219 if (ConstantFolder::GetConstantInt(*value, &switchValue)) {
220 SwitchCase* defaultCase = nullptr;
221 SwitchCase* matchingCase = nullptr;
222 for (const std::unique_ptr<Statement>& stmt : cases) {
223 SwitchCase& sc = stmt->as<SwitchCase>();
224 if (sc.isDefault()) {
225 defaultCase = &sc;
226 continue;
227 }
228
229 if (sc.value() == switchValue) {
230 matchingCase = &sc;
231 break;
232 }
233 }
234
235 if (!matchingCase) {
236 // No case value matches the switch value.
237 if (!defaultCase) {
238 // No default switch-case exists; the switch had no effect.
239 // We can eliminate the entire switch!
240 return Nop::Make();
241 }
242 // We had a default case; that's what we matched with.
243 matchingCase = defaultCase;
244 }
245
246 // Strip down our case block to contain only the matching case, if we can.
247 if (block_for_case(caseBlock.get(), matchingCase)) {
248 return caseBlock;
249 }
250 }
251 }
252
253 // The switch couldn't be optimized away; emit it normally.
254 auto stmt = std::make_unique<SwitchStatement>(pos, std::move(value), std::move(caseBlock));
255
256 // If a switch-case has variable declarations at its top level, we want to create a scoped block
257 // around the switch, then move the variable declarations out of the switch body and into the
258 // outer scope. This prevents scoping issues in backends which don't offer a native switch.
259 // (skia:14375)
260 return Transform::HoistSwitchVarDeclarationsAtTopLevel(context, std::move(stmt));
261}
262
263} // namespace SkSL
SkPoint pos
#define SkASSERT(cond)
Definition: SkAssert.h:116
int64_t SKSL_INT
Definition: SkSLDefines.h:16
constexpr int SkToInt(S x)
Definition: SkTo.h:29
static std::unique_ptr< Block > MakeBlock(Position pos, StatementArray statements, Kind kind=Kind::kBracedScope, std::unique_ptr< SymbolTable > symbols=nullptr)
Definition: SkSLBlock.cpp:59
const std::unique_ptr< Type > fInt
static bool GetConstantInt(const Expression &value, SKSL_INT *out)
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)
const T & as() const
Definition: SkSLIRNode.h:133
static std::unique_ptr< Statement > Make()
Definition: SkSLNop.h:26
bool visitStatementPtr(std::unique_ptr< Statement > &s) override
static std::unique_ptr< SwitchCase > MakeDefault(Position pos, std::unique_ptr< Statement > statement)
SKSL_INT value() const
bool isDefault() const
static std::unique_ptr< SwitchCase > Make(Position pos, SKSL_INT value, std::unique_ptr< Statement > statement)
std::unique_ptr< Statement > & statement()
std::unique_ptr< Expression > & value()
std::string description() const override
std::unique_ptr< Statement > & caseBlock()
static std::unique_ptr< Statement > Make(const Context &context, Position pos, std::unique_ptr< Expression > value, std::unique_ptr< Statement > caseBlock)
static std::unique_ptr< Statement > Convert(const Context &context, Position pos, std::unique_ptr< Expression > value, ExpressionArray caseValues, StatementArray caseStatements, std::unique_ptr< SymbolTable > symbolTable)
StatementArray & cases()
bool empty() const
Definition: SkTArray.h:199
void pop_back_n(int n)
Definition: SkTArray.h:330
int size() const
Definition: SkTArray.h:421
void add(T item)
Definition: SkTHash.h:592
bool contains(const T &item) const
Definition: SkTHash.h:595
EMSCRIPTEN_KEEPALIVE void empty()
uint8_t value
bool SwitchCaseContainsUnconditionalExit(const Statement &stmt)
bool SwitchCaseContainsConditionalExit(const Statement &stmt)
std::unique_ptr< Statement > HoistSwitchVarDeclarationsAtTopLevel(const Context &, std::unique_ptr< SwitchStatement >)
static void remove_break_statements(std::unique_ptr< Statement > &stmt)
static TArray< const SwitchCase * > find_duplicate_case_values(const StatementArray &cases)
static bool block_for_case(Statement *caseBlock, SwitchCase *caseToCapture)
static SkString to_string(int n)
Definition: nanobench.cpp:119
ProgramSettings fSettings