blob: 450a2e8a922dac5712e23edc9126e681c292cbae [file] [log] [blame]
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/transform/utils/hoist_to_decl_before.h"
#include <unordered_map>
#include "src/tint/ast/variable_decl_statement.h"
#include "src/tint/sem/block_statement.h"
#include "src/tint/sem/for_loop_statement.h"
#include "src/tint/sem/if_statement.h"
#include "src/tint/sem/reference.h"
#include "src/tint/sem/variable.h"
#include "src/tint/utils/reverse.h"
namespace tint::transform {
/// Private implementation of HoistToDeclBefore transform
class HoistToDeclBefore::State {
CloneContext& ctx;
ProgramBuilder& b;
/// Holds information about a for-loop that needs to be decomposed into a
/// loop, so that declaration statements can be inserted before the
/// condition expression or continuing statement.
struct LoopInfo {
ast::StatementList cond_decls;
ast::StatementList cont_decls;
};
/// Info for each else-if that needs decomposing
struct ElseIfInfo {
/// Decls to insert before condition
ast::StatementList cond_decls;
};
/// For-loops that need to be decomposed to loops.
std::unordered_map<const sem::ForLoopStatement*, LoopInfo> loops;
/// 'else if' statements that need to be decomposed to 'else {if}'
std::unordered_map<const ast::IfStatement*, ElseIfInfo> else_ifs;
// Converts any for-loops marked for conversion to loops, inserting
// registered declaration statements before the condition or continuing
// statement.
void ForLoopsToLoops() {
if (loops.empty()) {
return;
}
// At least one for-loop needs to be transformed into a loop.
ctx.ReplaceAll([&](const ast::ForLoopStatement* stmt) -> const ast::Statement* {
auto& sem = ctx.src->Sem();
if (auto* fl = sem.Get(stmt)) {
if (auto it = loops.find(fl); it != loops.end()) {
auto& info = it->second;
auto* for_loop = fl->Declaration();
// For-loop needs to be decomposed to a loop.
// Build the loop body's statements.
// Start with any let declarations for the conditional
// expression.
auto body_stmts = info.cond_decls;
// If the for-loop has a condition, emit this next as:
// if (!cond) { break; }
if (auto* cond = for_loop->condition) {
// !condition
auto* not_cond =
b.create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, ctx.Clone(cond));
// { break; }
auto* break_body = b.Block(b.create<ast::BreakStatement>());
// if (!condition) { break; }
body_stmts.emplace_back(b.If(not_cond, break_body));
}
// Next emit the for-loop body
body_stmts.emplace_back(ctx.Clone(for_loop->body));
// Finally create the continuing block if there was one.
const ast::BlockStatement* continuing = nullptr;
if (auto* cont = for_loop->continuing) {
// Continuing block starts with any let declarations used by
// the continuing.
auto cont_stmts = info.cont_decls;
cont_stmts.emplace_back(ctx.Clone(cont));
continuing = b.Block(cont_stmts);
}
auto* body = b.Block(body_stmts);
auto* loop = b.Loop(body, continuing);
if (auto* init = for_loop->initializer) {
return b.Block(ctx.Clone(init), loop);
}
return loop;
}
}
return nullptr;
});
}
void ElseIfsToElseWithNestedIfs() {
// Decompose 'else-if' statements into 'else { if }' blocks.
ctx.ReplaceAll([&](const ast::IfStatement* else_if) -> const ast::Statement* {
if (!else_ifs.count(else_if)) {
return nullptr;
}
auto& else_if_info = else_ifs[else_if];
// Build the else block's body statements, starting with let decls for
// the conditional expression.
auto& body_stmts = else_if_info.cond_decls;
// Move the 'else-if' into the new `else` block as a plain 'if'.
auto* cond = ctx.Clone(else_if->condition);
auto* body = ctx.Clone(else_if->body);
auto* new_if = b.If(cond, body, b.Else(ctx.Clone(else_if->else_statement)));
body_stmts.emplace_back(new_if);
// Replace the 'else-if' with the new 'else' block.
return b.Block(body_stmts);
});
}
public:
/// Constructor
/// @param ctx_in the clone context
explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
/// Hoists `expr` to a `let` or `var` with optional `decl_name`, inserting it
/// before `before_expr`.
/// @param before_expr expression to insert `expr` before
/// @param expr expression to hoist
/// @param as_const hoist to `let` if true, otherwise to `var`
/// @param decl_name optional name to use for the variable/constant name
/// @return true on success
bool Add(const sem::Expression* before_expr,
const ast::Expression* expr,
bool as_const,
const char* decl_name) {
auto name = b.Symbols().New(decl_name);
// Construct the let/var that holds the hoisted expr
auto* v = as_const ? b.Let(name, nullptr, ctx.Clone(expr))
: b.Var(name, nullptr, ctx.Clone(expr));
auto* decl = b.Decl(v);
if (!InsertBefore(before_expr->Stmt(), decl)) {
return false;
}
// Replace the initializer expression with a reference to the let
ctx.Replace(expr, b.Expr(name));
return true;
}
/// Inserts `stmt` before `before_stmt`, possibly marking a for-loop to be
/// converted to a loop, or an else-if to an else { if }. If `decl` is
/// nullptr, for-loop and else-if conversions are marked, but no hoisting
/// takes place.
/// @param before_stmt statement to insert `stmt` before
/// @param stmt statement to insert
/// @return true on success
bool InsertBefore(const sem::Statement* before_stmt, const ast::Statement* stmt) {
auto* ip = before_stmt->Declaration();
auto* else_if = before_stmt->As<sem::IfStatement>();
if (else_if && else_if->Parent()->Is<sem::IfStatement>()) {
// Insertion point is an 'else if' condition.
// Need to convert 'else if' to 'else { if }'.
auto& else_if_info = else_ifs[else_if->Declaration()];
// Index the map to convert this else if, even if `stmt` is nullptr.
auto& decls = else_if_info.cond_decls;
if (stmt) {
decls.emplace_back(stmt);
}
return true;
}
if (auto* fl = before_stmt->As<sem::ForLoopStatement>()) {
// Insertion point is a for-loop condition.
// For-loop needs to be decomposed to a loop.
// Index the map to convert this for-loop, even if `stmt` is nullptr.
auto& decls = loops[fl].cond_decls;
if (stmt) {
decls.emplace_back(stmt);
}
return true;
}
auto* parent = before_stmt->Parent(); // The statement's parent
if (auto* block = parent->As<sem::BlockStatement>()) {
// Insert point sits in a block. Simple case.
// Insert the stmt before the parent statement.
if (stmt) {
ctx.InsertBefore(block->Declaration()->statements, ip, stmt);
}
return true;
}
if (auto* fl = parent->As<sem::ForLoopStatement>()) {
// Insertion point is a for-loop initializer or continuing statement.
// These require special care.
if (fl->Declaration()->initializer == ip) {
// Insertion point is a for-loop initializer.
// Insert the new statement above the for-loop.
if (stmt) {
ctx.InsertBefore(fl->Block()->Declaration()->statements, fl->Declaration(),
stmt);
}
return true;
}
if (fl->Declaration()->continuing == ip) {
// Insertion point is a for-loop continuing statement.
// For-loop needs to be decomposed to a loop.
// Index the map to convert this for-loop, even if `stmt` is nullptr.
auto& decls = loops[fl].cont_decls;
if (stmt) {
decls.emplace_back(stmt);
}
return true;
}
TINT_ICE(Transform, b.Diagnostics()) << "unhandled use of expression in for-loop";
return false;
}
TINT_ICE(Transform, b.Diagnostics())
<< "unhandled expression parent statement type: " << parent->TypeInfo().name;
return false;
}
/// Use to signal that we plan on hoisting a decl before `before_expr`. This
/// will convert 'for-loop's to 'loop's and 'else-if's to 'else {if}'s if
/// needed.
/// @param before_expr expression we would hoist a decl before
/// @return true on success
bool Prepare(const sem::Expression* before_expr) {
return InsertBefore(before_expr->Stmt(), nullptr);
}
/// Applies any scheduled insertions from previous calls to Add() to
/// CloneContext. Call this once before ctx.Clone().
/// @return true on success
bool Apply() {
ForLoopsToLoops();
ElseIfsToElseWithNestedIfs();
return true;
}
};
HoistToDeclBefore::HoistToDeclBefore(CloneContext& ctx) : state_(std::make_unique<State>(ctx)) {}
HoistToDeclBefore::~HoistToDeclBefore() {}
bool HoistToDeclBefore::Add(const sem::Expression* before_expr,
const ast::Expression* expr,
bool as_const,
const char* decl_name) {
return state_->Add(before_expr, expr, as_const, decl_name);
}
bool HoistToDeclBefore::InsertBefore(const sem::Statement* before_stmt,
const ast::Statement* stmt) {
return state_->InsertBefore(before_stmt, stmt);
}
bool HoistToDeclBefore::Prepare(const sem::Expression* before_expr) {
return state_->Prepare(before_expr);
}
bool HoistToDeclBefore::Apply() {
return state_->Apply();
}
} // namespace tint::transform