diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn index c6affb1769..a805fe12a6 100644 --- a/src/tint/BUILD.gn +++ b/src/tint/BUILD.gn @@ -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", ] diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc index cce89a481e..ff1981437b 100644 --- a/src/tint/ir/builder_impl.cc +++ b/src/tint/ir/builder_impl.cc @@ -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(); - 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(); - 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(); 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(); - 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(), 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(); } - 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(); @@ -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 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{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, diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h index 58d4844d53..fd80a3e34d 100644 --- a/src/tint/ir/builder_impl.h +++ b/src/tint/ir/builder_impl.h @@ -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 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 ast_to_flow_; - - constant::CloneContext clone_ctx_; }; } // namespace tint::ir diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc index 4df35bfd19..f9de91e015 100644 --- a/src/tint/ir/builder_impl_test.cc +++ b/src/tint/ir/builder_impl_test.cc @@ -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()); - EXPECT_EQ(d.AsString(), R"(%1(vec3) = construct + EXPECT_EQ(Disassemble(m), R"(%fn0 = block +%1(ref, read_write>) = var private read_write +store %1(ref, read_write>), vec3 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()); - EXPECT_EQ(d.AsString(), R"(%2(vec3) = construct 2.0f, 3.0f, %1(void) + EXPECT_EQ(Disassemble(m), R"(%fn0 = block +%1(ref) = var private read_write +store %1(ref), 1.0f +ret + +%fn1 = func test_function + %fn2 = block + %2(vec3) = construct 2.0f, 3.0f, %1(ref) + 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()); - EXPECT_EQ(d.AsString(), R"(%2(f32) = convert i32, %1(void) + EXPECT_EQ(Disassemble(m), R"(%fn0 = block +%1(ref) = var private read_write +store %1(ref), 1i +ret + +%fn1 = func test_function + %fn2 = block + %2(f32) = convert i32, %1(ref) + 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) = var private read_write +store %1(ref), 1.0f +ret + +%fn1 = func test_function + %fn2 = block + %2(f32) = asin %1(ref) + ret +func_end - Disassembler d(b.builder.ir); - d.EmitBlockInstructions(b.current_flow_block->As()); - EXPECT_EQ(d.AsString(), R"(%2(f32) = asin %1(void) )"); }