diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt index 5cdadc69bf..4c4e9654e1 100644 --- a/src/tint/CMakeLists.txt +++ b/src/tint/CMakeLists.txt @@ -740,6 +740,8 @@ if(${TINT_BUILD_IR}) ir/switch.h ir/terminator.cc ir/terminator.h + ir/unary.cc + ir/unary.h ir/user_call.cc ir/user_call.h ir/value.cc @@ -1418,6 +1420,7 @@ if(TINT_BUILD_TESTS) ir/discard_test.cc ir/runtime_test.cc ir/test_helper.h + ir/unary_test.cc ) endif() diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc index 0e43265028..aa295267ae 100644 --- a/src/tint/ir/builder.cc +++ b/src/tint/ir/builder.cc @@ -173,6 +173,30 @@ Binary* Builder::Modulo(const type::Type* type, Value* lhs, Value* rhs) { return CreateBinary(Binary::Kind::kModulo, type, lhs, rhs); } +Unary* Builder::CreateUnary(Unary::Kind kind, const type::Type* type, Value* val) { + return ir.instructions.Create(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(Runtime(type), val); } diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h index f53e4c83fd..4a87e08972 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 @@ class Builder { /// @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 07226ce502..957875a872 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 @@ utils::Result BuilderImpl::EmitExpression(const ast::Expression* expr) { // [&](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 @@ void BuilderImpl::EmitVariable(const ast::Variable* var) { }); } +utils::Result 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 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 3a1dbbd407..73f2aeedf6 100644 --- a/src/tint/ir/builder_impl.h +++ b/src/tint/ir/builder_impl.h @@ -53,6 +53,7 @@ class Node; class ReturnStatement; class Statement; class SwitchStatement; +class UnaryOpExpression; class WhileStatement; class Variable; } // namespace tint::ast @@ -150,6 +151,11 @@ class BuilderImpl { /// @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 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 0000000000..532efccbe4 --- /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 0000000000..0337b4f2ce --- /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 { + 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 0000000000..cfc5579ada --- /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( + b.builder.ir.types.Get(), + builtin::AddressSpace::kPrivate, builtin::Access::kReadWrite), + b.builder.Constant(4_i)); + + EXPECT_EQ(instr->GetKind(), Unary::Kind::kAddressOf); + + ASSERT_TRUE(instr->Result()->Is()); + ASSERT_NE(instr->Result()->Type(), nullptr); + EXPECT_EQ(Runtime::Id(42), instr->Result()->As()->AsId()); + + ASSERT_TRUE(instr->Val()->Is()); + auto lhs = instr->Val()->As()->value; + ASSERT_TRUE(lhs->Is>()); + EXPECT_EQ(4_i, lhs->As>()->ValueAs()); + + utils::StringStream str; + instr->ToString(str); + EXPECT_EQ(str.str(), "%42 (ptr) = &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(), b.builder.Constant(4_i)); + + EXPECT_EQ(instr->GetKind(), Unary::Kind::kComplement); + + ASSERT_TRUE(instr->Result()->Is()); + EXPECT_EQ(Runtime::Id(42), instr->Result()->As()->AsId()); + + ASSERT_TRUE(instr->Val()->Is()); + auto lhs = instr->Val()->As()->value; + ASSERT_TRUE(lhs->Is>()); + EXPECT_EQ(4_i, lhs->As>()->ValueAs()); + + 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(), b.builder.Constant(4_i)); + + EXPECT_EQ(instr->GetKind(), Unary::Kind::kIndirection); + + ASSERT_TRUE(instr->Result()->Is()); + EXPECT_EQ(Runtime::Id(42), instr->Result()->As()->AsId()); + + ASSERT_TRUE(instr->Val()->Is()); + auto lhs = instr->Val()->As()->value; + ASSERT_TRUE(lhs->Is>()); + EXPECT_EQ(4_i, lhs->As>()->ValueAs()); + + 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(), b.builder.Constant(4_i)); + + EXPECT_EQ(instr->GetKind(), Unary::Kind::kNegation); + + ASSERT_TRUE(instr->Result()->Is()); + EXPECT_EQ(Runtime::Id(42), instr->Result()->As()->AsId()); + + ASSERT_TRUE(instr->Val()->Is()); + auto lhs = instr->Val()->As()->value; + ASSERT_TRUE(lhs->Is>()); + EXPECT_EQ(4_i, lhs->As>()->ValueAs()); + + 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(), b.builder.Constant(true)); + + EXPECT_EQ(instr->GetKind(), Unary::Kind::kNot); + + ASSERT_TRUE(instr->Result()->Is()); + EXPECT_EQ(Runtime::Id(42), instr->Result()->As()->AsId()); + + ASSERT_TRUE(instr->Val()->Is()); + auto lhs = instr->Val()->As()->value; + ASSERT_TRUE(lhs->Is>()); + EXPECT_TRUE(lhs->As>()->ValueAs()); + + 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(), 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