[ir] Add Unary expressions

This CL adds support for UnaryOpExpressions and converts them into Unary
instructions in the IR.

Bug: tint:1718

Change-Id: I736e29cec5e722b7c7f1b0f4f22ce55a3d3e4109
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/129221
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 0e43265..aa29526 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -173,6 +173,30 @@
     return CreateBinary(Binary::Kind::kModulo, type, lhs, rhs);
 }
 
+Unary* Builder::CreateUnary(Unary::Kind kind, const type::Type* type, Value* val) {
+    return ir.instructions.Create<ir::Unary>(kind, Runtime(type), val);
+}
+
+Unary* Builder::AddressOf(const type::Type* type, Value* val) {
+    return CreateUnary(Unary::Kind::kAddressOf, type, val);
+}
+
+Unary* Builder::Complement(const type::Type* type, Value* val) {
+    return CreateUnary(Unary::Kind::kComplement, type, val);
+}
+
+Unary* Builder::Indirection(const type::Type* type, Value* val) {
+    return CreateUnary(Unary::Kind::kIndirection, type, val);
+}
+
+Unary* Builder::Negation(const type::Type* type, Value* val) {
+    return CreateUnary(Unary::Kind::kNegation, type, val);
+}
+
+Unary* Builder::Not(const type::Type* type, Value* val) {
+    return CreateUnary(Unary::Kind::kNot, type, val);
+}
+
 ir::Bitcast* Builder::Bitcast(const type::Type* type, Value* val) {
     return ir.instructions.Create<ir::Bitcast>(Runtime(type), val);
 }
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index f53e4c8..4a87e08 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -32,6 +32,7 @@
 #include "src/tint/ir/runtime.h"
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/terminator.h"
+#include "src/tint/ir/unary.h"
 #include "src/tint/ir/user_call.h"
 #include "src/tint/ir/value.h"
 #include "src/tint/type/bool.h"
@@ -280,6 +281,43 @@
     /// @returns the operation
     Binary* Modulo(const type::Type* type, Value* lhs, Value* rhs);
 
+    /// Creates an op for `kind val`
+    /// @param kind the kind of operation
+    /// @param type the result type of the binary expression
+    /// @param val the value of the operation
+    /// @returns the operation
+    Unary* CreateUnary(Unary::Kind kind, const type::Type* type, Value* val);
+
+    /// Creates an AddressOf operation
+    /// @param type the result type of the expression
+    /// @param val the value
+    /// @returns the operation
+    Unary* AddressOf(const type::Type* type, Value* val);
+
+    /// Creates a Complement operation
+    /// @param type the result type of the expression
+    /// @param val the value
+    /// @returns the operation
+    Unary* Complement(const type::Type* type, Value* val);
+
+    /// Creates an Indirection operation
+    /// @param type the result type of the expression
+    /// @param val the value
+    /// @returns the operation
+    Unary* Indirection(const type::Type* type, Value* val);
+
+    /// Creates a Negation operation
+    /// @param type the result type of the expression
+    /// @param val the value
+    /// @returns the operation
+    Unary* Negation(const type::Type* type, Value* val);
+
+    /// Creates a Not operation
+    /// @param type the result type of the expression
+    /// @param val the value
+    /// @returns the operation
+    Unary* Not(const type::Type* type, Value* val);
+
     /// Creates a bitcast instruction
     /// @param type the result type of the bitcast
     /// @param val the value being bitcast
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 07226ce..957875a 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -46,6 +46,7 @@
 #include "src/tint/ast/struct_member_size_attribute.h"
 #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/variable_decl_statement.h"
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ir/function.h"
@@ -579,9 +580,7 @@
         // [&](const ast::PhonyExpression*) {
         // TODO(dsinclair): Implement. The call may have side effects so has to be made.
         // },
-        // [&](const ast::UnaryOpExpression* u) {
-        // TODO(dsinclair): Implement
-        // },
+        [&](const ast::UnaryOpExpression* u) { return EmitUnary(u); },
         [&](Default) {
             add_error(expr->source,
                       "unknown expression type: " + std::string(expr->TypeInfo().name));
@@ -611,6 +610,38 @@
         });
 }
 
+utils::Result<Value*> BuilderImpl::EmitUnary(const ast::UnaryOpExpression* expr) {
+    auto val = EmitExpression(expr->expr);
+    if (!val) {
+        return utils::Failure;
+    }
+
+    auto* sem = program_->Sem().Get(expr);
+    auto* ty = sem->Type()->Clone(clone_ctx_.type_ctx);
+
+    Unary* instr = nullptr;
+    switch (expr->op) {
+        case ast::UnaryOp::kAddressOf:
+            instr = builder.AddressOf(ty, val.Get());
+            break;
+        case ast::UnaryOp::kComplement:
+            instr = builder.Complement(ty, val.Get());
+            break;
+        case ast::UnaryOp::kIndirection:
+            instr = builder.Indirection(ty, val.Get());
+            break;
+        case ast::UnaryOp::kNegation:
+            instr = builder.Negation(ty, val.Get());
+            break;
+        case ast::UnaryOp::kNot:
+            instr = builder.Not(ty, val.Get());
+            break;
+    }
+
+    current_flow_block->instructions.Push(instr);
+    return instr->Result();
+}
+
 utils::Result<Value*> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
     auto lhs = EmitExpression(expr->lhs);
     if (!lhs) {
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 3a1dbbd..73f2aee 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -53,6 +53,7 @@
 class ReturnStatement;
 class Statement;
 class SwitchStatement;
+class UnaryOpExpression;
 class WhileStatement;
 class Variable;
 }  // namespace tint::ast
@@ -150,6 +151,11 @@
     /// @param var the variable to emit
     void EmitVariable(const ast::Variable* var);
 
+    /// Emits a Unary expression
+    /// @param expr the unary expression
+    /// @returns the value storing the result if successful, utils::Failure otherwise
+    utils::Result<Value*> EmitUnary(const ast::UnaryOpExpression* expr);
+
     /// Emits a binary expression
     /// @param expr the binary expression
     /// @returns the value storing the result if successful, utils::Failure otherwise
diff --git a/src/tint/ir/unary.cc b/src/tint/ir/unary.cc
new file mode 100644
index 0000000..532efcc
--- /dev/null
+++ b/src/tint/ir/unary.cc
@@ -0,0 +1,53 @@
+// 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/unary.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Unary);
+
+namespace tint::ir {
+
+Unary::Unary(Kind kind, Value* result, Value* val) : Base(result), kind_(kind), val_(val) {
+    TINT_ASSERT(IR, val_);
+    val_->AddUsage(this);
+}
+
+Unary::~Unary() = default;
+
+utils::StringStream& Unary::ToString(utils::StringStream& out) const {
+    Result()->ToString(out) << " = ";
+    switch (GetKind()) {
+        case Unary::Kind::kAddressOf:
+            out << "&";
+            break;
+        case Unary::Kind::kComplement:
+            out << "~";
+            break;
+        case Unary::Kind::kIndirection:
+            out << "*";
+            break;
+        case Unary::Kind::kNegation:
+            out << "-";
+            break;
+        case Unary::Kind::kNot:
+            out << "!";
+            break;
+    }
+    val_->ToString(out);
+
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/unary.h b/src/tint/ir/unary.h
new file mode 100644
index 0000000..0337b4f
--- /dev/null
+++ b/src/tint/ir/unary.h
@@ -0,0 +1,66 @@
+// 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_UNARY_H_
+#define SRC_TINT_IR_UNARY_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 Unary : public utils::Castable<Unary, Instruction> {
+  public:
+    /// The kind of instruction.
+    enum class Kind {
+        kAddressOf,
+        kComplement,
+        kIndirection,
+        kNegation,
+        kNot,
+    };
+
+    /// Constructor
+    /// @param kind the kind of unary instruction
+    /// @param result the result value
+    /// @param val the lhs of the instruction
+    Unary(Kind kind, Value* result, Value* val);
+    Unary(const Unary& instr) = delete;
+    Unary(Unary&& instr) = delete;
+    ~Unary() override;
+
+    Unary& operator=(const Unary& instr) = delete;
+    Unary& operator=(Unary&& instr) = delete;
+
+    /// @returns the kind of instruction
+    Kind GetKind() const { return kind_; }
+
+    /// @returns the value for the instruction
+    const Value* Val() const { return val_; }
+
+    /// 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:
+    Kind kind_;
+    Value* val_ = nullptr;
+};
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_UNARY_H_
diff --git a/src/tint/ir/unary_test.cc b/src/tint/ir/unary_test.cc
new file mode 100644
index 0000000..cfc5579
--- /dev/null
+++ b/src/tint/ir/unary_test.cc
@@ -0,0 +1,161 @@
+// 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, CreateAddressOf) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_runtime_id = Runtime::Id(42);
+    // TODO(dsinclair): This would be better as an identifier, but works for now.
+    const auto* instr =
+        b.builder.AddressOf(b.builder.ir.types.Get<type::Pointer>(
+                                b.builder.ir.types.Get<type::I32>(),
+                                builtin::AddressSpace::kPrivate, builtin::Access::kReadWrite),
+                            b.builder.Constant(4_i));
+
+    EXPECT_EQ(instr->GetKind(), Unary::Kind::kAddressOf);
+
+    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->Val()->Is<Constant>());
+    auto lhs = instr->Val()->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 (ptr<private, i32, read_write>) = &4");
+}
+
+TEST_F(IR_InstructionTest, CreateComplement) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_runtime_id = Runtime::Id(42);
+    const auto* instr =
+        b.builder.Complement(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i));
+
+    EXPECT_EQ(instr->GetKind(), Unary::Kind::kComplement);
+
+    ASSERT_TRUE(instr->Result()->Is<Runtime>());
+    EXPECT_EQ(Runtime::Id(42), instr->Result()->As<Runtime>()->AsId());
+
+    ASSERT_TRUE(instr->Val()->Is<Constant>());
+    auto lhs = instr->Val()->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, CreateIndirection) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_runtime_id = Runtime::Id(42);
+    // TODO(dsinclair): This would be better as an identifier, but works for now.
+    const auto* instr =
+        b.builder.Indirection(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i));
+
+    EXPECT_EQ(instr->GetKind(), Unary::Kind::kIndirection);
+
+    ASSERT_TRUE(instr->Result()->Is<Runtime>());
+    EXPECT_EQ(Runtime::Id(42), instr->Result()->As<Runtime>()->AsId());
+
+    ASSERT_TRUE(instr->Val()->Is<Constant>());
+    auto lhs = instr->Val()->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, CreateNegation) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_runtime_id = Runtime::Id(42);
+    const auto* instr =
+        b.builder.Negation(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i));
+
+    EXPECT_EQ(instr->GetKind(), Unary::Kind::kNegation);
+
+    ASSERT_TRUE(instr->Result()->Is<Runtime>());
+    EXPECT_EQ(Runtime::Id(42), instr->Result()->As<Runtime>()->AsId());
+
+    ASSERT_TRUE(instr->Val()->Is<Constant>());
+    auto lhs = instr->Val()->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, CreateNot) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_runtime_id = Runtime::Id(42);
+    const auto* instr =
+        b.builder.Not(b.builder.ir.types.Get<type::Bool>(), b.builder.Constant(true));
+
+    EXPECT_EQ(instr->GetKind(), Unary::Kind::kNot);
+
+    ASSERT_TRUE(instr->Result()->Is<Runtime>());
+    EXPECT_EQ(Runtime::Id(42), instr->Result()->As<Runtime>()->AsId());
+
+    ASSERT_TRUE(instr->Val()->Is<Constant>());
+    auto lhs = instr->Val()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<bool>>());
+    EXPECT_TRUE(lhs->As<constant::Scalar<bool>>()->ValueAs<bool>());
+
+    utils::StringStream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 (bool) = !true");
+}
+
+TEST_F(IR_InstructionTest, Unary_Usage) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_runtime_id = Runtime::Id(42);
+    const auto* instr =
+        b.builder.Negation(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i));
+
+    EXPECT_EQ(instr->GetKind(), Unary::Kind::kNegation);
+
+    ASSERT_NE(instr->Result(), nullptr);
+    ASSERT_EQ(instr->Result()->Usage().Length(), 1u);
+    EXPECT_EQ(instr->Result()->Usage()[0], instr);
+
+    ASSERT_NE(instr->Val(), nullptr);
+    ASSERT_EQ(instr->Val()->Usage().Length(), 1u);
+    EXPECT_EQ(instr->Val()->Usage()[0], instr);
+}
+
+}  // namespace
+}  // namespace tint::ir