[ir] Handle IdentifierExpression

This Cl adds a scope stack into the IR builder and uses it to replace
IdentifierExpressions with the relevant IDs. If the IdentifierExpression
was const-eval'd then it will be replaced by the constant value.

Bug: tint:1919
Change-Id: I54e38d56bd24e2ced1818c509115dd5a5149cb40
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/130900
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
dan sinclair 2023-05-03 19:20:48 +00:00 committed by Dawn LUCI CQ
parent 94bc4cf046
commit 4cadbc4daf
4 changed files with 86 additions and 57 deletions

View File

@ -1118,6 +1118,7 @@ libtint_source_set("libtint_ir_builder_src") {
":libtint_ir_src",
":libtint_program_src",
":libtint_sem_src",
":libtint_symbols_src",
":libtint_type_src",
":libtint_utils_src",
]

View File

@ -74,6 +74,7 @@
#include "src/tint/sem/variable.h"
#include "src/tint/switch.h"
#include "src/tint/type/void.h"
#include "src/tint/utils/defer.h"
#include "src/tint/utils/scoped_assignment.h"
namespace tint::ir {
@ -217,7 +218,7 @@ void BuilderImpl::EmitFunction(const ast::Function* ast_func) {
FlowStackScope scope(this, ir_func);
current_flow_block = ir_func->start_target;
EmitStatements(ast_func->body->statements);
EmitBlock(ast_func->body);
// TODO(dsinclair): Store return type and attributes
// TODO(dsinclair): Store parameters
@ -363,6 +364,9 @@ void BuilderImpl::EmitCompoundAssignment(const ast::CompoundAssignmentStatement*
}
void BuilderImpl::EmitBlock(const ast::BlockStatement* block) {
scopes_.Push();
TINT_DEFER(scopes_.Pop());
// Note, this doesn't need to emit a Block as the current block flow node should be
// sufficient as the blocks all get flattened. Each flow control node will inject the basic
// blocks it requires.
@ -387,7 +391,7 @@ void BuilderImpl::EmitIf(const ast::IfStatement* stmt) {
FlowStackScope scope(this, if_node);
current_flow_block = if_node->true_.target->As<Block>();
EmitStatement(stmt->body);
EmitBlock(stmt->body);
// If the true branch did not execute control flow, then go to the merge target
BranchToIfNeeded(if_node->merge.target);
@ -421,14 +425,14 @@ void BuilderImpl::EmitLoop(const ast::LoopStatement* stmt) {
FlowStackScope scope(this, loop_node);
current_flow_block = loop_node->start.target->As<Block>();
EmitStatement(stmt->body);
EmitBlock(stmt->body);
// The current block didn't `break`, `return` or `continue`, go to the continuing block.
BranchToIfNeeded(loop_node->continuing.target);
current_flow_block = loop_node->continuing.target->As<Block>();
if (stmt->continuing) {
EmitStatement(stmt->continuing);
EmitBlock(stmt->continuing);
}
// Branch back to the start node if the continue target didn't branch out already
@ -477,7 +481,7 @@ void BuilderImpl::EmitWhile(const ast::WhileStatement* stmt) {
BranchTo(if_node);
current_flow_block = if_node->merge.target->As<Block>();
EmitStatement(stmt->body);
EmitBlock(stmt->body);
BranchToIfNeeded(loop_node->continuing.target);
}
@ -492,6 +496,10 @@ void BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
builder.Branch(loop_node->continuing.target->As<Block>(), loop_node->start.target,
utils::Empty);
// Make sure the initializer ends up in a contained scope
scopes_.Push();
TINT_DEFER(scopes_.Pop());
if (stmt->initializer) {
// Emit the for initializer before branching to the loop
EmitStatement(stmt->initializer);
@ -527,7 +535,7 @@ void BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
current_flow_block = if_node->merge.target->As<Block>();
}
EmitStatement(stmt->body);
EmitBlock(stmt->body);
BranchToIfNeeded(loop_node->continuing.target);
if (stmt->continuing) {
@ -535,6 +543,7 @@ void BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
EmitStatement(stmt->continuing);
}
}
// The while loop always has a path to the merge target as the break statement comes before
// anything inside the loop.
current_flow_block = loop_node->merge.target->As<Block>();
@ -569,7 +578,8 @@ void BuilderImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
}
current_flow_block = builder.CreateCase(switch_node, selectors);
EmitStatement(c->Body()->Declaration());
EmitBlock(c->Body()->Declaration());
BranchToIfNeeded(switch_node->merge.target);
}
}
@ -677,9 +687,10 @@ utils::Result<Value*> BuilderImpl::EmitExpression(const ast::Expression* expr) {
[&](const ast::BinaryExpression* b) { return EmitBinary(b); },
[&](const ast::BitcastExpression* b) { return EmitBitcast(b); },
[&](const ast::CallExpression* c) { return EmitCall(c); },
// [&](const ast::IdentifierExpression* i) {
// TODO(dsinclair): Implement
// },
[&](const ast::IdentifierExpression* i) {
auto* v = scopes_.Get(i->identifier->symbol);
return utils::Result<Value*>{v};
},
[&](const ast::LiteralExpression* l) { return EmitLiteral(l); },
// [&](const ast::MemberAccessorExpression* m) {
// TODO(dsinclair): Implement
@ -714,7 +725,8 @@ void BuilderImpl::EmitVariable(const ast::Variable* var) {
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
// Store the declaration so we can get the instruction to store too
scopes_.Set(v->name->symbol, val);
},
[&](const ast::Let* l) {
// A `let` doesn't exist as a standalone item in the IR, it's just the result of the
@ -723,7 +735,9 @@ void BuilderImpl::EmitVariable(const ast::Variable* var) {
if (!init) {
return;
}
// TODO(dsinclair): Store the mapping from the let name to the `init` value
// Store the results of the initialization
scopes_.Set(l->name->symbol, init.Get());
},
[&](const ast::Override*) {
add_error(var->source,

View File

@ -26,6 +26,7 @@
#include "src/tint/ir/flow_node.h"
#include "src/tint/ir/module.h"
#include "src/tint/ir/value.h"
#include "src/tint/scope_stack.h"
#include "src/tint/utils/result.h"
// Forward Declarations
@ -232,19 +233,17 @@ class BuilderImpl {
void add_error(const Source& s, const std::string& err);
const Program* program_ = nullptr;
Symbol CloneSymbol(Symbol sym) const;
diag::List diagnostics_;
const Program* program_ = nullptr;
Function* current_function_ = nullptr;
ScopeStack<Symbol, Value*> scopes_;
constant::CloneContext clone_ctx_;
diag::List diagnostics_;
/// Map from ast nodes to flow nodes, used to retrieve the flow node for a given AST node.
/// Used for testing purposes.
std::unordered_map<const ast::Node*, const FlowNode*> ast_to_flow_;
constant::CloneContext clone_ctx_;
};
} // namespace tint::ir

View File

@ -2105,58 +2105,68 @@ TEST_F(IR_BuilderImplTest, EmitStatement_UserFunction) {
)");
}
// TODO(dsinclair): This needs assignment in order to output correctly. The empty constructor ends
// up materializing, so there is no expression to emit until there is a usage. When assigment is
// implemented this can be enabled (and the output updated).
TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_ConstructEmpty) {
TEST_F(IR_BuilderImplTest, EmitExpression_ConstructEmpty) {
auto* expr = vec3(ty.f32());
GlobalVar("i", builtin::AddressSpace::kPrivate, expr);
auto& b = CreateBuilder();
InjectFlowBlock();
auto r = b.EmitExpression(expr);
ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
auto r = Build();
ASSERT_TRUE(r) << Error();
auto m = r.Move();
ASSERT_TRUE(r);
Disassembler d(b.builder.ir);
d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
EXPECT_EQ(d.AsString(), R"(%1(vec3<f32>) = construct
EXPECT_EQ(Disassemble(m), R"(%fn0 = block
%1(ref<private, vec3<f32>, read_write>) = var private read_write
store %1(ref<private, vec3<f32>, read_write>), vec3<f32> 0.0f
ret
)");
}
// Requires identifier expressions
TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_Construct) {
TEST_F(IR_BuilderImplTest, EmitExpression_Construct) {
auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_f));
auto* expr = vec3(ty.f32(), 2_f, 3_f, i);
WrapInFunction(expr);
auto& b = CreateBuilder();
InjectFlowBlock();
auto r = b.EmitExpression(expr);
ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
auto r = Build();
ASSERT_TRUE(r) << Error();
auto m = r.Move();
ASSERT_TRUE(r);
Disassembler d(b.builder.ir);
d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
EXPECT_EQ(d.AsString(), R"(%2(vec3<f32>) = construct 2.0f, 3.0f, %1(void)
EXPECT_EQ(Disassemble(m), R"(%fn0 = block
%1(ref<private, f32, read_write>) = var private read_write
store %1(ref<private, f32, read_write>), 1.0f
ret
%fn1 = func test_function
%fn2 = block
%2(vec3<f32>) = construct 2.0f, 3.0f, %1(ref<private, f32, read_write>)
ret
func_end
)");
}
// Requires identifier expressions
TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_Convert) {
TEST_F(IR_BuilderImplTest, EmitExpression_Convert) {
auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_i));
auto* expr = Call(ty.f32(), i);
WrapInFunction(expr);
auto& b = CreateBuilder();
InjectFlowBlock();
auto r = b.EmitExpression(expr);
ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
auto r = Build();
ASSERT_TRUE(r) << Error();
auto m = r.Move();
ASSERT_TRUE(r);
Disassembler d(b.builder.ir);
d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
EXPECT_EQ(d.AsString(), R"(%2(f32) = convert i32, %1(void)
EXPECT_EQ(Disassemble(m), R"(%fn0 = block
%1(ref<private, i32, read_write>) = var private read_write
store %1(ref<private, i32, read_write>), 1i
ret
%fn1 = func test_function
%fn2 = block
%2(f32) = convert i32, %1(ref<private, i32, read_write>)
ret
func_end
)");
}
@ -2177,21 +2187,26 @@ func_end
)");
}
// Requires identifier expressions
TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_Builtin) {
TEST_F(IR_BuilderImplTest, EmitExpression_Builtin) {
auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_f));
auto* expr = Call("asin", i);
WrapInFunction(expr);
auto& b = CreateBuilder();
InjectFlowBlock();
auto r = b.EmitExpression(expr);
ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
ASSERT_TRUE(r);
auto r = Build();
ASSERT_TRUE(r) << Error();
auto m = r.Move();
EXPECT_EQ(Disassemble(m), R"(%fn0 = block
%1(ref<private, f32, read_write>) = var private read_write
store %1(ref<private, f32, read_write>), 1.0f
ret
%fn1 = func test_function
%fn2 = block
%2(f32) = asin %1(ref<private, f32, read_write>)
ret
func_end
Disassembler d(b.builder.ir);
d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
EXPECT_EQ(d.AsString(), R"(%2(f32) = asin %1(void)
)");
}