From 669e15e1390960b58a705fa2116c36c868730ef3 Mon Sep 17 00:00:00 2001 From: dan sinclair Date: Wed, 23 Nov 2022 02:11:52 +0000 Subject: [PATCH] [ir] Add EmitBinary This CL adds the machinery to emit binary operations to the IR. The debug helper is split into Debug and Disassembler. The Disassembler is used to help test the IR output. Bug: tint:1718 Change-Id: Iffdd3be92e69a87828655ac41be91b34d5618174 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/110841 Kokoro: Kokoro Commit-Queue: Dan Sinclair Reviewed-by: Ben Clayton --- src/tint/CMakeLists.txt | 5 + src/tint/cmd/main.cc | 4 +- src/tint/ir/block.h | 5 + src/tint/ir/builder.cc | 80 +++++ src/tint/ir/builder.h | 123 ++++++++ src/tint/ir/builder_impl.cc | 80 ++++- src/tint/ir/builder_impl.h | 6 + src/tint/ir/builder_impl_test.cc | 216 ++++++++++++++ src/tint/ir/debug.cc | 125 -------- src/tint/ir/disassembler.cc | 154 ++++++++++ src/tint/ir/disassembler.h | 58 ++++ src/tint/ir/op.cc | 105 +++++++ src/tint/ir/op.h | 113 +++++++ src/tint/ir/op_test.cc | 494 +++++++++++++++++++++++++++++++ src/tint/ir/register.cc | 46 +-- src/tint/ir/register.h | 50 ++-- src/tint/ir/register_test.cc | 55 +++- src/tint/ir/test_helper.h | 1 + 18 files changed, 1540 insertions(+), 180 deletions(-) create mode 100644 src/tint/ir/disassembler.cc create mode 100644 src/tint/ir/disassembler.h create mode 100644 src/tint/ir/op.cc create mode 100644 src/tint/ir/op.h create mode 100644 src/tint/ir/op_test.cc diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt index 16ba8cff50..dfe943388a 100644 --- a/src/tint/CMakeLists.txt +++ b/src/tint/CMakeLists.txt @@ -658,6 +658,8 @@ if(${TINT_BUILD_IR}) ir/builder_impl.h ir/debug.cc ir/debug.h + ir/disassembler.cc + ir/disassembler.h ir/flow_node.cc ir/flow_node.h ir/function.cc @@ -668,6 +670,8 @@ if(${TINT_BUILD_IR}) ir/loop.h ir/module.cc ir/module.h + ir/op.cc + ir/op.h ir/register.cc ir/register.h ir/switch.cc @@ -1335,6 +1339,7 @@ if(TINT_BUILD_TESTS) if (${TINT_BUILD_IR}) list(APPEND TINT_TEST_SRCS ir/builder_impl_test.cc + ir/op_test.cc ir/register_test.cc ir/test_helper.h ) diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc index 4eababd4ec..ae2e2e21c8 100644 --- a/src/tint/cmd/main.cc +++ b/src/tint/cmd/main.cc @@ -42,6 +42,7 @@ #if TINT_BUILD_IR #include "src/tint/ir/debug.h" +#include "src/tint/ir/disassembler.h" #include "src/tint/ir/module.h" #endif // TINT_BUILD_IR @@ -1343,7 +1344,8 @@ int main(int argc, const char** argv) { } else { auto mod = result.Move(); if (options.dump_ir) { - std::cout << tint::ir::Debug::AsString(&mod) << std::endl; + tint::ir::Disassembler d; + std::cout << d.Disassemble(mod) << std::endl; } if (options.dump_ir_graph) { auto graph = tint::ir::Debug::AsDotGraph(&mod); diff --git a/src/tint/ir/block.h b/src/tint/ir/block.h index 022aff251f..850c13ca57 100644 --- a/src/tint/ir/block.h +++ b/src/tint/ir/block.h @@ -16,6 +16,8 @@ #define SRC_TINT_IR_BLOCK_H_ #include "src/tint/ir/flow_node.h" +#include "src/tint/ir/op.h" +#include "src/tint/utils/vector.h" namespace tint::ir { @@ -30,6 +32,9 @@ class Block : public Castable { /// The node this block branches too. const FlowNode* branch_target = nullptr; + + /// The operations in the block + utils::Vector ops; }; } // namespace tint::ir diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc index 851d2d658b..57a40a795e 100644 --- a/src/tint/ir/builder.cc +++ b/src/tint/ir/builder.cc @@ -93,4 +93,84 @@ void Builder::Branch(Block* from, FlowNode* to) { to->inbound_branches.Push(from); } +Register::Id Builder::AllocateRegister() { + return next_register_id++; +} + +Op Builder::CreateOp(Op::Kind kind, Register lhs, Register rhs) { + return Op(kind, Register(AllocateRegister()), lhs, rhs); +} + +Op Builder::And(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kAnd, lhs, rhs); +} + +Op Builder::Or(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kOr, lhs, rhs); +} + +Op Builder::Xor(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kXor, lhs, rhs); +} + +Op Builder::LogicalAnd(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kLogicalAnd, lhs, rhs); +} + +Op Builder::LogicalOr(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kLogicalOr, lhs, rhs); +} + +Op Builder::Equal(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kEqual, lhs, rhs); +} + +Op Builder::NotEqual(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kNotEqual, lhs, rhs); +} + +Op Builder::LessThan(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kLessThan, lhs, rhs); +} + +Op Builder::GreaterThan(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kGreaterThan, lhs, rhs); +} + +Op Builder::LessThanEqual(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kLessThanEqual, lhs, rhs); +} + +Op Builder::GreaterThanEqual(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kGreaterThanEqual, lhs, rhs); +} + +Op Builder::ShiftLeft(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kShiftLeft, lhs, rhs); +} + +Op Builder::ShiftRight(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kShiftRight, lhs, rhs); +} + +Op Builder::Add(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kAdd, lhs, rhs); +} + +Op Builder::Subtract(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kSubtract, lhs, rhs); +} + +Op Builder::Multiply(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kMultiply, lhs, rhs); +} + +Op Builder::Divide(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kDivide, lhs, rhs); +} + +Op Builder::Modulo(Register lhs, Register rhs) { + return CreateOp(Op::Kind::kModulo, lhs, rhs); +} + } // namespace tint::ir diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h index cf0de701b2..091aac85f8 100644 --- a/src/tint/ir/builder.h +++ b/src/tint/ir/builder.h @@ -19,6 +19,8 @@ #include "src/tint/ir/if.h" #include "src/tint/ir/loop.h" #include "src/tint/ir/module.h" +#include "src/tint/ir/op.h" +#include "src/tint/ir/register.h" #include "src/tint/ir/switch.h" #include "src/tint/ir/terminator.h" @@ -81,8 +83,129 @@ class Builder { /// @param to the node to branch too void Branch(Block* from, FlowNode* to); + /// Creates an op for `lhs kind rhs` + /// @param kind the kind of operation + /// @param lhs the left-hand-side of the operation + /// @param rhs the right-hand-side of the operation + /// @returns the operation + Op CreateOp(Op::Kind kind, Register lhs, Register rhs); + + /// Creates an And operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op And(Register lhs, Register rhs); + + /// Creates an Or operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Or(Register lhs, Register rhs); + + /// Creates an Xor operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Xor(Register lhs, Register rhs); + + /// Creates an LogicalAnd operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op LogicalAnd(Register lhs, Register rhs); + + /// Creates an LogicalOr operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op LogicalOr(Register lhs, Register rhs); + + /// Creates an Equal operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Equal(Register lhs, Register rhs); + + /// Creates an NotEqual operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op NotEqual(Register lhs, Register rhs); + + /// Creates an LessThan operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op LessThan(Register lhs, Register rhs); + + /// Creates an GreaterThan operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op GreaterThan(Register lhs, Register rhs); + + /// Creates an LessThanEqual operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op LessThanEqual(Register lhs, Register rhs); + + /// Creates an GreaterThanEqual operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op GreaterThanEqual(Register lhs, Register rhs); + + /// Creates an ShiftLeft operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op ShiftLeft(Register lhs, Register rhs); + + /// Creates an ShiftRight operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op ShiftRight(Register lhs, Register rhs); + + /// Creates an Add operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Add(Register lhs, Register rhs); + + /// Creates an Subtract operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Subtract(Register lhs, Register rhs); + + /// Creates an Multiply operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Multiply(Register lhs, Register rhs); + + /// Creates an Divide operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Divide(Register lhs, Register rhs); + + /// Creates an Modulo operation + /// @param lhs the lhs of the add + /// @param rhs the rhs of the add + /// @returns the operation + Op Modulo(Register lhs, Register rhs); + + /// @returns a unique register id + Register::Id AllocateRegister(); + /// The IR module. Module ir; + + /// The next register number to allocate + Register::Id next_register_id = 1; }; } // namespace tint::ir diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc index b2dab25699..06ed837b86 100644 --- a/src/tint/ir/builder_impl.cc +++ b/src/tint/ir/builder_impl.cc @@ -15,6 +15,7 @@ #include "src/tint/ir/builder_impl.h" #include "src/tint/ast/alias.h" +#include "src/tint/ast/binary_expression.h" #include "src/tint/ast/block_statement.h" #include "src/tint/ast/bool_literal_expression.h" #include "src/tint/ast/break_if_statement.h" @@ -519,7 +520,7 @@ utils::Result BuilderImpl::EmitExpression(const ast::Expression* expr) return tint::Switch( expr, // [&](const ast::IndexAccessorExpression* a) { return EmitIndexAccessor(a); }, - // [&](const ast::BinaryExpression* b) { return EmitBinary(b); }, + [&](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) { return EmitIdentifier(i); }, @@ -550,6 +551,83 @@ bool BuilderImpl::EmitVariable(const ast::Variable* var) { }); } +utils::Result BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) { + auto lhs = EmitExpression(expr->lhs); + if (!lhs) { + return utils::Failure; + } + + auto rhs = EmitExpression(expr->rhs); + if (!rhs) { + return utils::Failure; + } + + Op op; + switch (expr->op) { + case ast::BinaryOp::kAnd: + op = builder.And(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kOr: + op = builder.Or(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kXor: + op = builder.Xor(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kLogicalAnd: + op = builder.LogicalAnd(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kLogicalOr: + op = builder.LogicalOr(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kEqual: + op = builder.Equal(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kNotEqual: + op = builder.NotEqual(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kLessThan: + op = builder.LessThan(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kGreaterThan: + op = builder.GreaterThan(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kLessThanEqual: + op = builder.LessThanEqual(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kGreaterThanEqual: + op = builder.GreaterThanEqual(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kShiftLeft: + op = builder.ShiftLeft(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kShiftRight: + op = builder.ShiftRight(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kAdd: + op = builder.Add(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kSubtract: + op = builder.Subtract(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kMultiply: + op = builder.Multiply(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kDivide: + op = builder.Divide(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kModulo: + op = builder.Modulo(lhs.Get(), rhs.Get()); + break; + case ast::BinaryOp::kNone: + TINT_ICE(IR, diagnostics_) << "missing binary operand type"; + return utils::Failure; + } + + auto result = op.Result(); + current_flow_block->ops.Push(op); + return result; +} + utils::Result BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) { return tint::Switch( // lit, diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h index 18c4d3e960..ef5238ec6e 100644 --- a/src/tint/ir/builder_impl.h +++ b/src/tint/ir/builder_impl.h @@ -31,6 +31,7 @@ namespace tint { class Program; } // namespace tint namespace tint::ast { +class BinaryExpression; class BlockStatement; class BreakIfStatement; class BreakStatement; @@ -146,6 +147,11 @@ class BuilderImpl { /// @returns true if successful, false otherwise bool EmitVariable(const ast::Variable* var); + /// Emits a binary expression + /// @param expr the binary expression + /// @returns the register storing the result if successful, utils::Failure otherwise + utils::Result EmitBinary(const ast::BinaryExpression* expr); + /// Emits a literal expression /// @param lit the literal to emit /// @returns true if successful, false otherwise diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc index 04c8d00916..4a1efd53c5 100644 --- a/src/tint/ir/builder_impl_test.cc +++ b/src/tint/ir/builder_impl_test.cc @@ -1396,5 +1396,221 @@ TEST_F(IR_BuilderImplTest, EmitLiteral_U32) { EXPECT_EQ(2_u, reg.AsU32()); } +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Add) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Add(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 + 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Subtract) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Sub(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 - 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Multiply) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Mul(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 * 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Div) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Div(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 / 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Modulo) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Mod(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 % 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_And) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(And(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 & 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Or) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Or(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 | 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Xor) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Xor(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 ^ 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalAnd) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(LogicalAnd(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 && 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalOr) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(LogicalOr(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 || 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Eqaul) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Equal(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 == 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_NotEqual) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(NotEqual(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 != 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThan) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(LessThan(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 < 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThan) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(GreaterThan(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 > 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThanEqual) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(LessThanEqual(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 <= 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThanEqual) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(GreaterThanEqual(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 >= 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftLeft) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Shl(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 << 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftRight) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(Shr(3_u, 4_u)); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 >> 4 +)"); +} + +TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Compound) { + auto& b = CreateEmptyBuilder(); + auto r = b.EmitExpression(LogicalOr( // + LessThan(1_u, Add(Shr(3_u, 4_u), 9_u)), GreaterThan(2.5_f, Div(6.7_f, Mul(2.3_f, 5.5_f))))); + ASSERT_TRUE(r); + + Disassembler d; + d.EmitBlockOps(b.current_flow_block); + EXPECT_EQ(d.AsString(), R"(%1 = 3 >> 4 +%2 = %1 + 9 +%3 = 1 < %2 +%4 = 2.300000 * 5.500000 +%5 = 6.700000 / %4 +%6 = 2.500000 > %5 +%7 = %3 || %6 +)"); +} + } // namespace } // namespace tint::ir diff --git a/src/tint/ir/debug.cc b/src/tint/ir/debug.cc index bcac46d9bf..be83111202 100644 --- a/src/tint/ir/debug.cc +++ b/src/tint/ir/debug.cc @@ -26,33 +26,6 @@ #include "src/tint/program.h" namespace tint::ir { -namespace { - -class ScopedStopNode { - public: - ScopedStopNode(std::unordered_set* stop_nodes, const FlowNode* node) - : stop_nodes_(stop_nodes), node_(node) { - stop_nodes_->insert(node_); - } - - ~ScopedStopNode() { stop_nodes_->erase(node_); } - - private: - std::unordered_set* stop_nodes_; - const FlowNode* node_; -}; - -class ScopedIndent { - public: - explicit ScopedIndent(uint32_t* indent) : indent_(indent) { (*indent_) += 2; } - - ~ScopedIndent() { (*indent_) -= 2; } - - private: - uint32_t* indent_; -}; - -} // namespace // static std::string Debug::AsDotGraph(const Module* mod) { @@ -183,102 +156,4 @@ std::string Debug::AsDotGraph(const Module* mod) { return out.str(); } -// static -std::string Debug::AsString(const Module* mod) { - std::stringstream out; - - std::unordered_set visited; - std::unordered_set stop_nodes; - uint32_t indent_size = 0; - - std::function indent = [&]() -> std::ostream& { - for (uint32_t i = 0; i < indent_size; i++) { - out << " "; - } - return out; - }; - - std::function Walk = [&](const FlowNode* node) { - if ((visited.count(node) > 0) || (stop_nodes.count(node) > 0)) { - return; - } - visited.insert(node); - - tint::Switch( - node, - [&](const ir::Function* f) { - out << "Function" << std::endl; - - { - ScopedIndent func_indent(&indent_size); - ScopedStopNode scope(&stop_nodes, f->end_target); - Walk(f->start_target); - } - Walk(f->end_target); - }, - [&](const ir::Block* b) { - indent() << "Block" << std::endl; - Walk(b->branch_target); - }, - [&](const ir::Switch* s) { - indent() << "Switch (" << s->condition.AsString() << ")" << std::endl; - - { - ScopedIndent switch_indent(&indent_size); - ScopedStopNode scope(&stop_nodes, s->merge_target); - for (const auto& c : s->cases) { - indent() << "Case" << std::endl; - ScopedIndent case_indent(&indent_size); - Walk(c.start_target); - } - } - - indent() << "Switch Merge" << std::endl; - Walk(s->merge_target); - }, - [&](const ir::If* i) { - indent() << "if (" << i->condition.AsString() << ")" << std::endl; - { - ScopedIndent if_indent(&indent_size); - ScopedStopNode scope(&stop_nodes, i->merge_target); - - indent() << "true branch" << std::endl; - Walk(i->true_target); - - indent() << "false branch" << std::endl; - Walk(i->false_target); - } - - indent() << "if merge" << std::endl; - Walk(i->merge_target); - }, - [&](const ir::Loop* l) { - indent() << "loop" << std::endl; - { - ScopedStopNode loop_scope(&stop_nodes, l->merge_target); - ScopedIndent loop_indent(&indent_size); - { - ScopedStopNode inner_scope(&stop_nodes, l->continuing_target); - indent() << "loop start" << std::endl; - Walk(l->start_target); - } - - indent() << "loop continuing" << std::endl; - ScopedIndent continuing_indent(&indent_size); - Walk(l->continuing_target); - } - - indent() << "loop merge" << std::endl; - Walk(l->merge_target); - }, - [&](const ir::Terminator*) { indent() << "Function end" << std::endl; }); - }; - - for (const auto* func : mod->functions) { - Walk(func); - } - - return out.str(); -} - } // namespace tint::ir diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc new file mode 100644 index 0000000000..88e2e69ddc --- /dev/null +++ b/src/tint/ir/disassembler.cc @@ -0,0 +1,154 @@ +// Copyright 2022 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/disassembler.h" + +#include "src/tint/ir/block.h" +#include "src/tint/ir/if.h" +#include "src/tint/ir/loop.h" +#include "src/tint/ir/switch.h" +#include "src/tint/ir/terminator.h" +#include "src/tint/program.h" + +namespace tint::ir { +namespace { + +class ScopedStopNode { + public: + ScopedStopNode(std::unordered_set* stop_nodes_, const FlowNode* node) + : stop_nodes__(stop_nodes_), node_(node) { + stop_nodes__->insert(node_); + } + + ~ScopedStopNode() { stop_nodes__->erase(node_); } + + private: + std::unordered_set* stop_nodes__; + const FlowNode* node_; +}; + +class ScopedIndent { + public: + explicit ScopedIndent(uint32_t* indent) : indent_(indent) { (*indent_) += 2; } + + ~ScopedIndent() { (*indent_) -= 2; } + + private: + uint32_t* indent_; +}; + +} // namespace + +Disassembler::Disassembler() = default; + +Disassembler::~Disassembler() = default; + +std::ostream& Disassembler::Indent() { + for (uint32_t i = 0; i < indent_size_; i++) { + out_ << " "; + } + return out_; +} + +void Disassembler::EmitBlockOps(const Block* b) { + for (const auto& op : b->ops) { + out_ << op << std::endl; + } +} + +void Disassembler::Walk(const FlowNode* node) { + if ((visited_.count(node) > 0) || (stop_nodes_.count(node) > 0)) { + return; + } + visited_.insert(node); + + tint::Switch( + node, + [&](const ir::Function* f) { + Indent() << "Function" << std::endl; + + { + ScopedIndent func_indent(&indent_size_); + ScopedStopNode scope(&stop_nodes_, f->end_target); + Walk(f->start_target); + } + Walk(f->end_target); + }, + [&](const ir::Block* b) { + Indent() << "Block" << std::endl; + EmitBlockOps(b); + Walk(b->branch_target); + }, + [&](const ir::Switch* s) { + Indent() << "Switch (" << s->condition << ")" << std::endl; + + { + ScopedIndent switch_indent(&indent_size_); + ScopedStopNode scope(&stop_nodes_, s->merge_target); + for (const auto& c : s->cases) { + Indent() << "Case" << std::endl; + ScopedIndent case_indent(&indent_size_); + Walk(c.start_target); + } + } + + Indent() << "Switch Merge" << std::endl; + Walk(s->merge_target); + }, + [&](const ir::If* i) { + Indent() << "if (" << i->condition << ")" << std::endl; + { + ScopedIndent if_indent(&indent_size_); + ScopedStopNode scope(&stop_nodes_, i->merge_target); + + Indent() << "true branch" << std::endl; + Walk(i->true_target); + + Indent() << "false branch" << std::endl; + Walk(i->false_target); + } + + Indent() << "if merge" << std::endl; + Walk(i->merge_target); + }, + [&](const ir::Loop* l) { + Indent() << "loop" << std::endl; + { + ScopedStopNode loop_scope(&stop_nodes_, l->merge_target); + ScopedIndent loop_indent(&indent_size_); + { + ScopedStopNode inner_scope(&stop_nodes_, l->continuing_target); + Indent() << "loop start" << std::endl; + Walk(l->start_target); + } + + Indent() << "loop continuing" << std::endl; + ScopedIndent continuing_indent(&indent_size_); + Walk(l->continuing_target); + } + + Indent() << "loop merge" << std::endl; + Walk(l->merge_target); + }, + [&](const ir::Terminator*) { Indent() << "Function end" << std::endl; }); +} + +std::string Disassembler::Disassemble(const Module& mod) { + for (const auto* func : mod.functions) { + Walk(func); + } + return out_.str(); +} + +} // namespace tint::ir diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h new file mode 100644 index 0000000000..9cb8993fec --- /dev/null +++ b/src/tint/ir/disassembler.h @@ -0,0 +1,58 @@ +// Copyright 2022 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_DISASSEMBLER_H_ +#define SRC_TINT_IR_DISASSEMBLER_H_ + +#include +#include +#include + +#include "src/tint/ir/flow_node.h" +#include "src/tint/ir/module.h" + +namespace tint::ir { + +/// Helper class to disassemble the IR +class Disassembler { + public: + /// Constructor + Disassembler(); + ~Disassembler(); + + /// Returns the module as a string + /// @param mod the module to emit + /// @returns the string representation of the module + std::string Disassemble(const Module& mod); + + /// Writes the block ops to the stream + /// @param b the block containing the ops + void EmitBlockOps(const Block* b); + + /// @returns the string representation + std::string AsString() const { return out_.str(); } + + private: + std::ostream& Indent(); + void Walk(const FlowNode* node); + + std::stringstream out_; + std::unordered_set visited_; + std::unordered_set stop_nodes_; + uint32_t indent_size_ = 0; +}; + +} // namespace tint::ir + +#endif // SRC_TINT_IR_DISASSEMBLER_H_ diff --git a/src/tint/ir/op.cc b/src/tint/ir/op.cc new file mode 100644 index 0000000000..3e6d9936ee --- /dev/null +++ b/src/tint/ir/op.cc @@ -0,0 +1,105 @@ +// Copyright 2022 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/op.h" + +namespace tint::ir { + +Op::Op() {} + +Op::Op(Kind kind, Register result, Register lhs, Register rhs) + : kind_(kind), result_(result), args_({lhs, rhs}) {} + +Op::Op(const Op&) = default; + +Op::Op(Op&& o) = default; + +Op::~Op() = default; + +Op& Op::operator=(const Op& o) = default; + +Op& Op::operator=(Op&& o) = default; + +std::ostream& operator<<(std::ostream& out, const Op& op) { + out << op.Result() << " = "; + if (op.HasLHS()) { + out << op.LHS(); + } + out << " "; + + switch (op.GetKind()) { + case Op::Kind::kAdd: + out << "+"; + break; + case Op::Kind::kSubtract: + out << "-"; + break; + case Op::Kind::kMultiply: + out << "*"; + break; + case Op::Kind::kDivide: + out << "/"; + break; + case Op::Kind::kModulo: + out << "%"; + break; + case Op::Kind::kAnd: + out << "&"; + break; + case Op::Kind::kOr: + out << "|"; + break; + case Op::Kind::kXor: + out << "^"; + break; + case Op::Kind::kLogicalAnd: + out << "&&"; + break; + case Op::Kind::kLogicalOr: + out << "||"; + break; + case Op::Kind::kEqual: + out << "=="; + break; + case Op::Kind::kNotEqual: + out << "!="; + break; + case Op::Kind::kLessThan: + out << "<"; + break; + case Op::Kind::kGreaterThan: + out << ">"; + break; + case Op::Kind::kLessThanEqual: + out << "<="; + break; + case Op::Kind::kGreaterThanEqual: + out << ">="; + break; + case Op::Kind::kShiftLeft: + out << "<<"; + break; + case Op::Kind::kShiftRight: + out << ">>"; + break; + } + + if (op.HasRHS()) { + out << " " << op.RHS(); + } + + return out; +} + +} // namespace tint::ir diff --git a/src/tint/ir/op.h b/src/tint/ir/op.h new file mode 100644 index 0000000000..a9fc78ffd4 --- /dev/null +++ b/src/tint/ir/op.h @@ -0,0 +1,113 @@ +// Copyright 2022 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_OP_H_ +#define SRC_TINT_IR_OP_H_ + +#include + +#include "src/tint/ir/register.h" +#include "src/tint/utils/vector.h" + +namespace tint::ir { + +/// An operation in the IR. +class Op { + public: + /// The kind of operation. + enum class Kind { + kAdd, + kSubtract, + kMultiply, + kDivide, + kModulo, + + kAnd, + kOr, + kXor, + + kLogicalAnd, + kLogicalOr, + + kEqual, + kNotEqual, + kLessThan, + kGreaterThan, + kLessThanEqual, + kGreaterThanEqual, + + kShiftLeft, + kShiftRight + }; + + /// Constructor + Op(); + /// Constructor + /// @param kind the kind of operation + /// @param result the result register + /// @param lhs the lhs of the operation + /// @param rhs the rhs of the operation + Op(Kind kind, Register result, Register lhs, Register rhs); + /// Copy constructor + /// @param o the op to copy from + Op(const Op& o); + /// Move constructor + /// @param o the op to move from + Op(Op&& o); + /// Destructor + ~Op(); + + /// Copy assign + /// @param o the op to copy from + /// @returns a reference to this + Op& operator=(const Op& o); + /// Move assign + /// @param o the op to move from + /// @returns a reference to this + Op& operator=(Op&& o); + + /// @returns the kind of operation + Kind GetKind() const { return kind_; } + + /// @returns the result register for the operation + const Register& Result() const { return result_; } + + /// @returns true if the op has a LHS + bool HasLHS() const { return args_.Length() >= 1; } + /// @returns the left-hand-side register for the operation + const Register& LHS() const { + TINT_ASSERT(IR, HasLHS()); + return args_[0]; + } + + /// @returns true if the op has a RHS + bool HasRHS() const { return args_.Length() >= 2; } + /// @returns the right-hand-side register for the operation + const Register& RHS() const { + TINT_ASSERT(IR, HasRHS()); + return args_[1]; + } + + private: + Kind kind_; + + Register result_; + utils::Vector args_; +}; + +std::ostream& operator<<(std::ostream& out, const Op&); + +} // namespace tint::ir + +#endif // SRC_TINT_IR_OP_H_ diff --git a/src/tint/ir/op_test.cc b/src/tint/ir/op_test.cc new file mode 100644 index 0000000000..01604d12c0 --- /dev/null +++ b/src/tint/ir/op_test.cc @@ -0,0 +1,494 @@ +// Copyright 2022 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 + +#include "src/tint/ir/op.h" +#include "src/tint/ir/test_helper.h" + +namespace tint::ir { +namespace { + +using IR_OpTest = TestHelper; + +TEST_F(IR_OpTest, CreateAnd) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.And(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kAnd); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 & 2"); +} + +TEST_F(IR_OpTest, CreateOr) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Or(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kOr); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 | 2"); +} + +TEST_F(IR_OpTest, CreateXor) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Xor(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kXor); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 ^ 2"); +} + +TEST_F(IR_OpTest, CreateLogicalAnd) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.LogicalAnd(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kLogicalAnd); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 && 2"); +} + +TEST_F(IR_OpTest, CreateLogicalOr) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.LogicalOr(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kLogicalOr); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 || 2"); +} + +TEST_F(IR_OpTest, CreateEqual) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Equal(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kEqual); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 == 2"); +} + +TEST_F(IR_OpTest, CreateNotEqual) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.NotEqual(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kNotEqual); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 != 2"); +} + +TEST_F(IR_OpTest, CreateLessThan) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.LessThan(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kLessThan); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 < 2"); +} + +TEST_F(IR_OpTest, CreateGreaterThan) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.GreaterThan(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kGreaterThan); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 > 2"); +} + +TEST_F(IR_OpTest, CreateLessThanEqual) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.LessThanEqual(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kLessThanEqual); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 <= 2"); +} + +TEST_F(IR_OpTest, CreateGreaterThanEqual) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.GreaterThanEqual(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kGreaterThanEqual); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 >= 2"); +} + +TEST_F(IR_OpTest, CreateShiftLeft) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.ShiftLeft(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kShiftLeft); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 << 2"); +} + +TEST_F(IR_OpTest, CreateShiftRight) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.ShiftRight(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kShiftRight); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 >> 2"); +} + +TEST_F(IR_OpTest, CreateAdd) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Add(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kAdd); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 + 2"); +} + +TEST_F(IR_OpTest, CreateSubtract) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Subtract(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kSubtract); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 - 2"); +} + +TEST_F(IR_OpTest, CreateMultiply) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Multiply(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kMultiply); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 * 2"); +} + +TEST_F(IR_OpTest, CreateDivide) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Divide(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kDivide); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 / 2"); +} + +TEST_F(IR_OpTest, CreateModulo) { + auto& b = CreateEmptyBuilder(); + + b.builder.next_register_id = Register::Id(42); + auto o = b.builder.Modulo(Register(i32(4)), Register(i32(2))); + + EXPECT_EQ(o.GetKind(), Op::Kind::kModulo); + + ASSERT_TRUE(o.Result().IsTemp()); + EXPECT_EQ(Register::Id(42), o.Result().AsId()); + + ASSERT_TRUE(o.HasLHS()); + auto& lhs = o.LHS(); + ASSERT_TRUE(lhs.IsI32()); + EXPECT_EQ(i32(4), lhs.AsI32()); + + ASSERT_TRUE(o.HasRHS()); + auto& rhs = o.RHS(); + ASSERT_TRUE(rhs.IsI32()); + EXPECT_EQ(i32(2), rhs.AsI32()); + + std::stringstream str; + str << o; + EXPECT_EQ(str.str(), "%42 = 4 % 2"); +} + +} // namespace +} // namespace tint::ir diff --git a/src/tint/ir/register.cc b/src/tint/ir/register.cc index ba156e237e..f3caa76116 100644 --- a/src/tint/ir/register.cc +++ b/src/tint/ir/register.cc @@ -42,27 +42,35 @@ Register& Register::operator=(const Register& o) = default; Register& Register::operator=(Register&& o) = default; -std::string Register::AsString() const { - switch (kind_) { - case Kind::kTemp: - return "%" + std::to_string(AsId()); - case Kind::kF32: - return std::to_string(AsF32().value); - case Kind::kF16: - return std::to_string(AsF16().value); - case Kind::kI32: - return std::to_string(AsI32().value); - case Kind::kU32: - return std::to_string(AsU32().value); - // TODO(dsinclair): Emit the symbol instead of v - case Kind::kVar: - return "%v" + std::to_string(AsVarData().id); - case Kind::kBool: - return AsBool() ? "true" : "false"; - case Kind::kUninitialized: +std::ostream& operator<<(std::ostream& out, const Register& r) { + switch (r.GetKind()) { + case Register::Kind::kTemp: + out << "%" << std::to_string(r.AsId()); + break; + case Register::Kind::kF32: + out << std::to_string(r.AsF32().value); + break; + case Register::Kind::kF16: + out << std::to_string(r.AsF16().value); + break; + case Register::Kind::kI32: + out << std::to_string(r.AsI32().value); + break; + case Register::Kind::kU32: + out << std::to_string(r.AsU32().value); + break; + // TODO(dsinclair): Emit the symbol instead of v + case Register::Kind::kVar: + out << "%v" << std::to_string(r.AsVarData().id); + break; + case Register::Kind::kBool: + out << (r.AsBool() ? "true" : "false"); + break; + case Register::Kind::kUninitialized: + out << "unknown register"; break; } - return "unknown register"; + return out; } } // namespace tint::ir diff --git a/src/tint/ir/register.h b/src/tint/ir/register.h index 54d863d1f6..d1c41eba15 100644 --- a/src/tint/ir/register.h +++ b/src/tint/ir/register.h @@ -15,7 +15,7 @@ #ifndef SRC_TINT_IR_REGISTER_H_ #define SRC_TINT_IR_REGISTER_H_ -#include +#include #include #include "src/tint/number.h" @@ -31,6 +31,26 @@ class Register { /// A register id. using Id = uint32_t; + /// The type of the register + enum class Kind { + /// A uninitialized register + kUninitialized, + /// A temporary allocated register + kTemp, + /// A f32 register + kF32, + /// A f16 register + kF16, + /// An i32 register + kI32, + /// A u32 register + kU32, + /// A variable register + kVar, + /// A boolean register + kBool, + }; + /// Stores data for a given variable. There will be multiple `VarData` entries for a given `id`. /// The `id` acts like a generation number (although they aren't sequential, they are /// increasing). As the variable is stored too a new register will be created and the the `id` @@ -110,6 +130,9 @@ class Register { /// @returns true if this is a bool register bool IsBool() const { return kind_ == Kind::kBool; } + /// @returns the kind of register + Kind GetKind() const { return kind_; } + /// @returns the register data as a `f32`. /// @note, must only be called if `IsF32()` is true f32 AsF32() const { return std::get(data_); } @@ -132,36 +155,15 @@ class Register { /// @note, must only be called if `IsBool()` is true bool AsBool() const { return std::get(data_); } - /// @returns the string representation of the register - std::string AsString() const; - private: - /// The type of the register - enum class Kind { - /// A uninitialized register - kUninitialized, - /// A temporary allocated register - kTemp, - /// A f32 register - kF32, - /// A f16 register - kF16, - /// An i32 register - kI32, - /// A u32 register - kU32, - /// A variable register - kVar, - /// A boolean register - kBool, - }; - /// The type of data stored in this register Kind kind_; /// The data stored in the register std::variant data_; }; +std::ostream& operator<<(std::ostream& out, const Register& r); + } // namespace tint::ir #endif // SRC_TINT_IR_REGISTER_H_ diff --git a/src/tint/ir/register_test.cc b/src/tint/ir/register_test.cc index af4b659d2e..fb0194f89d 100644 --- a/src/tint/ir/register_test.cc +++ b/src/tint/ir/register_test.cc @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "src/tint/ir/test_helper.h" +#include #include "src/tint/ir/register.h" +#include "src/tint/ir/test_helper.h" namespace tint::ir { namespace { @@ -24,9 +25,13 @@ using namespace tint::number_suffixes; // NOLINT using IR_RegisterTest = TestHelper; TEST_F(IR_RegisterTest, f32) { + std::stringstream str; + Register r(1.2_f); EXPECT_EQ(1.2_f, r.AsF32()); - EXPECT_EQ("1.200000", r.AsString()); + + str << r; + EXPECT_EQ("1.200000", str.str()); EXPECT_TRUE(r.IsF32()); EXPECT_FALSE(r.IsF16()); @@ -38,9 +43,13 @@ TEST_F(IR_RegisterTest, f32) { } TEST_F(IR_RegisterTest, f16) { + std::stringstream str; + Register r(1.1_h); EXPECT_EQ(1.1_h, r.AsF16()); - EXPECT_EQ("1.099609", r.AsString()); + + str << r; + EXPECT_EQ("1.099609", str.str()); EXPECT_FALSE(r.IsF32()); EXPECT_TRUE(r.IsF16()); @@ -52,9 +61,13 @@ TEST_F(IR_RegisterTest, f16) { } TEST_F(IR_RegisterTest, i32) { + std::stringstream str; + Register r(1_i); EXPECT_EQ(1_i, r.AsI32()); - EXPECT_EQ("1", r.AsString()); + + str << r; + EXPECT_EQ("1", str.str()); EXPECT_FALSE(r.IsF32()); EXPECT_FALSE(r.IsF16()); @@ -66,9 +79,13 @@ TEST_F(IR_RegisterTest, i32) { } TEST_F(IR_RegisterTest, u32) { + std::stringstream str; + Register r(2_u); EXPECT_EQ(2_u, r.AsU32()); - EXPECT_EQ("2", r.AsString()); + + str << r; + EXPECT_EQ("2", str.str()); EXPECT_FALSE(r.IsF32()); EXPECT_FALSE(r.IsF16()); @@ -80,9 +97,13 @@ TEST_F(IR_RegisterTest, u32) { } TEST_F(IR_RegisterTest, id) { + std::stringstream str; + Register r(Register::Id(4)); EXPECT_EQ(4u, r.AsId()); - EXPECT_EQ("%4", r.AsString()); + + str << r; + EXPECT_EQ("%4", str.str()); EXPECT_FALSE(r.IsF32()); EXPECT_FALSE(r.IsF16()); @@ -94,13 +115,20 @@ TEST_F(IR_RegisterTest, id) { } TEST_F(IR_RegisterTest, bool) { + std::stringstream str; + Register r(false); EXPECT_FALSE(r.AsBool()); - EXPECT_EQ("false", r.AsString()); + str << r; + EXPECT_EQ("false", str.str()); + + str.str(""); r = Register(true); EXPECT_TRUE(r.AsBool()); - EXPECT_EQ("true", r.AsString()); + + str << r; + EXPECT_EQ("true", str.str()); EXPECT_FALSE(r.IsF32()); EXPECT_FALSE(r.IsF16()); @@ -112,16 +140,23 @@ TEST_F(IR_RegisterTest, bool) { } TEST_F(IR_RegisterTest, var) { + std::stringstream str; + Symbol s; Register r(s, 2); EXPECT_EQ(2u, r.AsVarData().id); EXPECT_EQ(s, r.AsVarData().sym); - EXPECT_EQ("%v2", r.AsString()); + + str << r; + EXPECT_EQ("%v2", str.str()); + str.str(""); r = Register(s, 4); EXPECT_EQ(4u, r.AsVarData().id); EXPECT_EQ(s, r.AsVarData().sym); - EXPECT_EQ("%v4", r.AsString()); + + str << r; + EXPECT_EQ("%v4", str.str()); EXPECT_FALSE(r.IsF32()); EXPECT_FALSE(r.IsF16()); diff --git a/src/tint/ir/test_helper.h b/src/tint/ir/test_helper.h index f2bdeb5f27..10694eeb53 100644 --- a/src/tint/ir/test_helper.h +++ b/src/tint/ir/test_helper.h @@ -20,6 +20,7 @@ #include "gtest/gtest.h" #include "src/tint/ir/builder_impl.h" +#include "src/tint/ir/disassembler.h" #include "src/tint/program_builder.h" namespace tint::ir {