blob: c0ee5e36e6a683408b45e21cf3e9c44285035341 [file] [log] [blame]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h"
#include <string>
#include <tuple>
#include <utility>
#include "src/tint/lang/core/builtin_type.h"
#include "src/tint/lang/core/constant/splat.h"
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/core/ir/access.h"
#include "src/tint/lang/core/ir/binary.h"
#include "src/tint/lang/core/ir/bitcast.h"
#include "src/tint/lang/core/ir/block.h"
#include "src/tint/lang/core/ir/break_if.h"
#include "src/tint/lang/core/ir/call.h"
#include "src/tint/lang/core/ir/constant.h"
#include "src/tint/lang/core/ir/construct.h"
#include "src/tint/lang/core/ir/continue.h"
#include "src/tint/lang/core/ir/convert.h"
#include "src/tint/lang/core/ir/core_builtin_call.h"
#include "src/tint/lang/core/ir/discard.h"
#include "src/tint/lang/core/ir/exit_if.h"
#include "src/tint/lang/core/ir/exit_loop.h"
#include "src/tint/lang/core/ir/exit_switch.h"
#include "src/tint/lang/core/ir/if.h"
#include "src/tint/lang/core/ir/instruction.h"
#include "src/tint/lang/core/ir/let.h"
#include "src/tint/lang/core/ir/load.h"
#include "src/tint/lang/core/ir/load_vector_element.h"
#include "src/tint/lang/core/ir/loop.h"
#include "src/tint/lang/core/ir/module.h"
#include "src/tint/lang/core/ir/multi_in_block.h"
#include "src/tint/lang/core/ir/next_iteration.h"
#include "src/tint/lang/core/ir/return.h"
#include "src/tint/lang/core/ir/store.h"
#include "src/tint/lang/core/ir/store_vector_element.h"
#include "src/tint/lang/core/ir/switch.h"
#include "src/tint/lang/core/ir/swizzle.h"
#include "src/tint/lang/core/ir/unary.h"
#include "src/tint/lang/core/ir/unreachable.h"
#include "src/tint/lang/core/ir/user_call.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/core/ir/var.h"
#include "src/tint/lang/core/type/atomic.h"
#include "src/tint/lang/core/type/depth_multisampled_texture.h"
#include "src/tint/lang/core/type/depth_texture.h"
#include "src/tint/lang/core/type/multisampled_texture.h"
#include "src/tint/lang/core/type/pointer.h"
#include "src/tint/lang/core/type/reference.h"
#include "src/tint/lang/core/type/sampler.h"
#include "src/tint/lang/core/type/texture.h"
#include "src/tint/lang/wgsl/ir/builtin_call.h"
#include "src/tint/lang/wgsl/program/program_builder.h"
#include "src/tint/lang/wgsl/resolver/resolve.h"
#include "src/tint/utils/containers/hashmap.h"
#include "src/tint/utils/containers/predicates.h"
#include "src/tint/utils/containers/reverse.h"
#include "src/tint/utils/containers/transform.h"
#include "src/tint/utils/containers/vector.h"
#include "src/tint/utils/macros/scoped_assignment.h"
#include "src/tint/utils/math/math.h"
#include "src/tint/utils/rtti/switch.h"
// 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_--)
using namespace tint::core::fluent_types; // NOLINT
namespace tint::wgsl::writer {
namespace {
class State {
public:
explicit State(const core::ir::Module& m) : mod(m) {}
Program Run(const ProgramOptions& options) {
if (auto res = core::ir::Validate(mod); res != Success) {
// IR module failed validation.
b.Diagnostics() = res.Failure().reason;
return Program{resolver::Resolve(b)};
}
RootBlock(mod.root_block);
// TODO(crbug.com/tint/1902): Emit user-declared types
for (auto& fn : mod.functions) {
Fn(fn);
}
if (options.allow_non_uniform_derivatives) {
// Suppress errors regarding non-uniform derivative operations if requested, by adding a
// diagnostic directive to the module.
b.DiagnosticDirective(wgsl::DiagnosticSeverity::kOff, "derivative_uniformity");
}
return Program{resolver::Resolve(b, options.allowed_features)};
}
private:
/// The AST representation for an IR pointer type
enum class PtrKind {
kPtr, // IR pointer is represented in the AST as a pointer
kRef, // IR pointer is represented in the AST as a reference
};
/// The source IR module
const core::ir::Module& mod;
/// The target ProgramBuilder
ProgramBuilder b;
/// The structure for a value held by a 'let', 'var' or parameter.
struct VariableValue {
Symbol name; // Name of the variable
PtrKind ptr_kind = PtrKind::kRef;
};
/// The structure for an inlined value
struct InlinedValue {
const ast::Expression* expr = nullptr;
PtrKind ptr_kind = PtrKind::kRef;
};
/// 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 {};
using ValueBinding = std::variant<VariableValue, InlinedValue, ConsumedValue>;
/// IR values to their representation
Hashmap<const core::ir::Value*, ValueBinding, 32> bindings_;
/// Names for values
Hashmap<const core::ir::Value*, Symbol, 32> names_;
/// 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 =
Vector<const ast::Statement*, decltype(ast::BlockStatement::statements)::static_length>;
StatementList* statements_ = nullptr;
/// The current switch case block
const core::ir::Block* current_switch_case_ = nullptr;
/// Set of enable directives emitted.
Hashset<wgsl::Extension, 4> enables_;
/// Map of struct to output program name.
Hashmap<const core::type::Struct*, Symbol, 8> structs_;
/// True if 'diagnostic(off, derivative_uniformity)' has been emitted
bool disabled_derivative_uniformity_ = false;
void RootBlock(const core::ir::Block* root) {
for (auto* inst : *root) {
tint::Switch(
inst, //
[&](const core::ir::Var* var) { Var(var); }, //
TINT_ICE_ON_NO_MATCH);
}
}
const ast::Function* Fn(const core::ir::Function* fn) {
SCOPED_NESTING();
// Emit parameters.
static constexpr size_t N = decltype(ast::Function::params)::static_length;
auto params = tint::Transform<N>(fn->Params(), [&](const core::ir::FunctionParam* param) {
auto ty = Type(param->Type());
auto name = NameFor(param);
Vector<const ast::Attribute*, 1> attrs{};
Bind(param, name, PtrKind::kPtr);
// Emit parameter attributes.
if (auto builtin = param->Builtin()) {
switch (builtin.value()) {
case core::BuiltinValue::kVertexIndex:
attrs.Push(b.Builtin(core::BuiltinValue::kVertexIndex));
break;
case core::BuiltinValue::kInstanceIndex:
attrs.Push(b.Builtin(core::BuiltinValue::kInstanceIndex));
break;
case core::BuiltinValue::kPosition:
attrs.Push(b.Builtin(core::BuiltinValue::kPosition));
break;
case core::BuiltinValue::kFrontFacing:
attrs.Push(b.Builtin(core::BuiltinValue::kFrontFacing));
break;
case core::BuiltinValue::kLocalInvocationId:
attrs.Push(b.Builtin(core::BuiltinValue::kLocalInvocationId));
break;
case core::BuiltinValue::kLocalInvocationIndex:
attrs.Push(b.Builtin(core::BuiltinValue::kLocalInvocationIndex));
break;
case core::BuiltinValue::kGlobalInvocationId:
attrs.Push(b.Builtin(core::BuiltinValue::kGlobalInvocationId));
break;
case core::BuiltinValue::kWorkgroupId:
attrs.Push(b.Builtin(core::BuiltinValue::kWorkgroupId));
break;
case core::BuiltinValue::kNumWorkgroups:
attrs.Push(b.Builtin(core::BuiltinValue::kNumWorkgroups));
break;
case core::BuiltinValue::kSampleIndex:
attrs.Push(b.Builtin(core::BuiltinValue::kSampleIndex));
break;
case core::BuiltinValue::kSampleMask:
attrs.Push(b.Builtin(core::BuiltinValue::kSampleMask));
break;
case core::BuiltinValue::kSubgroupInvocationId:
Enable(wgsl::Extension::kChromiumExperimentalSubgroups);
attrs.Push(b.Builtin(core::BuiltinValue::kSubgroupInvocationId));
break;
case core::BuiltinValue::kSubgroupSize:
Enable(wgsl::Extension::kChromiumExperimentalSubgroups);
attrs.Push(b.Builtin(core::BuiltinValue::kSubgroupSize));
break;
default:
TINT_UNIMPLEMENTED() << builtin.value();
break;
}
}
if (auto loc = param->Location()) {
attrs.Push(b.Location(AInt(loc->value)));
if (auto interp = loc->interpolation) {
attrs.Push(b.Interpolate(interp->type, interp->sampling));
}
}
if (param->Invariant()) {
attrs.Push(b.Invariant());
}
return b.Param(name, ty, std::move(attrs));
});
auto name = NameFor(fn);
auto ret_ty = Type(fn->ReturnType());
auto* body = Block(fn->Block());
Vector<const ast::Attribute*, 1> attrs{};
Vector<const ast::Attribute*, 1> ret_attrs{};
// Emit entry point attributes.
switch (fn->Stage()) {
case core::ir::Function::PipelineStage::kUndefined:
break;
case core::ir::Function::PipelineStage::kCompute: {
auto wgsize = fn->WorkgroupSize().value();
attrs.Push(b.Stage(ast::PipelineStage::kCompute));
attrs.Push(b.WorkgroupSize(AInt(wgsize[0]), AInt(wgsize[1]), AInt(wgsize[2])));
break;
}
case core::ir::Function::PipelineStage::kFragment:
attrs.Push(b.Stage(ast::PipelineStage::kFragment));
break;
case core::ir::Function::PipelineStage::kVertex:
attrs.Push(b.Stage(ast::PipelineStage::kVertex));
break;
}
// Emit return type attributes.
if (auto builtin = fn->ReturnBuiltin()) {
switch (builtin.value()) {
case core::BuiltinValue::kPosition:
ret_attrs.Push(b.Builtin(core::BuiltinValue::kPosition));
break;
case core::BuiltinValue::kFragDepth:
ret_attrs.Push(b.Builtin(core::BuiltinValue::kFragDepth));
break;
case core::BuiltinValue::kSampleMask:
ret_attrs.Push(b.Builtin(core::BuiltinValue::kSampleMask));
break;
default:
TINT_UNIMPLEMENTED() << builtin.value();
break;
}
}
if (auto loc = fn->ReturnLocation()) {
ret_attrs.Push(b.Location(AInt(loc->value)));
if (auto interp = loc->interpolation) {
ret_attrs.Push(b.Interpolate(interp->type, interp->sampling));
}
}
if (fn->ReturnInvariant()) {
ret_attrs.Push(b.Invariant());
}
return b.Func(name, std::move(params), ret_ty, body, std::move(attrs),
std::move(ret_attrs));
}
const ast::BlockStatement* Block(const core::ir::Block* block) {
// TODO(crbug.com/tint/1902): Handle block arguments.
return b.Block(Statements(block));
}
StatementList Statements(const core::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(const core::ir::Instruction* inst) {
tint::Switch(
inst, //
[&](const core::ir::Access* i) { Access(i); }, //
[&](const core::ir::Binary* i) { Binary(i); }, //
[&](const core::ir::BreakIf* i) { BreakIf(i); }, //
[&](const core::ir::Call* i) { Call(i); }, //
[&](const core::ir::Continue*) {}, //
[&](const core::ir::ExitIf*) {}, //
[&](const core::ir::ExitLoop* i) { ExitLoop(i); }, //
[&](const core::ir::ExitSwitch* i) { ExitSwitch(i); }, //
[&](const core::ir::If* i) { If(i); }, //
[&](const core::ir::Let* i) { Let(i); }, //
[&](const core::ir::Load* l) { Load(l); }, //
[&](const core::ir::LoadVectorElement* i) { LoadVectorElement(i); }, //
[&](const core::ir::Loop* l) { Loop(l); }, //
[&](const core::ir::NextIteration*) {}, //
[&](const core::ir::Return* i) { Return(i); }, //
[&](const core::ir::Store* i) { Store(i); }, //
[&](const core::ir::StoreVectorElement* i) { StoreVectorElement(i); }, //
[&](const core::ir::Switch* i) { Switch(i); }, //
[&](const core::ir::Swizzle* i) { Swizzle(i); }, //
[&](const core::ir::Unary* i) { Unary(i); }, //
[&](const core::ir::Unreachable*) {}, //
[&](const core::ir::Var* i) { Var(i); }, //
TINT_ICE_ON_NO_MATCH);
}
void If(const core::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(const core::ir::Loop* l) {
SCOPED_NESTING();
// Build all the initializer statements
auto init_stmts = Statements(l->Initializer());
// If there's a single initializer statement and meets the WGSL 'for_init' pattern, then
// this can be used as the initializer for a for-loop.
// @see https://www.w3.org/TR/WGSL/#syntax-for_init
auto* init = (init_stmts.Length() == 1) &&
init_stmts.Front()
->IsAnyOf<ast::VariableDeclStatement, ast::AssignmentStatement,
ast::CompoundAssignmentStatement,
ast::IncrementDecrementStatement, ast::CallStatement>()
? init_stmts.Front()
: nullptr;
// Build the loop body statements. If the loop body starts with a if with the following
// pattern, then treat it as the loop condition:
// if cond {
// block { exit_if }
// block { exit_loop }
// }
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<core::ir::If>()) {
if (if_->Results().IsEmpty() && //
if_->True()->Length() == 1 && //
if_->False()->Length() == 1 && //
tint::Is<core::ir::ExitIf>(if_->True()->Front()) && //
tint::Is<core::ir::ExitLoop>(if_->False()->Front())) {
// Matched the loop condition.
cond = Expr(if_->Condition());
continue; // Don't emit this as an instruction in the body.
}
}
}
// Process the loop body instruction. Append to 'body_stmts'
Instruction(inst);
}
}
// Build any continuing statements
auto cont_stmts = Statements(l->Continuing());
// If there's a single continuing statement and meets the WGSL 'for_update' pattern then
// this can be used as the continuing for a for-loop.
// @see https://www.w3.org/TR/WGSL/#syntax-for_update
auto* cont =
(cont_stmts.Length() == 1) &&
cont_stmts.Front()
->IsAnyOf<ast::AssignmentStatement, ast::CompoundAssignmentStatement,
ast::IncrementDecrementStatement, ast::CallStatement>()
? cont_stmts.Front()
: nullptr;
// Depending on 'init', 'cond' and 'cont', build a 'for', 'while' or 'loop'
const ast::Statement* loop = nullptr;
if ((!cont && !cont_stmts.IsEmpty()) // Non-trivial continuing
|| !cond // or non-trivial or no condition
) {
// Build a loop
if (cond) {
body_stmts.Insert(0, b.If(b.Not(cond), b.Block(b.Break())));
}
auto* body = b.Block(std::move(body_stmts));
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));
}
} else if (init || cont) {
// Build a for-loop
auto* body = b.Block(std::move(body_stmts));
loop = b.For(init, cond, cont, body);
if (!init && !init_stmts.IsEmpty()) {
init_stmts.Push(loop);
loop = b.Block(std::move(init_stmts));
}
} else {
// Build a while-loop
auto* body = b.Block(std::move(body_stmts));
loop = b.While(cond, body);
if (!init_stmts.IsEmpty()) {
init_stmts.Push(loop);
loop = b.Block(std::move(init_stmts));
}
}
statements_->Push(loop);
}
void Switch(const core::ir::Switch* s) {
SCOPED_NESTING();
auto* cond = Expr(s->Condition());
auto cases = tint::Transform<4>(
s->Cases(), //
[&](const core::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 = tint::Transform(c.selectors, //
[&](const core::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 core::ir::ExitSwitch* e) {
if (current_switch_case_ && current_switch_case_->Terminator() == e) {
return; // No need to emit
}
Append(b.Break());
}
void ExitLoop(const core::ir::ExitLoop*) { Append(b.Break()); }
void BreakIf(const core::ir::BreakIf* i) { Append(b.BreakIf(Expr(i->Condition()))); }
void Return(const core::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() << "expected 1 value for return, got " << ret->Args().Length();
return;
}
Append(b.Return(Expr(ret->Args().Front())));
}
void Var(const core::ir::Var* var) {
auto* val = var->Result(0);
auto* ptr = As<core::type::Pointer>(val->Type());
auto ty = Type(ptr->StoreType());
Symbol name = NameFor(var->Result(0));
Bind(var->Result(0), name, PtrKind::kRef);
Vector<const ast::Attribute*, 4> attrs;
if (auto bp = var->BindingPoint()) {
attrs.Push(b.Group(AInt(bp->group)));
attrs.Push(b.Binding(AInt(bp->binding)));
}
const ast::Expression* init = nullptr;
if (var->Initializer()) {
init = Expr(var->Initializer());
}
switch (ptr->AddressSpace()) {
case core::AddressSpace::kFunction:
Append(b.Decl(b.Var(name, ty, init, std::move(attrs))));
return;
case core::AddressSpace::kStorage:
b.GlobalVar(name, ty, init, ptr->Access(), ptr->AddressSpace(), std::move(attrs));
return;
case core::AddressSpace::kHandle:
b.GlobalVar(name, ty, init, std::move(attrs));
return;
default:
b.GlobalVar(name, ty, init, ptr->AddressSpace(), std::move(attrs));
return;
}
}
void Let(const core::ir::Let* let) {
auto* result = let->Result(0);
if (mod.NameOf(result).IsValid() || result->NumUsages() > 0) {
Symbol name = NameFor(result);
Append(b.Decl(b.Let(name, Expr(let->Value(), PtrKind::kPtr))));
Bind(result, name, PtrKind::kPtr);
} else {
Append(b.Assign(b.Phony(), Expr(let->Value(), PtrKind::kPtr)));
}
}
void Store(const core::ir::Store* store) {
auto* dst = Expr(store->To());
auto* src = Expr(store->From());
Append(b.Assign(dst, src));
}
void StoreVectorElement(const core::ir::StoreVectorElement* store) {
auto* ptr = Expr(store->To());
auto* val = Expr(store->Value());
Append(b.Assign(VectorMemberAccess(ptr, store->Index()), val));
}
void Call(const core::ir::Call* call) {
auto args = tint::Transform<4>(call->Args(), [&](const core::ir::Value* arg) {
// Pointer-like arguments are passed by pointer, never reference.
return Expr(arg, PtrKind::kPtr);
});
tint::Switch(
call, //
[&](const core::ir::UserCall* c) {
auto* expr = b.Call(NameFor(c->Target()), std::move(args));
if (call->Results().IsEmpty() || !call->Result(0)->IsUsed()) {
Append(b.CallStmt(expr));
return;
}
Bind(c->Result(0), expr, PtrKind::kPtr);
},
[&](const wgsl::ir::BuiltinCall* c) {
if (!disabled_derivative_uniformity_ && RequiresDerivativeUniformity(c->Func())) {
// TODO(crbug.com/tint/1985): Be smarter about disabling derivative uniformity.
b.DiagnosticDirective(wgsl::DiagnosticSeverity::kOff,
wgsl::CoreDiagnosticRule::kDerivativeUniformity);
disabled_derivative_uniformity_ = true;
}
switch (c->Func()) {
case wgsl::BuiltinFn::kSubgroupBallot:
case wgsl::BuiltinFn::kSubgroupBroadcast:
Enable(wgsl::Extension::kChromiumExperimentalSubgroups);
break;
default:
break;
}
auto* expr = b.Call(c->Func(), std::move(args));
if (call->Results().IsEmpty() || call->Result(0)->Type()->Is<core::type::Void>()) {
Append(b.CallStmt(expr));
return;
}
Bind(c->Result(0), expr, PtrKind::kPtr);
},
[&](const core::ir::Construct* c) {
auto ty = Type(c->Result(0)->Type());
Bind(c->Result(0), b.Call(ty, std::move(args)), PtrKind::kPtr);
},
[&](const core::ir::Convert* c) {
auto ty = Type(c->Result(0)->Type());
Bind(c->Result(0), b.Call(ty, std::move(args)), PtrKind::kPtr);
},
[&](const core::ir::Bitcast* c) {
auto ty = Type(c->Result(0)->Type());
Bind(c->Result(0), b.Bitcast(ty, args[0]), PtrKind::kPtr);
},
[&](const core::ir::Discard*) { Append(b.Discard()); }, //
TINT_ICE_ON_NO_MATCH);
}
void Load(const core::ir::Load* l) { Bind(l->Result(0), Expr(l->From())); }
void LoadVectorElement(const core::ir::LoadVectorElement* load) {
auto* ptr = Expr(load->From());
Bind(load->Result(0), VectorMemberAccess(ptr, load->Index()));
}
void Unary(const core::ir::Unary* u) {
const ast::Expression* expr = nullptr;
switch (u->Op()) {
case core::UnaryOp::kComplement:
expr = b.Complement(Expr(u->Val()));
break;
case core::UnaryOp::kNegation:
expr = b.Negation(Expr(u->Val()));
break;
default:
TINT_UNIMPLEMENTED() << u->Op();
break;
}
Bind(u->Result(0), expr);
}
void Access(const core::ir::Access* a) {
auto* expr = Expr(a->Object());
auto* obj_ty = a->Object()->Type()->UnwrapPtr();
for (auto* index : a->Indices()) {
tint::Switch(
obj_ty,
[&](const core::type::Vector* vec) {
TINT_DEFER(obj_ty = vec->type());
expr = VectorMemberAccess(expr, index);
},
[&](const core::type::Matrix* mat) {
obj_ty = mat->ColumnType();
expr = b.IndexAccessor(expr, Expr(index));
},
[&](const core::type::Array* arr) {
obj_ty = arr->ElemType();
expr = b.IndexAccessor(expr, Expr(index));
},
[&](const core::type::Struct* s) {
if (auto* c = index->As<core::ir::Constant>()) {
auto i = c->Value()->ValueAs<uint32_t>();
TINT_ASSERT_OR_RETURN(i < s->Members().Length());
auto* member = s->Members()[i];
obj_ty = member->Type();
expr = b.MemberAccessor(expr, member->Name().NameView());
} else {
TINT_ICE() << "invalid index for struct type: " << index->TypeInfo().name;
}
}, //
TINT_ICE_ON_NO_MATCH);
}
Bind(a->Result(0), expr);
}
void Swizzle(const core::ir::Swizzle* s) {
auto* vec = Expr(s->Object());
Vector<char, 4> components;
for (uint32_t i : s->Indices()) {
if (TINT_UNLIKELY(i >= 4)) {
TINT_ICE() << "invalid swizzle index: " << i;
return;
}
components.Push("xyzw"[i]);
}
auto* swizzle =
b.MemberAccessor(vec, std::string_view(components.begin(), components.Length()));
Bind(s->Result(0), swizzle);
}
void Binary(const core::ir::Binary* e) {
if (e->Op() == core::BinaryOp::kEqual) {
auto* rhs = e->RHS()->As<core::ir::Constant>();
if (rhs && rhs->Type()->Is<core::type::Bool>() &&
rhs->Value()->ValueAs<bool>() == false) {
// expr == false
Bind(e->Result(0), b.Not(Expr(e->LHS())));
return;
}
}
auto* lhs = Expr(e->LHS());
auto* rhs = Expr(e->RHS());
const ast::Expression* expr = nullptr;
switch (e->Op()) {
case core::BinaryOp::kAdd:
expr = b.Add(lhs, rhs);
break;
case core::BinaryOp::kSubtract:
expr = b.Sub(lhs, rhs);
break;
case core::BinaryOp::kMultiply:
expr = b.Mul(lhs, rhs);
break;
case core::BinaryOp::kDivide:
expr = b.Div(lhs, rhs);
break;
case core::BinaryOp::kModulo:
expr = b.Mod(lhs, rhs);
break;
case core::BinaryOp::kAnd:
expr = b.And(lhs, rhs);
break;
case core::BinaryOp::kOr:
expr = b.Or(lhs, rhs);
break;
case core::BinaryOp::kXor:
expr = b.Xor(lhs, rhs);
break;
case core::BinaryOp::kEqual:
expr = b.Equal(lhs, rhs);
break;
case core::BinaryOp::kNotEqual:
expr = b.NotEqual(lhs, rhs);
break;
case core::BinaryOp::kLessThan:
expr = b.LessThan(lhs, rhs);
break;
case core::BinaryOp::kGreaterThan:
expr = b.GreaterThan(lhs, rhs);
break;
case core::BinaryOp::kLessThanEqual:
expr = b.LessThanEqual(lhs, rhs);
break;
case core::BinaryOp::kGreaterThanEqual:
expr = b.GreaterThanEqual(lhs, rhs);
break;
case core::BinaryOp::kShiftLeft:
expr = b.Shl(lhs, rhs);
break;
case core::BinaryOp::kShiftRight:
expr = b.Shr(lhs, rhs);
break;
case core::BinaryOp::kLogicalAnd:
expr = b.LogicalAnd(lhs, rhs);
break;
case core::BinaryOp::kLogicalOr:
expr = b.LogicalOr(lhs, rhs);
break;
}
Bind(e->Result(0), expr);
}
TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
const ast::Expression* Expr(const core::ir::Value* value,
PtrKind want_ptr_kind = PtrKind::kRef) {
using ExprAndPtrKind = std::pair<const ast::Expression*, PtrKind>;
auto [expr, got_ptr_kind] = tint::Switch(
value,
[&](const core::ir::Constant* c) -> ExprAndPtrKind {
return {Constant(c), PtrKind::kRef};
},
[&](Default) -> ExprAndPtrKind {
auto lookup = bindings_.Get(value);
if (TINT_UNLIKELY(!lookup)) {
TINT_ICE() << "Expr(" << (value ? value->TypeInfo().name : "null")
<< ") value has no expression";
return {};
}
return std::visit(
[&](auto&& got) -> ExprAndPtrKind {
using T = std::decay_t<decltype(got)>;
if constexpr (std::is_same_v<T, VariableValue>) {
return {b.Expr(got.name), got.ptr_kind};
}
if constexpr (std::is_same_v<T, InlinedValue>) {
auto result = ExprAndPtrKind{got.expr, got.ptr_kind};
// Single use (inlined) expression.
// Mark the bindings_ map entry as consumed.
*lookup = ConsumedValue{};
return result;
}
if constexpr (std::is_same_v<T, ConsumedValue>) {
TINT_ICE() << "Expr(" << value->TypeInfo().name
<< ") called twice on the same value";
} else {
TINT_ICE()
<< "Expr(" << value->TypeInfo().name << ") has unhandled value";
}
return {};
},
*lookup);
});
if (!expr) {
return b.Expr("<error>");
}
if (value->Type()->Is<core::type::Pointer>()) {
return ToPtrKind(expr, got_ptr_kind, want_ptr_kind);
}
return expr;
}
TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
const ast::Expression* Constant(const core::ir::Constant* c) { return Constant(c->Value()); }
const ast::Expression* Constant(const core::constant::Value* c) {
auto composite = [&](bool can_splat) {
auto ty = Type(c->Type());
if (c->AllZero()) {
return b.Call(ty);
}
if (can_splat && c->Is<core::constant::Splat>()) {
return b.Call(ty, Constant(c->Index(0)));
}
Vector<const ast::Expression*, 8> els;
for (size_t i = 0, n = c->NumElements(); i < n; i++) {
els.Push(Constant(c->Index(i)));
}
return b.Call(ty, std::move(els));
};
return tint::Switch(
c->Type(), //
[&](const core::type::I32*) { return b.Expr(c->ValueAs<i32>()); },
[&](const core::type::U32*) { return b.Expr(c->ValueAs<u32>()); },
[&](const core::type::F32*) { return b.Expr(c->ValueAs<f32>()); },
[&](const core::type::F16*) {
Enable(wgsl::Extension::kF16);
return b.Expr(c->ValueAs<f16>());
},
[&](const core::type::Bool*) { return b.Expr(c->ValueAs<bool>()); },
[&](const core::type::Array*) { return composite(/* can_splat */ false); },
[&](const core::type::Vector*) { return composite(/* can_splat */ true); },
[&](const core::type::Matrix*) { return composite(/* can_splat */ false); },
[&](const core::type::Struct*) { return composite(/* can_splat */ false); }, //
TINT_ICE_ON_NO_MATCH);
}
void Enable(wgsl::Extension ext) {
if (enables_.Add(ext)) {
b.Enable(ext);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// 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 core::type::Type* ty) {
return tint::Switch(
ty, //
[&](const core::type::Void*) { return ast::Type{}; }, //
[&](const core::type::I32*) { return b.ty.i32(); }, //
[&](const core::type::U32*) { return b.ty.u32(); }, //
[&](const core::type::F16*) {
Enable(wgsl::Extension::kF16);
return b.ty.f16();
},
[&](const core::type::F32*) { return b.ty.f32(); }, //
[&](const core::type::Bool*) { return b.ty.bool_(); },
[&](const core::type::Matrix* m) {
return b.ty.mat(Type(m->type()), m->columns(), m->rows());
},
[&](const core::type::Vector* v) {
auto el = Type(v->type());
if (v->Packed()) {
TINT_ASSERT(v->Width() == 3u);
return b.ty(core::BuiltinType::kPackedVec3, el);
} else {
return b.ty.vec(el, v->Width());
}
},
[&](const core::type::Array* a) {
auto el = Type(a->ElemType());
Vector<const ast::Attribute*, 1> attrs;
if (!a->IsStrideImplicit()) {
attrs.Push(b.Stride(a->Stride()));
}
if (a->Count()->Is<core::type::RuntimeArrayCount>()) {
return b.ty.array(el, std::move(attrs));
}
auto count = a->ConstantCount();
if (TINT_UNLIKELY(!count)) {
TINT_ICE() << core::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 core::type::Struct* s) { return Struct(s); },
[&](const core::type::Atomic* a) { return b.ty.atomic(Type(a->Type())); },
[&](const core::type::DepthTexture* t) { return b.ty.depth_texture(t->dim()); },
[&](const core::type::DepthMultisampledTexture* t) {
return b.ty.depth_multisampled_texture(t->dim());
},
[&](const core::type::ExternalTexture*) { return b.ty.external_texture(); },
[&](const core::type::MultisampledTexture* t) {
auto el = Type(t->type());
return b.ty.multisampled_texture(t->dim(), el);
},
[&](const core::type::SampledTexture* t) {
auto el = Type(t->type());
return b.ty.sampled_texture(t->dim(), el);
},
[&](const core::type::StorageTexture* t) {
return b.ty.storage_texture(t->dim(), t->texel_format(), t->access());
},
[&](const core::type::Sampler* s) { return b.ty.sampler(s->kind()); },
[&](const core::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 == core::AddressSpace::kStorage
? p->Access()
: core::Access::kUndefined;
return b.ty.ptr(address_space, el, access);
},
[&](const core::type::Reference*) {
TINT_ICE() << "reference types should never appear in the IR";
return b.ty.i32();
}, //
TINT_ICE_ON_NO_MATCH);
}
ast::Type Struct(const core::type::Struct* s) {
auto n = structs_.GetOrAdd(s, [&] {
auto members = tint::Transform<8>(s->Members(), [&](const core::type::StructMember* m) {
auto ty = Type(m->Type());
const auto& ir_attrs = m->Attributes();
Vector<const ast::Attribute*, 4> ast_attrs;
if (m->Type()->Align() != m->Align()) {
ast_attrs.Push(b.MemberAlign(u32(m->Align())));
}
if (m->Type()->Size() != m->Size()) {
ast_attrs.Push(b.MemberSize(u32(m->Size())));
}
if (auto location = ir_attrs.location) {
ast_attrs.Push(b.Location(u32(*location)));
}
if (auto blend_src = ir_attrs.blend_src) {
Enable(wgsl::Extension::kChromiumInternalDualSourceBlending);
ast_attrs.Push(b.BlendSrc(u32(*blend_src)));
}
if (auto builtin = ir_attrs.builtin) {
if (RequiresSubgroups(*builtin)) {
Enable(wgsl::Extension::kChromiumExperimentalSubgroups);
}
ast_attrs.Push(b.Builtin(*builtin));
}
if (auto interpolation = ir_attrs.interpolation) {
ast_attrs.Push(b.Interpolate(interpolation->type, interpolation->sampling));
}
if (ir_attrs.invariant) {
ast_attrs.Push(b.Invariant());
}
return b.Member(m->Name().NameView(), ty, std::move(ast_attrs));
});
// TODO(crbug.com/tint/1902): Emit structure attributes
Vector<const ast::Attribute*, 2> attrs;
auto name = b.Symbols().Register(s->Name().NameView());
b.Structure(name, std::move(members), std::move(attrs));
return name;
});
return b.ty(n);
}
const ast::Expression* ToPtrKind(const ast::Expression* in, PtrKind got, PtrKind want) {
if (want == PtrKind::kRef && got == PtrKind::kPtr) {
return b.Deref(in);
}
if (want == PtrKind::kPtr && got == PtrKind::kRef) {
return b.AddressOf(in);
}
return in;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Bindings
////////////////////////////////////////////////////////////////////////////////////////////////
/// @returns the AST name for the given value, creating and returning a new name on the first
/// call.
Symbol NameFor(const core::ir::Value* value, std::string_view suggested = {}) {
return names_.GetOrAdd(value, [&] {
if (!suggested.empty()) {
return b.Symbols().Register(suggested);
}
if (auto sym = mod.NameOf(value)) {
return b.Symbols().Register(sym.NameView());
}
return b.Symbols().New("v");
});
}
/// Associates the IR value @p value with the AST expression @p expr.
/// @p ptr_kind defines how pointer values are represented by @p expr.
void Bind(const core::ir::Value* value,
const ast::Expression* expr,
PtrKind ptr_kind = PtrKind::kRef) {
TINT_ASSERT(value);
// Value will be inlined at its place of usage.
if (TINT_UNLIKELY(!bindings_.Add(value, InlinedValue{expr, ptr_kind}))) {
TINT_ICE() << "Bind(" << value->TypeInfo().name << ") called twice for same value";
}
}
/// Associates the IR value @p value with the AST 'var', 'let' or parameter with the name @p
/// name.
/// @p ptr_kind defines how pointer values are represented by @p expr.
void Bind(const core::ir::Value* value, Symbol name, PtrKind ptr_kind) {
TINT_ASSERT(value);
bool added = bindings_.Add(value, VariableValue{name, ptr_kind});
if (TINT_UNLIKELY(!added)) {
TINT_ICE() << "Bind(" << value->TypeInfo().name << ") called twice for same value";
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////////////////////////////
bool AsShortCircuit(const core::ir::If* i,
const StatementList& true_stmts,
const StatementList& false_stmts) {
if (i->Results().IsEmpty()) {
return false;
}
auto* result = i->Result(0);
if (!result->Type()->Is<core::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(const core::ir::Value* val, bool value) {
if (auto* c = val->As<core::ir::Constant>()) {
if (c->Type()->Is<core::type::Bool>()) {
return c->Value()->ValueAs<bool>() == value;
}
}
return false;
}
const ast::Expression* VectorMemberAccess(const ast::Expression* expr,
const core::ir::Value* index) {
if (auto* c = index->As<core::ir::Constant>()) {
switch (c->Value()->ValueAs<int>()) {
case 0:
return b.MemberAccessor(expr, "x");
case 1:
return b.MemberAccessor(expr, "y");
case 2:
return b.MemberAccessor(expr, "z");
case 3:
return b.MemberAccessor(expr, "w");
}
}
return b.IndexAccessor(expr, Expr(index));
}
bool RequiresDerivativeUniformity(wgsl::BuiltinFn fn) {
switch (fn) {
case wgsl::BuiltinFn::kDpdxCoarse:
case wgsl::BuiltinFn::kDpdyCoarse:
case wgsl::BuiltinFn::kFwidthCoarse:
case wgsl::BuiltinFn::kDpdxFine:
case wgsl::BuiltinFn::kDpdyFine:
case wgsl::BuiltinFn::kFwidthFine:
case wgsl::BuiltinFn::kDpdx:
case wgsl::BuiltinFn::kDpdy:
case wgsl::BuiltinFn::kFwidth:
case wgsl::BuiltinFn::kTextureSample:
case wgsl::BuiltinFn::kTextureSampleBias:
case wgsl::BuiltinFn::kTextureSampleCompare:
return true;
default:
return false;
}
}
/// @returns true if the builtin value requires the kChromiumExperimentalSubgroups extension to
/// be enabled.
bool RequiresSubgroups(core::BuiltinValue builtin) {
switch (builtin) {
case core::BuiltinValue::kSubgroupInvocationId:
case core::BuiltinValue::kSubgroupSize:
return true;
default:
return false;
}
}
};
} // namespace
Program IRToProgram(const core::ir::Module& i, const ProgramOptions& options) {
return State{i}.Run(options);
}
} // namespace tint::wgsl::writer