[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