[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 <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
dan sinclair 2022-11-23 02:11:52 +00:00 committed by Dawn LUCI CQ
parent f6fcf0a3ef
commit 669e15e139
18 changed files with 1540 additions and 180 deletions

View File

@ -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
)

View File

@ -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);

View File

@ -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<Block, FlowNode> {
/// The node this block branches too.
const FlowNode* branch_target = nullptr;
/// The operations in the block
utils::Vector<Op, 16> ops;
};
} // namespace tint::ir

View File

@ -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

View File

@ -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

View File

@ -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<Register> 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<Register> 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<Register> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
return tint::Switch( //
lit,

View File

@ -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<Register> EmitBinary(const ast::BinaryExpression* expr);
/// Emits a literal expression
/// @param lit the literal to emit
/// @returns true if successful, false otherwise

View File

@ -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

View File

@ -26,33 +26,6 @@
#include "src/tint/program.h"
namespace tint::ir {
namespace {
class ScopedStopNode {
public:
ScopedStopNode(std::unordered_set<const FlowNode*>* stop_nodes, const FlowNode* node)
: stop_nodes_(stop_nodes), node_(node) {
stop_nodes_->insert(node_);
}
~ScopedStopNode() { stop_nodes_->erase(node_); }
private:
std::unordered_set<const FlowNode*>* 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<const FlowNode*> visited;
std::unordered_set<const FlowNode*> stop_nodes;
uint32_t indent_size = 0;
std::function<std::ostream&(void)> indent = [&]() -> std::ostream& {
for (uint32_t i = 0; i < indent_size; i++) {
out << " ";
}
return out;
};
std::function<void(const FlowNode*)> 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

154
src/tint/ir/disassembler.cc Normal file
View File

@ -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<const FlowNode*>* stop_nodes_, const FlowNode* node)
: stop_nodes__(stop_nodes_), node_(node) {
stop_nodes__->insert(node_);
}
~ScopedStopNode() { stop_nodes__->erase(node_); }
private:
std::unordered_set<const FlowNode*>* 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

View File

@ -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 <sstream>
#include <string>
#include <unordered_set>
#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<const FlowNode*> visited_;
std::unordered_set<const FlowNode*> stop_nodes_;
uint32_t indent_size_ = 0;
};
} // namespace tint::ir
#endif // SRC_TINT_IR_DISASSEMBLER_H_

105
src/tint/ir/op.cc Normal file
View File

@ -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

113
src/tint/ir/op.h Normal file
View File

@ -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 <ostream>
#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<Register, 2> args_;
};
std::ostream& operator<<(std::ostream& out, const Op&);
} // namespace tint::ir
#endif // SRC_TINT_IR_OP_H_

494
src/tint/ir/op_test.cc Normal file
View File

@ -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 <sstream>
#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

View File

@ -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);
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 Kind::kVar:
return "%v" + std::to_string(AsVarData().id);
case Kind::kBool:
return AsBool() ? "true" : "false";
case Kind::kUninitialized:
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

View File

@ -15,7 +15,7 @@
#ifndef SRC_TINT_IR_REGISTER_H_
#define SRC_TINT_IR_REGISTER_H_
#include <string>
#include <ostream>
#include <variant>
#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<f32>(data_); }
@ -132,36 +155,15 @@ class Register {
/// @note, must only be called if `IsBool()` is true
bool AsBool() const { return std::get<bool>(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<Id, f32, f16, u32, i32, VarData, bool> data_;
};
std::ostream& operator<<(std::ostream& out, const Register& r);
} // namespace tint::ir
#endif // SRC_TINT_IR_REGISTER_H_

View File

@ -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 <sstream>
#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());

View File

@ -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 {