[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:
dan sinclair 2023-05-02 15:47:29 +00:00 committed by Dawn LUCI CQ
parent ae7df9e8c6
commit c970e806db
11 changed files with 259 additions and 17 deletions

View File

@ -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 = [

View File

@ -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()

View File

@ -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

View File

@ -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;

View File

@ -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 "

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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;

35
src/tint/ir/var.cc Normal file
View File

@ -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

63
src/tint/ir/var.h Normal file
View File

@ -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_