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