[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>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 56c2031..c6affb1 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1173,6 +1173,8 @@
"ir/user_call.h",
"ir/value.cc",
"ir/value.h",
+ "ir/var.cc",
+ "ir/var.h",
]
deps = [
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 2bd0cee..2e1ded8 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -750,6 +750,8 @@
ir/user_call.h
ir/value.cc
ir/value.h
+ ir/var.cc
+ ir/var.h
)
endif()
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 7c020df..2c97737 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -24,6 +24,17 @@
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 @@
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
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index 9c847c6..d509c06 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -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 @@
/// @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;
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 90f7fb3..19caf6f 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -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 @@
[&](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 @@
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 @@
}
// 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 @@
}
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 "
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 3cd3c60..1e6a26d 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -1558,6 +1558,72 @@
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);
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 53bf722..2b8de0b 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -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 @@
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 @@
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);
}
diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h
index eee6b76..04b3997 100644
--- a/src/tint/ir/disassembler.h
+++ b/src/tint/ir/disassembler.h
@@ -56,6 +56,7 @@
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
diff --git a/src/tint/ir/module.h b/src/tint/ir/module.h
index a7172ea..ebf9209 100644
--- a/src/tint/ir/module.h
+++ b/src/tint/ir/module.h
@@ -65,6 +65,9 @@
/// 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;
diff --git a/src/tint/ir/var.cc b/src/tint/ir/var.cc
new file mode 100644
index 0000000..740a35e
--- /dev/null
+++ b/src/tint/ir/var.cc
@@ -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
diff --git a/src/tint/ir/var.h b/src/tint/ir/var.h
new file mode 100644
index 0000000..456e7d3
--- /dev/null
+++ b/src/tint/ir/var.h
@@ -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_