| // Copyright 2023 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/ir/to_program.h" |
| |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "src/tint/ir/access.h" |
| #include "src/tint/ir/binary.h" |
| #include "src/tint/ir/block.h" |
| #include "src/tint/ir/call.h" |
| #include "src/tint/ir/constant.h" |
| #include "src/tint/ir/continue.h" |
| #include "src/tint/ir/exit_if.h" |
| #include "src/tint/ir/exit_loop.h" |
| #include "src/tint/ir/exit_switch.h" |
| #include "src/tint/ir/if.h" |
| #include "src/tint/ir/instruction.h" |
| #include "src/tint/ir/load.h" |
| #include "src/tint/ir/loop.h" |
| #include "src/tint/ir/module.h" |
| #include "src/tint/ir/multi_in_block.h" |
| #include "src/tint/ir/next_iteration.h" |
| #include "src/tint/ir/return.h" |
| #include "src/tint/ir/store.h" |
| #include "src/tint/ir/switch.h" |
| #include "src/tint/ir/unary.h" |
| #include "src/tint/ir/user_call.h" |
| #include "src/tint/ir/var.h" |
| #include "src/tint/program_builder.h" |
| #include "src/tint/switch.h" |
| #include "src/tint/type/atomic.h" |
| #include "src/tint/type/depth_multisampled_texture.h" |
| #include "src/tint/type/depth_texture.h" |
| #include "src/tint/type/multisampled_texture.h" |
| #include "src/tint/type/pointer.h" |
| #include "src/tint/type/reference.h" |
| #include "src/tint/type/sampler.h" |
| #include "src/tint/type/texture.h" |
| #include "src/tint/utils/hashmap.h" |
| #include "src/tint/utils/predicates.h" |
| #include "src/tint/utils/reverse.h" |
| #include "src/tint/utils/scoped_assignment.h" |
| #include "src/tint/utils/transform.h" |
| #include "src/tint/utils/vector.h" |
| |
| // Helper for calling TINT_UNIMPLEMENTED() from a Switch(object_ptr) default case. |
| #define UNHANDLED_CASE(object_ptr) \ |
| TINT_UNIMPLEMENTED(IR, b.Diagnostics()) \ |
| << "unhandled case in Switch(): " << (object_ptr ? object_ptr->TypeInfo().name : "<null>") |
| |
| // Helper for incrementing nesting_depth_ and then decrementing nesting_depth_ at the end |
| // of the scope that holds the call. |
| #define SCOPED_NESTING() \ |
| nesting_depth_++; \ |
| TINT_DEFER(nesting_depth_--) |
| |
| namespace tint::ir { |
| |
| namespace { |
| |
| /// Empty struct used as a sentinel value to indicate that an ast::Value has been consumed by its |
| /// single place of usage. Attempting to use this value a second time should result in an ICE. |
| struct ConsumedValue {}; |
| |
| class State { |
| public: |
| explicit State(Module& m) : mod(m) {} |
| |
| Program Run() { |
| // TODO(crbug.com/tint/1902): Emit root block |
| // TODO(crbug.com/tint/1902): Emit user-declared types |
| for (auto* fn : mod.functions) { |
| Fn(fn); |
| } |
| return Program{std::move(b)}; |
| } |
| |
| private: |
| /// The source IR module |
| Module& mod; |
| |
| /// The target ProgramBuilder |
| ProgramBuilder b; |
| |
| using ValueBinding = std::variant<Symbol, const ast::Expression*, ConsumedValue>; |
| |
| /// A hashmap of value to one of: |
| /// * Symbol - Name of 'let' (non-inlinable value), 'var' or parameter. |
| /// * ast::Expression* - single use, inlined expression. |
| /// * ConsumedValue - a special value used to indicate that the value has already been |
| /// consumed. |
| utils::Hashmap<Value*, ValueBinding, 32> bindings_; |
| |
| /// The nesting depth of the currently generated AST |
| /// 0 is module scope |
| /// 1 is root-level function scope |
| /// 2+ is within control flow |
| uint32_t nesting_depth_ = 0; |
| |
| using StatementList = utils::Vector<const ast::Statement*, |
| decltype(ast::BlockStatement::statements)::static_length>; |
| StatementList* statements_ = nullptr; |
| |
| /// The current switch case block |
| ir::Block* current_switch_case_ = nullptr; |
| |
| const ast::Function* Fn(ir::Function* fn) { |
| SCOPED_NESTING(); |
| |
| // TODO(crbug.com/tint/1915): Properly implement this when we've fleshed out Function |
| static constexpr size_t N = decltype(ast::Function::params)::static_length; |
| auto params = utils::Transform<N>(fn->Params(), [&](FunctionParam* param) { |
| auto name = BindName(param); |
| auto ty = Type(param->Type()); |
| return b.Param(name, ty); |
| }); |
| |
| auto name = BindName(fn); |
| auto ret_ty = Type(fn->ReturnType()); |
| auto* body = Block(fn->Block()); |
| utils::Vector<const ast::Attribute*, 1> attrs{}; |
| utils::Vector<const ast::Attribute*, 1> ret_attrs{}; |
| return b.Func(name, std::move(params), ret_ty, body, std::move(attrs), |
| std::move(ret_attrs)); |
| } |
| |
| const ast::BlockStatement* Block(ir::Block* block) { |
| // TODO(crbug.com/tint/1902): Handle block arguments. |
| return b.Block(Statements(block)); |
| } |
| |
| StatementList Statements(ir::Block* block) { |
| StatementList stmts; |
| if (block) { |
| TINT_SCOPED_ASSIGNMENT(statements_, &stmts); |
| for (auto* inst : *block) { |
| Instruction(inst); |
| } |
| } |
| return stmts; |
| } |
| |
| void Append(const ast::Statement* inst) { statements_->Push(inst); } |
| |
| void Instruction(ir::Instruction* inst) { |
| tint::Switch( |
| inst, // |
| [&](ir::Binary* u) { Binary(u); }, // |
| [&](ir::Call* i) { Call(i); }, // |
| [&](ir::ExitIf*) {}, // |
| [&](ir::ExitSwitch* i) { ExitSwitch(i); }, // |
| [&](ir::ExitLoop* i) { ExitLoop(i); }, // |
| [&](ir::If* i) { If(i); }, // |
| [&](ir::Load* l) { Load(l); }, // |
| [&](ir::Loop* l) { Loop(l); }, // |
| [&](ir::Return* i) { Return(i); }, // |
| [&](ir::Store* i) { Store(i); }, // |
| [&](ir::Switch* i) { Switch(i); }, // |
| [&](ir::Unary* u) { Unary(u); }, // |
| [&](ir::Var* i) { Var(i); }, // |
| [&](ir::NextIteration*) {}, // |
| [&](ir::Continue*) {}, // |
| [&](Default) { UNHANDLED_CASE(inst); }); |
| } |
| |
| void If(ir::If* if_) { |
| SCOPED_NESTING(); |
| |
| auto true_stmts = Statements(if_->True()); |
| auto false_stmts = Statements(if_->False()); |
| if (AsShortCircuit(if_, true_stmts, false_stmts)) { |
| return; |
| } |
| |
| auto* cond = Expr(if_->Condition()); |
| auto* true_block = b.Block(std::move(true_stmts)); |
| |
| switch (false_stmts.Length()) { |
| case 0: |
| Append(b.If(cond, true_block)); |
| return; |
| case 1: |
| if (auto* else_if = false_stmts.Front()->As<ast::IfStatement>()) { |
| Append(b.If(cond, true_block, b.Else(else_if))); |
| return; |
| } |
| break; |
| } |
| |
| auto* false_block = b.Block(std::move(false_stmts)); |
| Append(b.If(cond, true_block, b.Else(false_block))); |
| } |
| |
| void Loop(ir::Loop* l) { |
| auto init_stmts = Statements(l->Initializer()); |
| auto* init = init_stmts.Length() == 1 ? init_stmts.Front()->As<ast::VariableDeclStatement>() |
| : nullptr; |
| |
| const ast::Expression* cond = nullptr; |
| |
| StatementList body_stmts; |
| { |
| TINT_SCOPED_ASSIGNMENT(statements_, &body_stmts); |
| for (auto* inst : *l->Body()) { |
| if (body_stmts.IsEmpty()) { |
| if (auto* if_ = inst->As<ir::If>()) { |
| if (!if_->HasResults() && // |
| if_->True()->Length() == 1 && // |
| if_->False()->Length() == 1 && // |
| tint::Is<ir::ExitIf>(if_->True()->Front()) && // |
| tint::Is<ir::ExitLoop>(if_->False()->Front())) { |
| cond = Expr(if_->Condition()); |
| continue; |
| } |
| } |
| } |
| |
| Instruction(inst); |
| } |
| } |
| |
| auto cont_stmts = Statements(l->Continuing()); |
| auto* cont = cont_stmts.Length() == 1 ? cont_stmts.Front() : nullptr; |
| |
| auto* body = b.Block(std::move(body_stmts)); |
| |
| const ast::Statement* loop = nullptr; |
| if (cond) { |
| if (init || cont) { |
| loop = b.For(init, cond, cont, body); |
| } else { |
| loop = b.While(cond, body); |
| } |
| } else { |
| loop = cont_stmts.IsEmpty() ? b.Loop(body) // |
| : b.Loop(body, b.Block(std::move(cont_stmts))); |
| if (!init_stmts.IsEmpty()) { |
| init_stmts.Push(loop); |
| loop = b.Block(std::move(init_stmts)); |
| } |
| } |
| statements_->Push(loop); |
| } |
| |
| void Switch(ir::Switch* s) { |
| SCOPED_NESTING(); |
| |
| auto* cond = Expr(s->Condition()); |
| |
| auto cases = utils::Transform( |
| s->Cases(), // |
| [&](ir::Switch::Case c) -> const tint::ast::CaseStatement* { |
| SCOPED_NESTING(); |
| |
| const ast::BlockStatement* body = nullptr; |
| { |
| TINT_SCOPED_ASSIGNMENT(current_switch_case_, c.Block()); |
| body = Block(c.Block()); |
| } |
| |
| auto selectors = utils::Transform(c.selectors, // |
| [&](ir::Switch::CaseSelector cs) { |
| return cs.IsDefault() |
| ? b.DefaultCaseSelector() |
| : b.CaseSelector(Expr(cs.val)); |
| }); |
| return b.Case(std::move(selectors), body); |
| }); |
| |
| Append(b.Switch(cond, std::move(cases))); |
| } |
| |
| void ExitSwitch(const ir::ExitSwitch* e) { |
| if (current_switch_case_ && current_switch_case_->Terminator() == e) { |
| return; // No need to emit |
| } |
| Append(b.Break()); |
| } |
| |
| void ExitLoop(const ir::ExitLoop*) { Append(b.Break()); } |
| |
| void Return(ir::Return* ret) { |
| if (ret->Args().IsEmpty()) { |
| // Return has no arguments. |
| // If this block is nested withing some control flow, then we must |
| // emit a 'return' statement, otherwise we've just naturally reached |
| // the end of the function where the 'return' is redundant. |
| if (nesting_depth_ > 1) { |
| Append(b.Return()); |
| } |
| return; |
| } |
| |
| // Return has arguments - this is the return value. |
| if (ret->Args().Length() != 1) { |
| TINT_ICE(IR, b.Diagnostics()) |
| << "expected 1 value for return, got " << ret->Args().Length(); |
| return; |
| } |
| |
| Append(b.Return(Expr(ret->Args().Front()))); |
| } |
| |
| void Var(ir::Var* var) { |
| auto* val = var->Result(); |
| Symbol name = BindName(val); |
| auto* ptr = As<type::Pointer>(val->Type()); |
| auto ty = Type(ptr->StoreType()); |
| const ast::Expression* init = nullptr; |
| if (var->Initializer()) { |
| init = Expr(var->Initializer()); |
| } |
| switch (ptr->AddressSpace()) { |
| case builtin::AddressSpace::kFunction: |
| Append(b.Decl(b.Var(name, ty, init))); |
| return; |
| case builtin::AddressSpace::kStorage: |
| Append(b.Decl(b.Var(name, ty, init, ptr->Access(), ptr->AddressSpace()))); |
| return; |
| default: |
| Append(b.Decl(b.Var(name, ty, init, ptr->AddressSpace()))); |
| return; |
| } |
| } |
| |
| void Store(ir::Store* store) { |
| auto* dst = Expr(store->To()); |
| auto* src = Expr(store->From()); |
| Append(b.Assign(dst, src)); |
| } |
| |
| void Call(ir::Call* call) { |
| auto args = utils::Transform<2>(call->Args(), [&](ir::Value* arg) { return Expr(arg); }); |
| tint::Switch( |
| call, // |
| [&](ir::UserCall* c) { |
| auto* expr = b.Call(BindName(c->Func()), std::move(args)); |
| if (!call->HasResults() || call->Result()->Usages().IsEmpty()) { |
| Append(b.CallStmt(expr)); |
| return; |
| } |
| Bind(c->Result(), expr); |
| }, |
| [&](Default) { UNHANDLED_CASE(call); }); |
| } |
| |
| void Load(ir::Load* l) { Bind(l->Result(), Expr(l->From())); } |
| |
| void Unary(ir::Unary* u) { |
| const ast::Expression* expr = nullptr; |
| switch (u->Kind()) { |
| case ir::Unary::Kind::kComplement: |
| expr = b.Complement(Expr(u->Val())); |
| break; |
| case ir::Unary::Kind::kNegation: |
| expr = b.Negation(Expr(u->Val())); |
| break; |
| } |
| Bind(u->Result(), expr); |
| } |
| |
| void Binary(ir::Binary* e) { |
| if (e->Kind() == ir::Binary::Kind::kEqual) { |
| auto* rhs = e->RHS()->As<ir::Constant>(); |
| if (rhs && rhs->Type()->Is<type::Bool>() && rhs->Value()->ValueAs<bool>() == false) { |
| // expr == false |
| Bind(e->Result(), b.Not(Expr(e->LHS()))); |
| return; |
| } |
| } |
| auto* lhs = Expr(e->LHS()); |
| auto* rhs = Expr(e->RHS()); |
| const ast::Expression* expr = nullptr; |
| switch (e->Kind()) { |
| case ir::Binary::Kind::kAdd: |
| expr = b.Add(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kSubtract: |
| expr = b.Sub(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kMultiply: |
| expr = b.Mul(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kDivide: |
| expr = b.Div(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kModulo: |
| expr = b.Mod(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kAnd: |
| expr = b.And(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kOr: |
| expr = b.Or(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kXor: |
| expr = b.Xor(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kEqual: |
| expr = b.Equal(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kNotEqual: |
| expr = b.NotEqual(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kLessThan: |
| expr = b.LessThan(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kGreaterThan: |
| expr = b.GreaterThan(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kLessThanEqual: |
| expr = b.LessThanEqual(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kGreaterThanEqual: |
| expr = b.GreaterThanEqual(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kShiftLeft: |
| expr = b.Shl(lhs, rhs); |
| break; |
| case ir::Binary::Kind::kShiftRight: |
| expr = b.Shr(lhs, rhs); |
| break; |
| } |
| Bind(e->Result(), expr); |
| } |
| |
| TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE); |
| |
| const ast::Expression* Expr(ir::Value* value) { |
| return tint::Switch( |
| value, // |
| [&](ir::Constant* c) { return Constant(c); }, // |
| [&](Default) -> const ast::Expression* { |
| auto lookup = bindings_.Find(value); |
| if (TINT_UNLIKELY(!lookup)) { |
| TINT_ICE(IR, b.Diagnostics()) |
| << "Expr(" << (value ? value->TypeInfo().name : "null") |
| << ") value has no expression"; |
| return b.Expr("<error>"); |
| } |
| return std::visit( |
| [&](auto&& got) -> const ast::Expression* { |
| using T = std::decay_t<decltype(got)>; |
| |
| if constexpr (std::is_same_v<T, Symbol>) { |
| return b.Expr(got); // var, let or parameter. |
| } |
| |
| if constexpr (std::is_same_v<T, const ast::Expression*>) { |
| // Single use (inlined) expression. |
| // Mark the bindings_ map entry as consumed. |
| *lookup = ConsumedValue{}; |
| return got; |
| } |
| |
| if constexpr (std::is_same_v<T, ConsumedValue>) { |
| TINT_ICE(IR, b.Diagnostics()) << "Expr(" << value->TypeInfo().name |
| << ") called twice on the same value"; |
| } else { |
| TINT_ICE(IR, b.Diagnostics()) |
| << "Expr(" << value->TypeInfo().name << ") has unhandled value"; |
| } |
| return b.Expr("<error>"); |
| }, |
| *lookup); |
| }); |
| } |
| |
| TINT_END_DISABLE_WARNING(UNREACHABLE_CODE); |
| |
| const ast::Expression* Constant(ir::Constant* c) { |
| return tint::Switch( |
| c->Type(), // |
| [&](const type::I32*) { return b.Expr(c->Value()->ValueAs<i32>()); }, |
| [&](const type::U32*) { return b.Expr(c->Value()->ValueAs<u32>()); }, |
| [&](const type::F32*) { return b.Expr(c->Value()->ValueAs<f32>()); }, |
| [&](const type::F16*) { return b.Expr(c->Value()->ValueAs<f16>()); }, |
| [&](const type::Bool*) { return b.Expr(c->Value()->ValueAs<bool>()); }, |
| [&](Default) { |
| UNHANDLED_CASE(c); |
| return b.Expr("<error>"); |
| }); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| // Types |
| // |
| // The the case of an error: |
| // * The types generating methods must return a non-null ast type, which may not be semantically |
| // legal, but is enough to populate the AST. |
| // * A diagnostic error must be added to the ast::ProgramBuilder. |
| // This prevents littering the ToProgram logic with expensive error checking code. |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /// @param ty the type::Type |
| /// @return an ast::Type from @p ty. |
| /// @note May be a semantically-invalid placeholder type on error. |
| ast::Type Type(const type::Type* ty) { |
| return tint::Switch( |
| ty, // |
| [&](const type::Void*) { return ast::Type{}; }, // |
| [&](const type::I32*) { return b.ty.i32(); }, // |
| [&](const type::U32*) { return b.ty.u32(); }, // |
| [&](const type::F16*) { return b.ty.f16(); }, // |
| [&](const type::F32*) { return b.ty.f32(); }, // |
| [&](const type::Bool*) { return b.ty.bool_(); }, |
| [&](const type::Matrix* m) { |
| return b.ty.mat(Type(m->type()), m->columns(), m->rows()); |
| }, |
| [&](const type::Vector* v) { |
| auto el = Type(v->type()); |
| if (v->Packed()) { |
| TINT_ASSERT(IR, v->Width() == 3u); |
| return b.ty(builtin::Builtin::kPackedVec3, el); |
| } else { |
| return b.ty.vec(el, v->Width()); |
| } |
| }, |
| [&](const type::Array* a) { |
| auto el = Type(a->ElemType()); |
| utils::Vector<const ast::Attribute*, 1> attrs; |
| if (!a->IsStrideImplicit()) { |
| attrs.Push(b.Stride(a->Stride())); |
| } |
| if (a->Count()->Is<type::RuntimeArrayCount>()) { |
| return b.ty.array(el, std::move(attrs)); |
| } |
| auto count = a->ConstantCount(); |
| if (TINT_UNLIKELY(!count)) { |
| TINT_ICE(IR, b.Diagnostics()) << type::Array::kErrExpectedConstantCount; |
| return b.ty.array(el, u32(1), std::move(attrs)); |
| } |
| return b.ty.array(el, u32(count.value()), std::move(attrs)); |
| }, |
| [&](const type::Struct* s) { return b.ty(s->Name().NameView()); }, |
| [&](const type::Atomic* a) { return b.ty.atomic(Type(a->Type())); }, |
| [&](const type::DepthTexture* t) { return b.ty.depth_texture(t->dim()); }, |
| [&](const type::DepthMultisampledTexture* t) { |
| return b.ty.depth_multisampled_texture(t->dim()); |
| }, |
| [&](const type::ExternalTexture*) { return b.ty.external_texture(); }, |
| [&](const type::MultisampledTexture* t) { |
| auto el = Type(t->type()); |
| return b.ty.multisampled_texture(t->dim(), el); |
| }, |
| [&](const type::SampledTexture* t) { |
| auto el = Type(t->type()); |
| return b.ty.sampled_texture(t->dim(), el); |
| }, |
| [&](const type::StorageTexture* t) { |
| return b.ty.storage_texture(t->dim(), t->texel_format(), t->access()); |
| }, |
| [&](const type::Sampler* s) { return b.ty.sampler(s->kind()); }, |
| [&](const type::Pointer* p) { |
| // Note: type::Pointer always has an inferred access, but WGSL only allows an |
| // explicit access in the 'storage' address space. |
| auto el = Type(p->StoreType()); |
| auto address_space = p->AddressSpace(); |
| auto access = address_space == builtin::AddressSpace::kStorage |
| ? p->Access() |
| : builtin::Access::kUndefined; |
| return b.ty.ptr(address_space, el, access); |
| }, |
| [&](const type::Reference*) { |
| TINT_ICE(IR, b.Diagnostics()) << "reference types should never appear in the IR"; |
| return b.ty.i32(); |
| }, |
| [&](Default) { |
| UNHANDLED_CASE(ty); |
| return b.ty.i32(); |
| }); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| // Bindings |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /// Creates and returns a new, unique name for the given value, or returns the previously |
| /// created name. |
| /// @return the value's name |
| Symbol BindName(Value* value, std::string_view suggested = {}) { |
| TINT_ASSERT(IR, value); |
| auto& existing = bindings_.GetOrCreate(value, [&] { |
| if (!suggested.empty()) { |
| return b.Symbols().New(suggested); |
| } |
| if (auto sym = mod.NameOf(value)) { |
| return b.Symbols().New(sym.NameView()); |
| } |
| return b.Symbols().New("v"); |
| }); |
| if (auto* name = std::get_if<Symbol>(&existing); TINT_LIKELY(name)) { |
| return *name; |
| } |
| |
| TINT_ICE(IR, b.Diagnostics()) << "BindName(" << value->TypeInfo().name |
| << ") called on value that has non-name binding"; |
| return {}; |
| } |
| |
| template <typename T> |
| void Bind(ir::Value* value, const T* expr) { |
| TINT_ASSERT(IR, value); |
| if (CanInline(value)) { |
| // Value will be inlined at its place of usage. |
| bool added = bindings_.Add(value, expr); |
| if (TINT_UNLIKELY(!added)) { |
| TINT_ICE(IR, b.Diagnostics()) |
| << "Bind(" << value->TypeInfo().name << ") called twice for same node"; |
| } |
| } else { |
| Append(b.Decl(b.Let(BindName(value), expr))); |
| } |
| } |
| |
| /// @returns true if the if the value can be inlined into its single place |
| /// of usage. Currently a value is inlined if it has a single usage and is unnamed. |
| /// TODO(crbug.com/tint/1902): This logic needs to check that the sequence of side-effecting |
| /// expressions is not changed by inlining the expression. This needs fixing. |
| bool CanInline(Value* val) { return val->Usages().Count() == 1 && !mod.NameOf(val).IsValid(); } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| // Helpers |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| bool AsShortCircuit(ir::If* i, |
| const StatementList& true_stmts, |
| const StatementList& false_stmts) { |
| if (!i->HasResults()) { |
| return false; |
| } |
| auto* result = i->Result(); |
| if (!result->Type()->Is<type::Bool>()) { |
| return false; // Wrong result type |
| } |
| if (i->Exits().Count() != 2) { |
| return false; // Doesn't have two exits |
| } |
| if (!true_stmts.IsEmpty() || !false_stmts.IsEmpty()) { |
| return false; // True or False blocks contain statements |
| } |
| |
| auto* cond = i->Condition(); |
| auto* true_val = i->True()->Back()->Operands().Front(); |
| auto* false_val = i->False()->Back()->Operands().Front(); |
| if (IsConstant(false_val, false)) { |
| // %res = if %cond { |
| // block { # true |
| // exit_if %true_val; |
| // } |
| // block { # false |
| // exit_if false; |
| // } |
| // } |
| // |
| // transform into: |
| // |
| // res = cond && true_val; |
| // |
| auto* lhs = Expr(cond); |
| auto* rhs = Expr(true_val); |
| Bind(result, b.LogicalAnd(lhs, rhs)); |
| return true; |
| } |
| if (IsConstant(true_val, true)) { |
| // %res = if %cond { |
| // block { # true |
| // exit_if true; |
| // } |
| // block { # false |
| // exit_if %false_val; |
| // } |
| // } |
| // |
| // transform into: |
| // |
| // res = cond || false_val; |
| // |
| auto* lhs = Expr(cond); |
| auto* rhs = Expr(false_val); |
| Bind(result, b.LogicalOr(lhs, rhs)); |
| return true; |
| } |
| return false; |
| } |
| |
| bool IsConstant(ir::Value* val, bool value) { |
| if (auto* c = val->As<ir::Constant>()) { |
| if (c->Type()->Is<type::Bool>()) { |
| return c->Value()->ValueAs<bool>() == value; |
| } |
| } |
| return false; |
| } |
| }; |
| |
| } // namespace |
| |
| Program ToProgram(Module& i) { |
| return State{i}.Run(); |
| } |
| |
| } // namespace tint::ir |