[ir] Add assignment statements
This CL adds Assignment and CompoundAssignment statements to the IR
builder.
Bug: tint:1718
Change-Id: I3037da0115c7f4fe68941565b7e48866d421bbbf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/129201
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/CMakeLists.txt b/src/tint/CMakeLists.txt
index 4c4e965..f715428 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -736,6 +736,8 @@
ir/module.h
ir/runtime.cc
ir/runtime.h
+ ir/store.cc
+ ir/store.h
ir/switch.cc
ir/switch.h
ir/terminator.cc
@@ -1419,6 +1421,7 @@
ir/constant_test.cc
ir/discard_test.cc
ir/runtime_test.cc
+ ir/store_test.cc
ir/test_helper.h
ir/unary_test.cc
)
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index aa29526..13ef345 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -227,4 +227,8 @@
return ir.instructions.Create<ir::Builtin>(Runtime(type), func, args);
}
+ir::Store* Builder::Store(Value* to, Value* from) {
+ return ir.instructions.Create<ir::Store>(to, from);
+}
+
} // namespace tint::ir
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index 4a87e08..3fc78d2 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -30,6 +30,7 @@
#include "src/tint/ir/loop.h"
#include "src/tint/ir/module.h"
#include "src/tint/ir/runtime.h"
+#include "src/tint/ir/store.h"
#include "src/tint/ir/switch.h"
#include "src/tint/ir/terminator.h"
#include "src/tint/ir/unary.h"
@@ -359,6 +360,12 @@
builtin::Function func,
utils::VectorRef<Value*> args);
+ /// Creates an store instruction
+ /// @param to the expression being stored too
+ /// @param from the expression being stored
+ /// @returns the instruction
+ ir::Store* Store(Value* to, Value* from);
+
/// @returns a unique runtime id
Runtime::Id AllocateRuntimeId();
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 787fa1e..1f0f79a 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -17,6 +17,7 @@
#include <iostream>
#include "src/tint/ast/alias.h"
+#include "src/tint/ast/assignment_statement.h"
#include "src/tint/ast/binary_expression.h"
#include "src/tint/ast/bitcast_expression.h"
#include "src/tint/ast/block_statement.h"
@@ -25,6 +26,7 @@
#include "src/tint/ast/break_statement.h"
#include "src/tint/ast/call_expression.h"
#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/compound_assignment_statement.h"
#include "src/tint/ast/const.h"
#include "src/tint/ast/const_assert.h"
#include "src/tint/ast/continue_statement.h"
@@ -54,8 +56,10 @@
#include "src/tint/ir/if.h"
#include "src/tint/ir/loop.h"
#include "src/tint/ir/module.h"
+#include "src/tint/ir/store.h"
#include "src/tint/ir/switch.h"
#include "src/tint/ir/terminator.h"
+#include "src/tint/ir/value.h"
#include "src/tint/program.h"
#include "src/tint/sem/builtin.h"
#include "src/tint/sem/call.h"
@@ -236,17 +240,13 @@
void BuilderImpl::EmitStatement(const ast::Statement* stmt) {
tint::Switch(
- stmt,
- // [&](const ast::AssignmentStatement* a) {
- // TODO(dsinclair): Implement
- // },
+ stmt, //
+ [&](const ast::AssignmentStatement* a) { EmitAssignment(a); },
[&](const ast::BlockStatement* b) { EmitBlock(b); },
[&](const ast::BreakStatement* b) { EmitBreak(b); },
[&](const ast::BreakIfStatement* b) { EmitBreakIf(b); },
[&](const ast::CallStatement* c) { EmitCall(c); },
- // [&](const ast::CompoundAssignmentStatement* c) {
- // TODO(dsinclair): Implement
- // },
+ [&](const ast::CompoundAssignmentStatement* c) { EmitCompoundAssignment(c); },
[&](const ast::ContinueStatement* c) { EmitContinue(c); },
[&](const ast::DiscardStatement* d) { EmitDiscard(d); },
[&](const ast::IfStatement* i) { EmitIf(i); },
@@ -265,6 +265,98 @@
});
}
+void BuilderImpl::EmitAssignment(const ast::AssignmentStatement* stmt) {
+ auto lhs = EmitExpression(stmt->lhs);
+ if (!lhs) {
+ return;
+ }
+
+ auto rhs = EmitExpression(stmt->rhs);
+ if (!rhs) {
+ return;
+ }
+ auto store = builder.Store(lhs.Get(), rhs.Get());
+ current_flow_block->instructions.Push(store);
+}
+
+void BuilderImpl::EmitCompoundAssignment(const ast::CompoundAssignmentStatement* stmt) {
+ auto lhs = EmitExpression(stmt->lhs);
+ if (!lhs) {
+ return;
+ }
+
+ auto rhs = EmitExpression(stmt->rhs);
+ if (!rhs) {
+ return;
+ }
+
+ auto* ty = lhs.Get()->Type();
+ Binary* instr = nullptr;
+ switch (stmt->op) {
+ case ast::BinaryOp::kAnd:
+ instr = builder.And(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kOr:
+ instr = builder.Or(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kXor:
+ instr = builder.Xor(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kLogicalAnd:
+ instr = builder.LogicalAnd(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kLogicalOr:
+ instr = builder.LogicalOr(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kEqual:
+ instr = builder.Equal(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kNotEqual:
+ instr = builder.NotEqual(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kLessThan:
+ instr = builder.LessThan(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kGreaterThan:
+ instr = builder.GreaterThan(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kLessThanEqual:
+ instr = builder.LessThanEqual(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kGreaterThanEqual:
+ instr = builder.GreaterThanEqual(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kShiftLeft:
+ instr = builder.ShiftLeft(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kShiftRight:
+ instr = builder.ShiftRight(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kAdd:
+ instr = builder.Add(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kSubtract:
+ instr = builder.Subtract(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kMultiply:
+ instr = builder.Multiply(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kDivide:
+ instr = builder.Divide(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kModulo:
+ instr = builder.Modulo(ty, lhs.Get(), rhs.Get());
+ break;
+ case ast::BinaryOp::kNone:
+ TINT_ICE(IR, diagnostics_) << "missing binary operand type";
+ return;
+ }
+ current_flow_block->instructions.Push(instr);
+
+ auto store = builder.Store(lhs.Get(), instr->Result());
+ current_flow_block->instructions.Push(store);
+}
+
void BuilderImpl::EmitBlock(const ast::BlockStatement* block) {
// 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
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 73f2aee..7b18492 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -34,6 +34,7 @@
} // namespace tint
namespace tint::ast {
class Attribute;
+class AssignmentStatement;
class BinaryExpression;
class BitcastExpression;
class BlockStatement;
@@ -41,6 +42,7 @@
class BreakStatement;
class CallExpression;
class CallStatement;
+class CompoundAssignmentStatement;
class ContinueStatement;
class DiscardStatement;
class Expression;
@@ -142,6 +144,14 @@
/// @param stmt the break-if statement
void EmitBreakIf(const ast::BreakIfStatement* stmt);
+ /// Emits an assignment statement
+ /// @param stmt the statement
+ void EmitAssignment(const ast::AssignmentStatement* stmt);
+
+ /// Emits a compound assignment statement
+ /// @param stmt the statement
+ void EmitCompoundAssignment(const ast::CompoundAssignmentStatement* stmt);
+
/// Emits an expression
/// @param expr the expression to emit
/// @returns true if successful, false otherwise
diff --git a/src/tint/ir/store.cc b/src/tint/ir/store.cc
new file mode 100644
index 0000000..32162c3
--- /dev/null
+++ b/src/tint/ir/store.cc
@@ -0,0 +1,36 @@
+// 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/store.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Store);
+
+namespace tint::ir {
+
+Store::Store(Value* to, Value* from) : Base(to), from_(from) {
+ TINT_ASSERT(IR, from_);
+ from_->AddUsage(this);
+}
+
+Store::~Store() = default;
+
+utils::StringStream& Store::ToString(utils::StringStream& out) const {
+ Result()->ToString(out);
+ out << " = ";
+ from_->ToString(out);
+ return out;
+}
+
+} // namespace tint::ir
diff --git a/src/tint/ir/store.h b/src/tint/ir/store.h
new file mode 100644
index 0000000..57544fc
--- /dev/null
+++ b/src/tint/ir/store.h
@@ -0,0 +1,52 @@
+// 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_STORE_H_
+#define SRC_TINT_IR_STORE_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 Store : public utils::Castable<Store, Instruction> {
+ public:
+ /// Constructor
+ /// @param to the value to store too
+ /// @param from the value being stored from
+ Store(Value* to, Value* from);
+ Store(const Store& instr) = delete;
+ Store(Store&& instr) = delete;
+ ~Store() override;
+
+ Store& operator=(const Store& instr) = delete;
+ Store& operator=(Store&& instr) = delete;
+
+ /// @returns the value being stored
+ const Value* from() const { return from_; }
+
+ /// Write the instruction to the given stream
+ /// @param out the stream to write to
+ /// @returns the stream
+ utils::StringStream& ToString(utils::StringStream& out) const override;
+
+ private:
+ Value* from_ = nullptr;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_STORE_H_
diff --git a/src/tint/ir/store_test.cc b/src/tint/ir/store_test.cc
new file mode 100644
index 0000000..e0632dd
--- /dev/null
+++ b/src/tint/ir/store_test.cc
@@ -0,0 +1,65 @@
+// 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/instruction.h"
+#include "src/tint/ir/test_helper.h"
+#include "src/tint/utils/string_stream.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes; // NOLINT
+
+using IR_InstructionTest = TestHelper;
+
+TEST_F(IR_InstructionTest, CreateStore) {
+ auto& b = CreateEmptyBuilder();
+
+ b.builder.next_runtime_id = Runtime::Id(42);
+
+ auto* rt = b.builder.Runtime(b.builder.ir.types.Get<type::I32>());
+ const auto* instr = b.builder.Store(rt, b.builder.Constant(4_i));
+
+ ASSERT_TRUE(instr->Result()->Is<Runtime>());
+ ASSERT_NE(instr->Result()->Type(), nullptr);
+ EXPECT_EQ(Runtime::Id(42), instr->Result()->As<Runtime>()->AsId());
+
+ ASSERT_TRUE(instr->from()->Is<Constant>());
+ auto lhs = instr->from()->As<Constant>()->value;
+ ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+ EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
+
+ utils::StringStream str;
+ instr->ToString(str);
+ EXPECT_EQ(str.str(), "%42 (i32) = 4");
+}
+
+TEST_F(IR_InstructionTest, Store_Usage) {
+ auto& b = CreateEmptyBuilder();
+
+ b.builder.next_runtime_id = Runtime::Id(42);
+ auto* rt = b.builder.Runtime(b.builder.ir.types.Get<type::I32>());
+ const auto* instr = b.builder.Store(rt, b.builder.Constant(4_i));
+
+ ASSERT_NE(instr->Result(), nullptr);
+ ASSERT_EQ(instr->Result()->Usage().Length(), 1u);
+ EXPECT_EQ(instr->Result()->Usage()[0], instr);
+
+ ASSERT_NE(instr->from(), nullptr);
+ ASSERT_EQ(instr->from()->Usage().Length(), 1u);
+ EXPECT_EQ(instr->from()->Usage()[0], instr);
+}
+
+} // namespace
+} // namespace tint::ir