blob: 0e3d9dae4becc4b8a0642370f463ab3b69540470 [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkTArray.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLDefines.h"
#include "src/sksl/SkSLPosition.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLIRHelpers.h"
#include "src/sksl/ir/SkSLIRNode.h"
#include "src/sksl/ir/SkSLModifierFlags.h"
#include "src/sksl/ir/SkSLNop.h"
#include "src/sksl/ir/SkSLStatement.h"
#include "src/sksl/ir/SkSLSwitchStatement.h"
#include "src/sksl/ir/SkSLSymbolTable.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/transform/SkSLProgramWriter.h"
#include "src/sksl/transform/SkSLTransform.h"
#include <memory>
#include <utility>
using namespace skia_private;
namespace SkSL {
class Context;
std::unique_ptr<Statement> Transform::HoistSwitchVarDeclarationsAtTopLevel(
const Context& context,
std::unique_ptr<SwitchStatement> stmt) {
struct HoistSwitchVarDeclsVisitor : public ProgramWriter {
HoistSwitchVarDeclsVisitor(const Context& c) : fContext(c) {}
bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
// We don't need to recurse into expressions.
return false;
}
bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
switch (stmt->kind()) {
case StatementKind::kSwitchCase:
// Recurse inward from the switch and its inner switch-cases.
return INHERITED::visitStatementPtr(stmt);
case StatementKind::kBlock:
if (!stmt->as<Block>().isScope()) {
// Recurse inward from unscoped blocks.
return INHERITED::visitStatementPtr(stmt);
}
break;
case StatementKind::kVarDeclaration:
// Keep track of variable declarations.
fVarDeclarations.push_back(&stmt);
break;
default:
break;
}
// We don't need to recurse into other statement types; we're only interested in the top
// level of the switch statement.
return false;
}
const Context& fContext;
TArray<std::unique_ptr<Statement>*> fVarDeclarations;
using INHERITED = ProgramWriter;
};
// Visit every switch-case in the switch, looking for hoistable var-declarations.
HoistSwitchVarDeclsVisitor visitor(context);
for (std::unique_ptr<Statement>& sc : stmt->as<SwitchStatement>().cases()) {
visitor.visitStatementPtr(sc);
}
// If no declarations were found, return the switch as-is.
if (visitor.fVarDeclarations.empty()) {
return stmt;
}
// Move all of the var-declaration statements into a separate block.
SymbolTable* switchSymbols = stmt->caseBlock()->as<Block>().symbolTable();
std::unique_ptr<SymbolTable> blockSymbols = switchSymbols->insertNewParent();
StatementArray blockStmts;
blockStmts.reserve_exact(visitor.fVarDeclarations.size() + 1);
for (std::unique_ptr<Statement>* innerDeclaration : visitor.fVarDeclarations) {
VarDeclaration& decl = (*innerDeclaration)->as<VarDeclaration>();
Variable* var = decl.var();
bool isConst = var->modifierFlags().isConst();
std::unique_ptr<Statement> replacementStmt;
if (decl.value() && !isConst) {
// The inner variable-declaration has an initial-value; we must replace the declaration
// with an assignment to the variable. This also has the helpful effect of stripping off
// the initial-value from the declaration.
struct AssignmentHelper : public IRHelpers {
using IRHelpers::IRHelpers;
std::unique_ptr<Statement> makeAssignmentStmt(VarDeclaration& decl) const {
return Assign(Ref(decl.var()), std::move(decl.value()));
}
};
AssignmentHelper helper(context);
replacementStmt = helper.makeAssignmentStmt(decl);
} else {
// The inner variable-declaration has no initial-value, or it's const and has a constant
// value; we can move it upwards as-is and replace its statement with a no-op.
SkASSERT(!isConst || Analysis::IsConstantExpression(*decl.value()));
replacementStmt = Nop::Make();
}
// Move the var-declaration above the switch, and replace the existing statement with either
// an assignment (if there was an initial-value) or a no-op (if there wasn't one).
blockStmts.push_back(std::move(*innerDeclaration));
*innerDeclaration = std::move(replacementStmt);
// Hoist the variable's symbol outside of the switch's symbol table, and into the enclosing
// block's symbol table.
switchSymbols->moveSymbolTo(blockSymbols.get(), var, context);
}
// Return a scoped Block holding the switch.
Position pos = stmt->fPosition;
blockStmts.push_back(std::move(stmt));
return Block::MakeBlock(pos, std::move(blockStmts), Block::Kind::kBracedScope,
std::move(blockSymbols));
}
} // namespace SkSL