[ir] Emit `var` and `let` into the IR
This CL adds a nodes for `var` variables. In order to support global var a new module level block is added, the `root_block`. It only exists if there are declarations which are emitted into it. The initializer for the `var` is assigned through a `store` instruction. A `let` declaration is just the initializer result. Bug: tint:1897 Change-Id: Icb949699ef643988151cc52026f9ad6a12cdd93f Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/130761 Reviewed-by: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
This commit is contained in:
parent
ae7df9e8c6
commit
c970e806db
|
@ -1173,6 +1173,8 @@ libtint_source_set("libtint_ir_src") {
|
|||
"ir/user_call.h",
|
||||
"ir/value.cc",
|
||||
"ir/value.h",
|
||||
"ir/var.cc",
|
||||
"ir/var.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
|
|
|
@ -750,6 +750,8 @@ if(${TINT_BUILD_IR})
|
|||
ir/user_call.h
|
||||
ir/value.cc
|
||||
ir/value.h
|
||||
ir/var.cc
|
||||
ir/var.h
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -24,6 +24,17 @@ Builder::Builder(Module&& mod) : ir(std::move(mod)) {}
|
|||
|
||||
Builder::~Builder() = default;
|
||||
|
||||
ir::Block* Builder::CreateRootBlockIfNeeded() {
|
||||
if (!ir.root_block) {
|
||||
ir.root_block = CreateBlock();
|
||||
|
||||
// Everything in the module scope must have been const-eval's, so everything will go into a
|
||||
// single block. So, we can create the terminator for the root-block now.
|
||||
ir.root_block->branch.target = CreateTerminator();
|
||||
}
|
||||
return ir.root_block;
|
||||
}
|
||||
|
||||
Block* Builder::CreateBlock() {
|
||||
return ir.flow_nodes.Create<Block>();
|
||||
}
|
||||
|
@ -225,4 +236,10 @@ ir::Store* Builder::Store(Value* to, Value* from) {
|
|||
return ir.instructions.Create<ir::Store>(to, from);
|
||||
}
|
||||
|
||||
ir::Var* Builder::Declare(const type::Type* type,
|
||||
builtin::AddressSpace address_space,
|
||||
builtin::Access access) {
|
||||
return ir.instructions.Create<ir::Var>(next_inst_id(), type, address_space, access);
|
||||
}
|
||||
|
||||
} // namespace tint::ir
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "src/tint/ir/unary.h"
|
||||
#include "src/tint/ir/user_call.h"
|
||||
#include "src/tint/ir/value.h"
|
||||
#include "src/tint/ir/var.h"
|
||||
#include "src/tint/type/bool.h"
|
||||
#include "src/tint/type/f16.h"
|
||||
#include "src/tint/type/f32.h"
|
||||
|
@ -358,6 +359,19 @@ class Builder {
|
|||
/// @returns the instruction
|
||||
ir::Store* Store(Value* to, Value* from);
|
||||
|
||||
/// Creates a new `var` declaration
|
||||
/// @param type the var type
|
||||
/// @param address_space the address space
|
||||
/// @param access the access mode
|
||||
/// @returns the instruction
|
||||
ir::Var* Declare(const type::Type* type,
|
||||
builtin::AddressSpace address_space,
|
||||
builtin::Access access);
|
||||
|
||||
/// Retrieves the root block for the module, creating if necessary
|
||||
/// @returns the root block
|
||||
ir::Block* CreateRootBlockIfNeeded();
|
||||
|
||||
/// The IR module.
|
||||
Module ir;
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "src/tint/ast/identifier_expression.h"
|
||||
#include "src/tint/ast/if_statement.h"
|
||||
#include "src/tint/ast/int_literal_expression.h"
|
||||
#include "src/tint/ast/let.h"
|
||||
#include "src/tint/ast/literal_expression.h"
|
||||
#include "src/tint/ast/loop_statement.h"
|
||||
#include "src/tint/ast/override.h"
|
||||
|
@ -50,6 +51,7 @@
|
|||
#include "src/tint/ast/switch_statement.h"
|
||||
#include "src/tint/ast/templated_identifier.h"
|
||||
#include "src/tint/ast/unary_op_expression.h"
|
||||
#include "src/tint/ast/var.h"
|
||||
#include "src/tint/ast/variable_decl_statement.h"
|
||||
#include "src/tint/ast/while_statement.h"
|
||||
#include "src/tint/ir/function.h"
|
||||
|
@ -69,8 +71,10 @@
|
|||
#include "src/tint/sem/value_constructor.h"
|
||||
#include "src/tint/sem/value_conversion.h"
|
||||
#include "src/tint/sem/value_expression.h"
|
||||
#include "src/tint/sem/variable.h"
|
||||
#include "src/tint/switch.h"
|
||||
#include "src/tint/type/void.h"
|
||||
#include "src/tint/utils/scoped_assignment.h"
|
||||
|
||||
namespace tint::ir {
|
||||
namespace {
|
||||
|
@ -169,10 +173,13 @@ ResultType BuilderImpl::Build() {
|
|||
[&](const ast::Alias*) {
|
||||
// Folded away and doesn't appear in the IR.
|
||||
},
|
||||
// [&](const ast::Variable* var) {
|
||||
// TODO(dsinclair): Implement
|
||||
// },
|
||||
[&](const ast::Function* func) { return EmitFunction(func); },
|
||||
[&](const ast::Variable* var) {
|
||||
// Setup the current flow node to be the root block for the module. The builder will
|
||||
// handle creating it if it doesn't exist already.
|
||||
TINT_SCOPED_ASSIGNMENT(current_flow_block, builder.CreateRootBlockIfNeeded());
|
||||
EmitVariable(var);
|
||||
},
|
||||
[&](const ast::Function* func) { EmitFunction(func); },
|
||||
// [&](const ast::Enable*) {
|
||||
// TODO(dsinclair): Implement? I think these need to be passed along so further stages
|
||||
// know what is enabled.
|
||||
|
@ -430,8 +437,8 @@ void BuilderImpl::EmitLoop(const ast::LoopStatement* stmt) {
|
|||
BranchToIfNeeded(loop_node->start.target);
|
||||
}
|
||||
|
||||
// The loop merge can get disconnected if the loop returns directly, or the continuing target
|
||||
// branches, eventually, to the merge, but nothing branched to the continuing target.
|
||||
// The loop merge can get disconnected if the loop returns directly, or the continuing
|
||||
// target branches, eventually, to the merge, but nothing branched to the continuing target.
|
||||
current_flow_block = loop_node->merge.target->As<Block>();
|
||||
if (!IsConnected(loop_node->merge.target)) {
|
||||
current_flow_block = nullptr;
|
||||
|
@ -613,9 +620,9 @@ void BuilderImpl::EmitContinue(const ast::ContinueStatement*) {
|
|||
}
|
||||
|
||||
// Discard is being treated as an instruction. The semantics in WGSL is demote_to_helper, so the
|
||||
// code has to continue as before it just predicates writes. If WGSL grows some kind of terminating
|
||||
// discard that would probably make sense as a FlowNode but would then require figuring out the
|
||||
// multi-level exit that is triggered.
|
||||
// code has to continue as before it just predicates writes. If WGSL grows some kind of
|
||||
// terminating discard that would probably make sense as a FlowNode but would then require
|
||||
// figuring out the multi-level exit that is triggered.
|
||||
void BuilderImpl::EmitDiscard(const ast::DiscardStatement*) {
|
||||
auto* inst = builder.Discard();
|
||||
current_flow_block->instructions.Push(inst);
|
||||
|
@ -691,14 +698,35 @@ utils::Result<Value*> BuilderImpl::EmitExpression(const ast::Expression* expr) {
|
|||
}
|
||||
|
||||
void BuilderImpl::EmitVariable(const ast::Variable* var) {
|
||||
auto* sem = program_->Sem().Get(var);
|
||||
|
||||
return tint::Switch( //
|
||||
var,
|
||||
// [&](const ast::Var* var) {
|
||||
// TODO(dsinclair): Implement
|
||||
// },
|
||||
// [&](const ast::Let*) {
|
||||
// TODO(dsinclair): Implement
|
||||
// },
|
||||
[&](const ast::Var* v) {
|
||||
auto* ty = sem->Type()->Clone(clone_ctx_.type_ctx);
|
||||
auto* val = builder.Declare(ty, sem->AddressSpace(), sem->Access());
|
||||
current_flow_block->instructions.Push(val);
|
||||
|
||||
if (v->initializer) {
|
||||
auto init = EmitExpression(v->initializer);
|
||||
if (!init) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* store = builder.Store(val, init.Get());
|
||||
current_flow_block->instructions.Push(store);
|
||||
}
|
||||
// TODO(dsinclair): Store the mapping from the var name to the `Declare` value
|
||||
},
|
||||
[&](const ast::Let* l) {
|
||||
// A `let` doesn't exist as a standalone item in the IR, it's just the result of the
|
||||
// initializer.
|
||||
auto init = EmitExpression(l->initializer);
|
||||
if (!init) {
|
||||
return;
|
||||
}
|
||||
// TODO(dsinclair): Store the mapping from the let name to the `init` value
|
||||
},
|
||||
[&](const ast::Override*) {
|
||||
add_error(var->source,
|
||||
"found an `Override` variable. The SubstituteOverrides "
|
||||
|
|
|
@ -1558,6 +1558,72 @@ TEST_F(IR_BuilderImplTest, EmitLiteral_U32) {
|
|||
EXPECT_EQ(2_u, val->As<constant::Scalar<u32>>()->ValueAs<f32>());
|
||||
}
|
||||
|
||||
TEST_F(IR_BuilderImplTest, Emit_GlobalVar_NoInit) {
|
||||
GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate);
|
||||
|
||||
auto r = Build();
|
||||
ASSERT_TRUE(r) << Error();
|
||||
auto m = r.Move();
|
||||
|
||||
EXPECT_EQ(Disassemble(m), R"(%fn0 = block
|
||||
%1(ref<private, u32, read_write>) = var private read_write
|
||||
ret
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(IR_BuilderImplTest, Emit_GlobalVar_Init) {
|
||||
auto* expr = Expr(2_u);
|
||||
GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate, expr);
|
||||
|
||||
auto r = Build();
|
||||
ASSERT_TRUE(r) << Error();
|
||||
auto m = r.Move();
|
||||
|
||||
EXPECT_EQ(Disassemble(m), R"(%fn0 = block
|
||||
%1(ref<private, u32, read_write>) = var private read_write
|
||||
store %1(ref<private, u32, read_write>), 2u
|
||||
ret
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(IR_BuilderImplTest, Emit_Var_NoInit) {
|
||||
auto* a = Var("a", ty.u32(), builtin::AddressSpace::kFunction);
|
||||
WrapInFunction(a);
|
||||
|
||||
auto r = Build();
|
||||
ASSERT_TRUE(r) << Error();
|
||||
auto m = r.Move();
|
||||
|
||||
EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
|
||||
%fn1 = block
|
||||
%1(ref<function, u32, read_write>) = var function read_write
|
||||
ret
|
||||
func_end
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(IR_BuilderImplTest, Emit_Var_Init) {
|
||||
auto* expr = Expr(2_u);
|
||||
auto* a = Var("a", ty.u32(), builtin::AddressSpace::kFunction, expr);
|
||||
WrapInFunction(a);
|
||||
|
||||
auto r = Build();
|
||||
ASSERT_TRUE(r) << Error();
|
||||
auto m = r.Move();
|
||||
|
||||
EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
|
||||
%fn1 = block
|
||||
%1(ref<function, u32, read_write>) = var function read_write
|
||||
store %1(ref<function, u32, read_write>), 2u
|
||||
ret
|
||||
func_end
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Add) {
|
||||
Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
|
||||
auto* expr = Add(Call("my_func"), 4_u);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "src/tint/ir/switch.h"
|
||||
#include "src/tint/ir/terminator.h"
|
||||
#include "src/tint/switch.h"
|
||||
#include "src/tint/utils/scoped_assignment.h"
|
||||
|
||||
namespace tint::ir {
|
||||
namespace {
|
||||
|
@ -89,6 +90,8 @@ void Disassembler::Walk(const FlowNode* node) {
|
|||
tint::Switch(
|
||||
node,
|
||||
[&](const ir::Function* f) {
|
||||
TINT_SCOPED_ASSIGNMENT(in_function_, true);
|
||||
|
||||
Indent() << "%fn" << GetIdForNode(f) << " = func " << f->name.Name() << std::endl;
|
||||
|
||||
{
|
||||
|
@ -241,11 +244,19 @@ void Disassembler::Walk(const FlowNode* node) {
|
|||
Walk(l->merge.target);
|
||||
}
|
||||
},
|
||||
[&](const ir::Terminator*) { Indent() << "func_end" << std::endl
|
||||
<< std::endl; });
|
||||
[&](const ir::Terminator*) {
|
||||
if (in_function_) {
|
||||
Indent() << "func_end" << std::endl;
|
||||
}
|
||||
out_ << std::endl;
|
||||
});
|
||||
}
|
||||
|
||||
std::string Disassembler::Disassemble() {
|
||||
if (mod_.root_block) {
|
||||
Walk(mod_.root_block);
|
||||
}
|
||||
|
||||
for (const auto* func : mod_.functions) {
|
||||
Walk(func);
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ class Disassembler {
|
|||
std::unordered_map<const FlowNode*, size_t> flow_node_to_id_;
|
||||
size_t next_node_id_ = 0;
|
||||
uint32_t indent_size_ = 0;
|
||||
bool in_function_ = false;
|
||||
};
|
||||
|
||||
} // namespace tint::ir
|
||||
|
|
|
@ -65,6 +65,9 @@ class Module {
|
|||
/// List of indexes into the functions list for the entry points
|
||||
utils::Vector<Function*, 8> entry_points;
|
||||
|
||||
/// The block containing module level declarations, if any exist.
|
||||
Block* root_block = nullptr;
|
||||
|
||||
/// The type manager for the module
|
||||
type::Manager types;
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// 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/var.h"
|
||||
#include "src/tint/debug.h"
|
||||
|
||||
TINT_INSTANTIATE_TYPEINFO(tint::ir::Var);
|
||||
|
||||
namespace tint::ir {
|
||||
|
||||
Var::Var(uint32_t id,
|
||||
const type::Type* ty,
|
||||
builtin::AddressSpace address_space,
|
||||
builtin::Access access)
|
||||
: Base(id, ty), address_space_(address_space), access_(access) {}
|
||||
|
||||
Var::~Var() = default;
|
||||
|
||||
utils::StringStream& Var::ToInstruction(utils::StringStream& out) const {
|
||||
ToValue(out) << " = var " << address_space_ << " " << access_;
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace tint::ir
|
|
@ -0,0 +1,63 @@
|
|||
// 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.
|
||||
|
||||
#ifndef SRC_TINT_IR_VAR_H_
|
||||
#define SRC_TINT_IR_VAR_H_
|
||||
|
||||
#include "src/tint/builtin/access.h"
|
||||
#include "src/tint/builtin/address_space.h"
|
||||
#include "src/tint/ir/instruction.h"
|
||||
#include "src/tint/utils/castable.h"
|
||||
#include "src/tint/utils/string_stream.h"
|
||||
|
||||
namespace tint::ir {
|
||||
|
||||
/// An instruction in the IR.
|
||||
class Var : public utils::Castable<Var, Instruction> {
|
||||
public:
|
||||
/// Constructor
|
||||
/// @param id the instruction id
|
||||
/// @param type the type
|
||||
/// @param address_space the address space of the var
|
||||
/// @param access the access mode of the var
|
||||
Var(uint32_t id,
|
||||
const type::Type* type,
|
||||
builtin::AddressSpace address_space,
|
||||
builtin::Access access);
|
||||
Var(const Var& inst) = delete;
|
||||
Var(Var&& inst) = delete;
|
||||
~Var() override;
|
||||
|
||||
Var& operator=(const Var& inst) = delete;
|
||||
Var& operator=(Var&& inst) = delete;
|
||||
|
||||
/// @returns the address space
|
||||
builtin::AddressSpace AddressSpace() const { return address_space_; }
|
||||
|
||||
/// @returns the access mode
|
||||
builtin::Access Access() const { return access_; }
|
||||
|
||||
/// Write the instruction to the given stream
|
||||
/// @param out the stream to write to
|
||||
/// @returns the stream
|
||||
utils::StringStream& ToInstruction(utils::StringStream& out) const override;
|
||||
|
||||
private:
|
||||
builtin::AddressSpace address_space_;
|
||||
builtin::Access access_;
|
||||
};
|
||||
|
||||
} // namespace tint::ir
|
||||
|
||||
#endif // SRC_TINT_IR_VAR_H_
|
Loading…
Reference in New Issue