[ir] Renaming Register and Op

This CL renames Register to Value and Op to Instruction.

Bug: tint:1718
Change-Id: Ided22c524213235369aae366a678d8058a516b60
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112041
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index a8644b7..06aa483 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -666,18 +666,18 @@
     ir/function.h
     ir/if.cc
     ir/if.h
+    ir/instruction.cc
+    ir/instruction.h
     ir/loop.cc
     ir/loop.h
     ir/module.cc
     ir/module.h
-    ir/op.cc
-    ir/op.h
-    ir/register.cc
-    ir/register.h
     ir/switch.cc
     ir/switch.h
     ir/terminator.cc
     ir/terminator.h
+    ir/value.cc
+    ir/value.h
   )
 endif()
 
@@ -1341,9 +1341,9 @@
   if (${TINT_BUILD_IR})
     list(APPEND TINT_TEST_SRCS
       ir/builder_impl_test.cc
-      ir/op_test.cc
-      ir/register_test.cc
+      ir/instruction_test.cc
       ir/test_helper.h
+      ir/value_test.cc
     )
   endif()
 
diff --git a/src/tint/ir/block.h b/src/tint/ir/block.h
index 850c13c..d27fdf4 100644
--- a/src/tint/ir/block.h
+++ b/src/tint/ir/block.h
@@ -16,7 +16,7 @@
 #define SRC_TINT_IR_BLOCK_H_
 
 #include "src/tint/ir/flow_node.h"
-#include "src/tint/ir/op.h"
+#include "src/tint/ir/instruction.h"
 #include "src/tint/utils/vector.h"
 
 namespace tint::ir {
@@ -33,8 +33,8 @@
     /// The node this block branches too.
     const FlowNode* branch_target = nullptr;
 
-    /// The operations in the block
-    utils::Vector<Op, 16> ops;
+    /// The instructions in the block
+    utils::Vector<Instruction, 16> instructions;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 57a40a7..0403807 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -93,84 +93,84 @@
     to->inbound_branches.Push(from);
 }
 
-Register::Id Builder::AllocateRegister() {
-    return next_register_id++;
+Value::Id Builder::AllocateValue() {
+    return next_value_id++;
 }
 
-Op Builder::CreateOp(Op::Kind kind, Register lhs, Register rhs) {
-    return Op(kind, Register(AllocateRegister()), lhs, rhs);
+Instruction Builder::CreateInstruction(Instruction::Kind kind, Value lhs, Value rhs) {
+    return Instruction(kind, Value(AllocateValue()), lhs, rhs);
 }
 
-Op Builder::And(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kAnd, lhs, rhs);
+Instruction Builder::And(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kAnd, lhs, rhs);
 }
 
-Op Builder::Or(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kOr, lhs, rhs);
+Instruction Builder::Or(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kOr, lhs, rhs);
 }
 
-Op Builder::Xor(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kXor, lhs, rhs);
+Instruction Builder::Xor(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kXor, lhs, rhs);
 }
 
-Op Builder::LogicalAnd(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kLogicalAnd, lhs, rhs);
+Instruction Builder::LogicalAnd(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kLogicalAnd, lhs, rhs);
 }
 
-Op Builder::LogicalOr(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kLogicalOr, lhs, rhs);
+Instruction Builder::LogicalOr(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kLogicalOr, lhs, rhs);
 }
 
-Op Builder::Equal(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kEqual, lhs, rhs);
+Instruction Builder::Equal(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kEqual, lhs, rhs);
 }
 
-Op Builder::NotEqual(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kNotEqual, lhs, rhs);
+Instruction Builder::NotEqual(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kNotEqual, lhs, rhs);
 }
 
-Op Builder::LessThan(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kLessThan, lhs, rhs);
+Instruction Builder::LessThan(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kLessThan, lhs, rhs);
 }
 
-Op Builder::GreaterThan(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kGreaterThan, lhs, rhs);
+Instruction Builder::GreaterThan(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kGreaterThan, lhs, rhs);
 }
 
-Op Builder::LessThanEqual(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kLessThanEqual, lhs, rhs);
+Instruction Builder::LessThanEqual(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kLessThanEqual, lhs, rhs);
 }
 
-Op Builder::GreaterThanEqual(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kGreaterThanEqual, lhs, rhs);
+Instruction Builder::GreaterThanEqual(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kGreaterThanEqual, lhs, rhs);
 }
 
-Op Builder::ShiftLeft(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kShiftLeft, lhs, rhs);
+Instruction Builder::ShiftLeft(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kShiftLeft, lhs, rhs);
 }
 
-Op Builder::ShiftRight(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kShiftRight, lhs, rhs);
+Instruction Builder::ShiftRight(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kShiftRight, lhs, rhs);
 }
 
-Op Builder::Add(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kAdd, lhs, rhs);
+Instruction Builder::Add(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kAdd, lhs, rhs);
 }
 
-Op Builder::Subtract(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kSubtract, lhs, rhs);
+Instruction Builder::Subtract(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kSubtract, lhs, rhs);
 }
 
-Op Builder::Multiply(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kMultiply, lhs, rhs);
+Instruction Builder::Multiply(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kMultiply, lhs, rhs);
 }
 
-Op Builder::Divide(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kDivide, lhs, rhs);
+Instruction Builder::Divide(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kDivide, lhs, rhs);
 }
 
-Op Builder::Modulo(Register lhs, Register rhs) {
-    return CreateOp(Op::Kind::kModulo, lhs, rhs);
+Instruction Builder::Modulo(Value lhs, Value rhs) {
+    return CreateInstruction(Instruction::Kind::kModulo, lhs, rhs);
 }
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index 091aac8..6776d35 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -17,12 +17,12 @@
 
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/if.h"
+#include "src/tint/ir/instruction.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"
+#include "src/tint/ir/value.h"
 
 // Forward Declarations
 namespace tint {
@@ -88,124 +88,124 @@
     /// @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);
+    Instruction CreateInstruction(Instruction::Kind kind, Value lhs, Value 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);
+    Instruction And(Value lhs, Value 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);
+    Instruction Or(Value lhs, Value 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);
+    Instruction Xor(Value lhs, Value 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);
+    Instruction LogicalAnd(Value lhs, Value 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);
+    Instruction LogicalOr(Value lhs, Value 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);
+    Instruction Equal(Value lhs, Value 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);
+    Instruction NotEqual(Value lhs, Value 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);
+    Instruction LessThan(Value lhs, Value 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);
+    Instruction GreaterThan(Value lhs, Value 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);
+    Instruction LessThanEqual(Value lhs, Value 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);
+    Instruction GreaterThanEqual(Value lhs, Value 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);
+    Instruction ShiftLeft(Value lhs, Value 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);
+    Instruction ShiftRight(Value lhs, Value 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);
+    Instruction Add(Value lhs, Value 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);
+    Instruction Subtract(Value lhs, Value 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);
+    Instruction Multiply(Value lhs, Value 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);
+    Instruction Divide(Value lhs, Value 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);
+    Instruction Modulo(Value lhs, Value rhs);
 
-    /// @returns a unique register id
-    Register::Id AllocateRegister();
+    /// @returns a unique Value id
+    Value::Id AllocateValue();
 
     /// The IR module.
     Module ir;
 
-    /// The next register number to allocate
-    Register::Id next_register_id = 1;
+    /// The next Value number to allocate
+    Value::Id next_value_id = 1;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 06ed837..3af54f9 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -516,7 +516,7 @@
     return true;
 }
 
-utils::Result<Register> BuilderImpl::EmitExpression(const ast::Expression* expr) {
+utils::Result<Value> BuilderImpl::EmitExpression(const ast::Expression* expr) {
     return tint::Switch(
         expr,
         // [&](const ast::IndexAccessorExpression* a) { return EmitIndexAccessor(a); },
@@ -551,7 +551,7 @@
         });
 }
 
-utils::Result<Register> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
+utils::Result<Value> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
     auto lhs = EmitExpression(expr->lhs);
     if (!lhs) {
         return utils::Failure;
@@ -562,89 +562,87 @@
         return utils::Failure;
     }
 
-    Op op;
+    Instruction instr;
     switch (expr->op) {
         case ast::BinaryOp::kAnd:
-            op = builder.And(lhs.Get(), rhs.Get());
+            instr = builder.And(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kOr:
-            op = builder.Or(lhs.Get(), rhs.Get());
+            instr = builder.Or(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kXor:
-            op = builder.Xor(lhs.Get(), rhs.Get());
+            instr = builder.Xor(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLogicalAnd:
-            op = builder.LogicalAnd(lhs.Get(), rhs.Get());
+            instr = builder.LogicalAnd(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLogicalOr:
-            op = builder.LogicalOr(lhs.Get(), rhs.Get());
+            instr = builder.LogicalOr(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kEqual:
-            op = builder.Equal(lhs.Get(), rhs.Get());
+            instr = builder.Equal(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kNotEqual:
-            op = builder.NotEqual(lhs.Get(), rhs.Get());
+            instr = builder.NotEqual(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLessThan:
-            op = builder.LessThan(lhs.Get(), rhs.Get());
+            instr = builder.LessThan(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kGreaterThan:
-            op = builder.GreaterThan(lhs.Get(), rhs.Get());
+            instr = builder.GreaterThan(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLessThanEqual:
-            op = builder.LessThanEqual(lhs.Get(), rhs.Get());
+            instr = builder.LessThanEqual(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kGreaterThanEqual:
-            op = builder.GreaterThanEqual(lhs.Get(), rhs.Get());
+            instr = builder.GreaterThanEqual(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kShiftLeft:
-            op = builder.ShiftLeft(lhs.Get(), rhs.Get());
+            instr = builder.ShiftLeft(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kShiftRight:
-            op = builder.ShiftRight(lhs.Get(), rhs.Get());
+            instr = builder.ShiftRight(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kAdd:
-            op = builder.Add(lhs.Get(), rhs.Get());
+            instr = builder.Add(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kSubtract:
-            op = builder.Subtract(lhs.Get(), rhs.Get());
+            instr = builder.Subtract(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kMultiply:
-            op = builder.Multiply(lhs.Get(), rhs.Get());
+            instr = builder.Multiply(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kDivide:
-            op = builder.Divide(lhs.Get(), rhs.Get());
+            instr = builder.Divide(lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kModulo:
-            op = builder.Modulo(lhs.Get(), rhs.Get());
+            instr = 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);
+    auto result = instr.Result();
+    current_flow_block->instructions.Push(instr);
     return result;
 }
 
-utils::Result<Register> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
+utils::Result<Value> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
     return tint::Switch(  //
         lit,
-        [&](const ast::BoolLiteralExpression* l) {
-            return utils::Result<Register>{Register(l->value)};
-        },
+        [&](const ast::BoolLiteralExpression* l) { return utils::Result<Value>{Value(l->value)}; },
         [&](const ast::FloatLiteralExpression* l) {
             if (l->suffix == ast::FloatLiteralExpression::Suffix::kF) {
-                return utils::Result<Register>{Register(f32(static_cast<float>(l->value)))};
+                return utils::Result<Value>{Value(f32(static_cast<float>(l->value)))};
             }
-            return utils::Result<Register>{Register(f16(static_cast<float>(l->value)))};
+            return utils::Result<Value>{Value(f16(static_cast<float>(l->value)))};
         },
         [&](const ast::IntLiteralExpression* l) {
             if (l->suffix == ast::IntLiteralExpression::Suffix::kI) {
-                return utils::Result<Register>{Register(i32(l->value))};
+                return utils::Result<Value>{Value(i32(l->value))};
             }
-            return utils::Result<Register>{Register(u32(l->value))};
+            return utils::Result<Value>{Value(u32(l->value))};
         },
         [&](Default) {
             diagnostics_.add_warning(tint::diag::System::IR,
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index ef5238e..60eda73 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -23,7 +23,7 @@
 #include "src/tint/ir/builder.h"
 #include "src/tint/ir/flow_node.h"
 #include "src/tint/ir/module.h"
-#include "src/tint/ir/register.h"
+#include "src/tint/ir/value.h"
 #include "src/tint/utils/result.h"
 
 // Forward Declarations
@@ -140,7 +140,7 @@
     /// Emits an expression
     /// @param expr the expression to emit
     /// @returns true if successful, false otherwise
-    utils::Result<Register> EmitExpression(const ast::Expression* expr);
+    utils::Result<Value> EmitExpression(const ast::Expression* expr);
 
     /// Emits a variable
     /// @param var the variable to emit
@@ -149,13 +149,13 @@
 
     /// 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);
+    /// @returns the value storing the result if successful, utils::Failure otherwise
+    utils::Result<Value> EmitBinary(const ast::BinaryExpression* expr);
 
     /// Emits a literal expression
     /// @param lit the literal to emit
     /// @returns true if successful, false otherwise
-    utils::Result<Register> EmitLiteral(const ast::LiteralExpression* lit);
+    utils::Result<Value> EmitLiteral(const ast::LiteralExpression* lit);
 
     /// Emits a type
     /// @param ty the type to emit
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 4a1efd5..6c8e496 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -101,9 +101,9 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto op = flow->condition;
-    ASSERT_TRUE(op.IsBool());
-    EXPECT_TRUE(op.AsBool());
+    auto instr = flow->condition;
+    ASSERT_TRUE(instr.IsBool());
+    EXPECT_TRUE(instr.AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, IfStatement_TrueReturns) {
@@ -502,9 +502,9 @@
     EXPECT_EQ(loop_flow->merge_target->branch_target, nullptr);
 
     // Check condition
-    auto op = if_flow->condition;
-    ASSERT_TRUE(op.IsBool());
-    EXPECT_TRUE(op.AsBool());
+    auto instr = if_flow->condition;
+    ASSERT_TRUE(instr.IsBool());
+    EXPECT_TRUE(instr.AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, Loop_WithOnlyReturn) {
@@ -947,9 +947,9 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto op = if_flow->condition;
-    ASSERT_TRUE(op.IsBool());
-    EXPECT_FALSE(op.AsBool());
+    auto instr = if_flow->condition;
+    ASSERT_TRUE(instr.IsBool());
+    EXPECT_FALSE(instr.AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, While_Return) {
@@ -1071,9 +1071,9 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto op = if_flow->condition;
-    ASSERT_TRUE(op.IsBool());
-    EXPECT_FALSE(op.AsBool());
+    auto instr = if_flow->condition;
+    ASSERT_TRUE(instr.IsBool());
+    EXPECT_FALSE(instr.AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, For_NoInitCondOrContinuing) {
@@ -1171,9 +1171,9 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto op = flow->condition;
-    ASSERT_TRUE(op.IsI32());
-    EXPECT_EQ(1_i, op.AsI32());
+    auto instr = flow->condition;
+    ASSERT_TRUE(instr.IsI32());
+    EXPECT_EQ(1_i, instr.AsI32());
 }
 
 TEST_F(IR_BuilderImplTest, Switch_OnlyDefault) {
@@ -1402,7 +1402,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 + 4
 )");
 }
@@ -1413,7 +1413,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 - 4
 )");
 }
@@ -1424,7 +1424,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 * 4
 )");
 }
@@ -1435,7 +1435,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 / 4
 )");
 }
@@ -1446,7 +1446,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 % 4
 )");
 }
@@ -1457,7 +1457,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 & 4
 )");
 }
@@ -1468,7 +1468,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 | 4
 )");
 }
@@ -1479,7 +1479,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 ^ 4
 )");
 }
@@ -1490,7 +1490,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 && 4
 )");
 }
@@ -1501,7 +1501,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 || 4
 )");
 }
@@ -1512,7 +1512,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 == 4
 )");
 }
@@ -1523,7 +1523,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 != 4
 )");
 }
@@ -1534,7 +1534,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 < 4
 )");
 }
@@ -1545,7 +1545,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 > 4
 )");
 }
@@ -1556,7 +1556,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 <= 4
 )");
 }
@@ -1567,7 +1567,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 >= 4
 )");
 }
@@ -1578,7 +1578,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 << 4
 )");
 }
@@ -1589,7 +1589,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 >> 4
 )");
 }
@@ -1601,7 +1601,7 @@
     ASSERT_TRUE(r);
 
     Disassembler d;
-    d.EmitBlockOps(b.current_flow_block);
+    d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 >> 4
 %2 = %1 + 9
 %3 = 1 < %2
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index a648d68..f98950f 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -61,9 +61,9 @@
     return out_;
 }
 
-void Disassembler::EmitBlockOps(const Block* b) {
-    for (const auto& op : b->ops) {
-        out_ << op << std::endl;
+void Disassembler::EmitBlockInstructions(const Block* b) {
+    for (const auto& instr : b->instructions) {
+        out_ << instr << std::endl;
     }
 }
 
@@ -87,7 +87,7 @@
         },
         [&](const ir::Block* b) {
             Indent() << "Block" << std::endl;
-            EmitBlockOps(b);
+            EmitBlockInstructions(b);
             Walk(b->branch_target);
         },
         [&](const ir::Switch* s) {
diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h
index 9cb8993..e24524a 100644
--- a/src/tint/ir/disassembler.h
+++ b/src/tint/ir/disassembler.h
@@ -36,9 +36,9 @@
     /// @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);
+    /// Writes the block instructions to the stream
+    /// @param b the block containing the instructions
+    void EmitBlockInstructions(const Block* b);
 
     /// @returns the string representation
     std::string AsString() const { return out_.str(); }
diff --git a/src/tint/ir/if.h b/src/tint/ir/if.h
index 109b990..4b2969d 100644
--- a/src/tint/ir/if.h
+++ b/src/tint/ir/if.h
@@ -17,7 +17,7 @@
 
 #include "src/tint/ast/if_statement.h"
 #include "src/tint/ir/flow_node.h"
-#include "src/tint/ir/register.h"
+#include "src/tint/ir/value.h"
 
 // Forward declarations
 namespace tint::ir {
@@ -44,8 +44,8 @@
     /// An block to reconvert the true/false barnches. The block always exists, but there maybe no
     /// branches into it. (e.g. if both branches `return`)
     Block* merge_target = nullptr;
-    /// Register holding the condition result
-    Register condition;
+    /// Value holding the condition result
+    Value condition;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/instruction.cc b/src/tint/ir/instruction.cc
new file mode 100644
index 0000000..9460f39
--- /dev/null
+++ b/src/tint/ir/instruction.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/instruction.h"
+
+namespace tint::ir {
+
+Instruction::Instruction() {}
+
+Instruction::Instruction(Kind kind, Value result, Value lhs, Value rhs)
+    : kind_(kind), result_(result), args_({lhs, rhs}) {}
+
+Instruction::Instruction(const Instruction&) = default;
+
+Instruction::Instruction(Instruction&& instr) = default;
+
+Instruction::~Instruction() = default;
+
+Instruction& Instruction::operator=(const Instruction& instr) = default;
+
+Instruction& Instruction::operator=(Instruction&& instr) = default;
+
+std::ostream& operator<<(std::ostream& out, const Instruction& instr) {
+    out << instr.Result() << " = ";
+    if (instr.HasLHS()) {
+        out << instr.LHS();
+    }
+    out << " ";
+
+    switch (instr.GetKind()) {
+        case Instruction::Kind::kAdd:
+            out << "+";
+            break;
+        case Instruction::Kind::kSubtract:
+            out << "-";
+            break;
+        case Instruction::Kind::kMultiply:
+            out << "*";
+            break;
+        case Instruction::Kind::kDivide:
+            out << "/";
+            break;
+        case Instruction::Kind::kModulo:
+            out << "%";
+            break;
+        case Instruction::Kind::kAnd:
+            out << "&";
+            break;
+        case Instruction::Kind::kOr:
+            out << "|";
+            break;
+        case Instruction::Kind::kXor:
+            out << "^";
+            break;
+        case Instruction::Kind::kLogicalAnd:
+            out << "&&";
+            break;
+        case Instruction::Kind::kLogicalOr:
+            out << "||";
+            break;
+        case Instruction::Kind::kEqual:
+            out << "==";
+            break;
+        case Instruction::Kind::kNotEqual:
+            out << "!=";
+            break;
+        case Instruction::Kind::kLessThan:
+            out << "<";
+            break;
+        case Instruction::Kind::kGreaterThan:
+            out << ">";
+            break;
+        case Instruction::Kind::kLessThanEqual:
+            out << "<=";
+            break;
+        case Instruction::Kind::kGreaterThanEqual:
+            out << ">=";
+            break;
+        case Instruction::Kind::kShiftLeft:
+            out << "<<";
+            break;
+        case Instruction::Kind::kShiftRight:
+            out << ">>";
+            break;
+    }
+
+    if (instr.HasRHS()) {
+        out << " " << instr.RHS();
+    }
+
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
new file mode 100644
index 0000000..b62300c
--- /dev/null
+++ b/src/tint/ir/instruction.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_INSTRUCTION_H_
+#define SRC_TINT_IR_INSTRUCTION_H_
+
+#include <ostream>
+
+#include "src/tint/ir/value.h"
+#include "src/tint/utils/vector.h"
+
+namespace tint::ir {
+
+/// An instruction in the IR.
+class Instruction {
+  public:
+    /// The kind of instruction.
+    enum class Kind {
+        kAdd,
+        kSubtract,
+        kMultiply,
+        kDivide,
+        kModulo,
+
+        kAnd,
+        kOr,
+        kXor,
+
+        kLogicalAnd,
+        kLogicalOr,
+
+        kEqual,
+        kNotEqual,
+        kLessThan,
+        kGreaterThan,
+        kLessThanEqual,
+        kGreaterThanEqual,
+
+        kShiftLeft,
+        kShiftRight
+    };
+
+    /// Constructor
+    Instruction();
+    /// Constructor
+    /// @param kind the kind of instruction
+    /// @param result the result value
+    /// @param lhs the lhs of the instruction
+    /// @param rhs the rhs of the instruction
+    Instruction(Kind kind, Value result, Value lhs, Value rhs);
+    /// Copy constructor
+    /// @param instr the instruction to copy from
+    Instruction(const Instruction& instr);
+    /// Move constructor
+    /// @param instr the instruction to move from
+    Instruction(Instruction&& instr);
+    /// Destructor
+    ~Instruction();
+
+    /// Copy assign
+    /// @param instr the instruction to copy from
+    /// @returns a reference to this
+    Instruction& operator=(const Instruction& instr);
+    /// Move assign
+    /// @param instr the instruction to move from
+    /// @returns a reference to this
+    Instruction& operator=(Instruction&& instr);
+
+    /// @returns the kind of instruction
+    Kind GetKind() const { return kind_; }
+
+    /// @returns the result value for the instruction
+    const Value& Result() const { return result_; }
+
+    /// @returns true if the instruction has a LHS
+    bool HasLHS() const { return args_.Length() >= 1; }
+    /// @returns the left-hand-side value for the instruction
+    const Value& LHS() const {
+        TINT_ASSERT(IR, HasLHS());
+        return args_[0];
+    }
+
+    /// @returns true if the instruction has a RHS
+    bool HasRHS() const { return args_.Length() >= 2; }
+    /// @returns the right-hand-side value for the instruction
+    const Value& RHS() const {
+        TINT_ASSERT(IR, HasRHS());
+        return args_[1];
+    }
+
+  private:
+    Kind kind_;
+
+    Value result_;
+    utils::Vector<Value, 2> args_;
+};
+
+std::ostream& operator<<(std::ostream& out, const Instruction&);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_INSTRUCTION_H_
diff --git a/src/tint/ir/instruction_test.cc b/src/tint/ir/instruction_test.cc
new file mode 100644
index 0000000..8fdd541
--- /dev/null
+++ b/src/tint/ir/instruction_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 <sstream>
+
+#include "src/tint/ir/instruction.h"
+#include "src/tint/ir/test_helper.h"
+
+namespace tint::ir {
+namespace {
+
+using IR_InstructionTest = TestHelper;
+
+TEST_F(IR_InstructionTest, CreateAnd) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.And(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kAnd);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 & 2");
+}
+
+TEST_F(IR_InstructionTest, CreateOr) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Or(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kOr);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 | 2");
+}
+
+TEST_F(IR_InstructionTest, CreateXor) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Xor(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kXor);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLogicalAnd) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.LogicalAnd(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLogicalAnd);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 && 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLogicalOr) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.LogicalOr(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLogicalOr);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 || 2");
+}
+
+TEST_F(IR_InstructionTest, CreateEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Equal(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kEqual);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 == 2");
+}
+
+TEST_F(IR_InstructionTest, CreateNotEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.NotEqual(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kNotEqual);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 != 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLessThan) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.LessThan(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLessThan);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 < 2");
+}
+
+TEST_F(IR_InstructionTest, CreateGreaterThan) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.GreaterThan(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kGreaterThan);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 > 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLessThanEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.LessThanEqual(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLessThanEqual);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 <= 2");
+}
+
+TEST_F(IR_InstructionTest, CreateGreaterThanEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.GreaterThanEqual(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kGreaterThanEqual);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 >= 2");
+}
+
+TEST_F(IR_InstructionTest, CreateShiftLeft) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.ShiftLeft(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kShiftLeft);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 << 2");
+}
+
+TEST_F(IR_InstructionTest, CreateShiftRight) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.ShiftRight(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kShiftRight);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 >> 2");
+}
+
+TEST_F(IR_InstructionTest, CreateAdd) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Add(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kAdd);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 + 2");
+}
+
+TEST_F(IR_InstructionTest, CreateSubtract) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Subtract(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kSubtract);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 - 2");
+}
+
+TEST_F(IR_InstructionTest, CreateMultiply) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Multiply(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kMultiply);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 * 2");
+}
+
+TEST_F(IR_InstructionTest, CreateDivide) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Divide(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kDivide);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 / 2");
+}
+
+TEST_F(IR_InstructionTest, CreateModulo) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_value_id = Value::Id(42);
+    auto instr = b.builder.Modulo(Value(i32(4)), Value(i32(2)));
+
+    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kModulo);
+
+    ASSERT_TRUE(instr.Result().IsTemp());
+    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+
+    ASSERT_TRUE(instr.HasLHS());
+    auto& lhs = instr.LHS();
+    ASSERT_TRUE(lhs.IsI32());
+    EXPECT_EQ(i32(4), lhs.AsI32());
+
+    ASSERT_TRUE(instr.HasRHS());
+    auto& rhs = instr.RHS();
+    ASSERT_TRUE(rhs.IsI32());
+    EXPECT_EQ(i32(2), rhs.AsI32());
+
+    std::stringstream str;
+    str << instr;
+    EXPECT_EQ(str.str(), "%42 = 4 % 2");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/op.cc b/src/tint/ir/op.cc
deleted file mode 100644
index 3e6d9936..0000000
--- a/src/tint/ir/op.cc
+++ /dev/null
@@ -1,105 +0,0 @@
-// 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
deleted file mode 100644
index a9fc78f..0000000
--- a/src/tint/ir/op.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// 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_
diff --git a/src/tint/ir/op_test.cc b/src/tint/ir/op_test.cc
deleted file mode 100644
index 01604d1..0000000
--- a/src/tint/ir/op_test.cc
+++ /dev/null
@@ -1,494 +0,0 @@
-// 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
diff --git a/src/tint/ir/register.cc b/src/tint/ir/register.cc
deleted file mode 100644
index f3caa76..0000000
--- a/src/tint/ir/register.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-// 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/register.h"
-
-namespace tint::ir {
-
-Register::Register() : kind_(Kind::kUninitialized), data_(Id(0)) {}
-
-Register::Register(Id id) : kind_(Kind::kTemp), data_(id) {}
-
-Register::Register(f32 f) : kind_(Kind::kF32), data_(f) {}
-
-Register::Register(f16 f) : kind_(Kind::kF16), data_(f) {}
-
-Register::Register(u32 u) : kind_(Kind::kU32), data_(u) {}
-
-Register::Register(i32 i) : kind_(Kind::kI32), data_(i) {}
-
-Register::Register(bool b) : kind_(Kind::kBool), data_(b) {}
-
-Register::Register(Symbol s, Id id) : kind_(Kind::kVar), data_(VarData{s, id}) {}
-
-Register::~Register() = default;
-
-Register::Register(const Register& o) = default;
-
-Register::Register(Register&& o) = default;
-
-Register& Register::operator=(const Register& o) = default;
-
-Register& Register::operator=(Register&& o) = default;
-
-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 out;
-}
-
-}  // namespace tint::ir
diff --git a/src/tint/ir/register.h b/src/tint/ir/register.h
deleted file mode 100644
index d1c41eb..0000000
--- a/src/tint/ir/register.h
+++ /dev/null
@@ -1,169 +0,0 @@
-// 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_REGISTER_H_
-#define SRC_TINT_IR_REGISTER_H_
-
-#include <ostream>
-#include <variant>
-
-#include "src/tint/number.h"
-#include "src/tint/symbol.h"
-
-namespace tint::ir {
-
-/// Register in the IR. The register can be one of several types these include, but aren't limited
-/// to, `f32`, `u32`, `temp`, `var`. The type of the register determines the type of data stored
-/// in the register.
-class Register {
-  public:
-    /// 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`
-    /// will be incremented.
-    struct VarData {
-        /// The symbol for the variable
-        Symbol sym;
-        /// The id for the variable.
-        Id id;
-        // TODO(dsinclair): Should var type data be stored here along side the variable info?
-    };
-
-    /// Constructor
-    /// Creates a uninitialized register
-    Register();
-
-    /// Constructor
-    /// @param id the id for the register
-    explicit Register(Id id);
-
-    /// Constructor
-    /// @param s the symbol for the register
-    /// @param id the id for the register
-    Register(Symbol s, Id id);
-
-    /// Constructor
-    /// @param b the `bool` value to store in the register
-    explicit Register(bool b);
-
-    /// Constructor
-    /// @param f the `f32` value to store in the register
-    explicit Register(f32 f);
-
-    /// Constructor
-    /// @param f the `f16` value to store in the register
-    explicit Register(f16 f);
-
-    /// Constructor
-    /// @param u the `u32` value to store in the register
-    explicit Register(u32 u);
-
-    /// Constructor
-    /// @param i the `i32` value to store in the register
-    explicit Register(i32 i);
-
-    /// Destructor
-    ~Register();
-
-    /// Copy constructor
-    /// @param o the register to copy from
-    Register(const Register& o);
-    /// Move constructor
-    /// @param o the register to move from
-    Register(Register&& o);
-
-    /// Copy assign
-    /// @param o the register to copy from
-    /// @returns this
-    Register& operator=(const Register& o);
-    /// Move assign
-    /// @param o the register to move from
-    /// @returns this
-    Register& operator=(Register&& o);
-
-    /// @returns true if this is a temporary register
-    bool IsTemp() const { return kind_ == Kind::kTemp; }
-    /// @returns true if this is a f32 register
-    bool IsF32() const { return kind_ == Kind::kF32; }
-    /// @returns true if this is a f16 register
-    bool IsF16() const { return kind_ == Kind::kF16; }
-    /// @returns true if this is an i32 register
-    bool IsI32() const { return kind_ == Kind::kI32; }
-    /// @returns true if this is a u32 register
-    bool IsU32() const { return kind_ == Kind::kU32; }
-    /// @returns true if this is a var register
-    bool IsVar() const { return kind_ == Kind::kVar; }
-    /// @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_); }
-    /// @returns the register data as a `f16`.
-    /// @note, must only be called if `IsF16()` is true
-    f16 AsF16() const { return std::get<f16>(data_); }
-    /// @returns the register data as an `i32`.
-    /// @note, must only be called if `IsI32()` is true
-    i32 AsI32() const { return std::get<i32>(data_); }
-    /// @returns the register data as a `u32`.
-    /// @note, must only be called if `IsU32()` is true
-    u32 AsU32() const { return std::get<u32>(data_); }
-    /// @returns the register data as an `Id`.
-    /// @note, must only be called if `IsTemp()` is true
-    Id AsId() const { return std::get<Id>(data_); }
-    /// @returns the register data as a `VarData` structure.
-    /// @note, must only be called if `IsVar()` is true
-    VarData AsVarData() const { return std::get<VarData>(data_); }
-    /// @returns the register data as a `bool`.
-    /// @note, must only be called if `IsBool()` is true
-    bool AsBool() const { return std::get<bool>(data_); }
-
-  private:
-    /// 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_
diff --git a/src/tint/ir/register_test.cc b/src/tint/ir/register_test.cc
deleted file mode 100644
index fb0194f..0000000
--- a/src/tint/ir/register_test.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-// 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/register.h"
-#include "src/tint/ir/test_helper.h"
-
-namespace tint::ir {
-namespace {
-
-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());
-
-    str << r;
-    EXPECT_EQ("1.200000", str.str());
-
-    EXPECT_TRUE(r.IsF32());
-    EXPECT_FALSE(r.IsF16());
-    EXPECT_FALSE(r.IsI32());
-    EXPECT_FALSE(r.IsU32());
-    EXPECT_FALSE(r.IsTemp());
-    EXPECT_FALSE(r.IsVar());
-    EXPECT_FALSE(r.IsBool());
-}
-
-TEST_F(IR_RegisterTest, f16) {
-    std::stringstream str;
-
-    Register r(1.1_h);
-    EXPECT_EQ(1.1_h, r.AsF16());
-
-    str << r;
-    EXPECT_EQ("1.099609", str.str());
-
-    EXPECT_FALSE(r.IsF32());
-    EXPECT_TRUE(r.IsF16());
-    EXPECT_FALSE(r.IsI32());
-    EXPECT_FALSE(r.IsU32());
-    EXPECT_FALSE(r.IsTemp());
-    EXPECT_FALSE(r.IsVar());
-    EXPECT_FALSE(r.IsBool());
-}
-
-TEST_F(IR_RegisterTest, i32) {
-    std::stringstream str;
-
-    Register r(1_i);
-    EXPECT_EQ(1_i, r.AsI32());
-
-    str << r;
-    EXPECT_EQ("1", str.str());
-
-    EXPECT_FALSE(r.IsF32());
-    EXPECT_FALSE(r.IsF16());
-    EXPECT_TRUE(r.IsI32());
-    EXPECT_FALSE(r.IsU32());
-    EXPECT_FALSE(r.IsTemp());
-    EXPECT_FALSE(r.IsVar());
-    EXPECT_FALSE(r.IsBool());
-}
-
-TEST_F(IR_RegisterTest, u32) {
-    std::stringstream str;
-
-    Register r(2_u);
-    EXPECT_EQ(2_u, r.AsU32());
-
-    str << r;
-    EXPECT_EQ("2", str.str());
-
-    EXPECT_FALSE(r.IsF32());
-    EXPECT_FALSE(r.IsF16());
-    EXPECT_FALSE(r.IsI32());
-    EXPECT_TRUE(r.IsU32());
-    EXPECT_FALSE(r.IsTemp());
-    EXPECT_FALSE(r.IsVar());
-    EXPECT_FALSE(r.IsBool());
-}
-
-TEST_F(IR_RegisterTest, id) {
-    std::stringstream str;
-
-    Register r(Register::Id(4));
-    EXPECT_EQ(4u, r.AsId());
-
-    str << r;
-    EXPECT_EQ("%4", str.str());
-
-    EXPECT_FALSE(r.IsF32());
-    EXPECT_FALSE(r.IsF16());
-    EXPECT_FALSE(r.IsI32());
-    EXPECT_FALSE(r.IsU32());
-    EXPECT_TRUE(r.IsTemp());
-    EXPECT_FALSE(r.IsVar());
-    EXPECT_FALSE(r.IsBool());
-}
-
-TEST_F(IR_RegisterTest, bool) {
-    std::stringstream str;
-
-    Register r(false);
-    EXPECT_FALSE(r.AsBool());
-
-    str << r;
-    EXPECT_EQ("false", str.str());
-
-    str.str("");
-    r = Register(true);
-    EXPECT_TRUE(r.AsBool());
-
-    str << r;
-    EXPECT_EQ("true", str.str());
-
-    EXPECT_FALSE(r.IsF32());
-    EXPECT_FALSE(r.IsF16());
-    EXPECT_FALSE(r.IsI32());
-    EXPECT_FALSE(r.IsU32());
-    EXPECT_FALSE(r.IsTemp());
-    EXPECT_FALSE(r.IsVar());
-    EXPECT_TRUE(r.IsBool());
-}
-
-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);
-
-    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);
-
-    str << r;
-    EXPECT_EQ("%v4", str.str());
-
-    EXPECT_FALSE(r.IsF32());
-    EXPECT_FALSE(r.IsF16());
-    EXPECT_FALSE(r.IsI32());
-    EXPECT_FALSE(r.IsU32());
-    EXPECT_FALSE(r.IsTemp());
-    EXPECT_TRUE(r.IsVar());
-    EXPECT_FALSE(r.IsBool());
-}
-
-TEST_F(IR_RegisterTest, uninitialized) {
-    Register r;
-
-    EXPECT_FALSE(r.IsF32());
-    EXPECT_FALSE(r.IsF16());
-    EXPECT_FALSE(r.IsI32());
-    EXPECT_FALSE(r.IsU32());
-    EXPECT_FALSE(r.IsTemp());
-    EXPECT_FALSE(r.IsVar());
-    EXPECT_FALSE(r.IsBool());
-}
-
-}  // namespace
-}  // namespace tint::ir
diff --git a/src/tint/ir/switch.h b/src/tint/ir/switch.h
index 73284cf..e526fe8 100644
--- a/src/tint/ir/switch.h
+++ b/src/tint/ir/switch.h
@@ -17,7 +17,7 @@
 
 #include "src/tint/ir/block.h"
 #include "src/tint/ir/flow_node.h"
-#include "src/tint/ir/register.h"
+#include "src/tint/ir/value.h"
 
 // Forward declarations
 namespace tint::ast {
@@ -52,8 +52,8 @@
     /// The switch case statements
     utils::Vector<Case, 4> cases;
 
-    /// Register holding the condition result
-    Register condition;
+    /// Value holding the condition result
+    Value condition;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/value.cc b/src/tint/ir/value.cc
new file mode 100644
index 0000000..bd45a98
--- /dev/null
+++ b/src/tint/ir/value.cc
@@ -0,0 +1,76 @@
+// 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/value.h"
+
+namespace tint::ir {
+
+Value::Value() : kind_(Kind::kUninitialized), data_(Id(0)) {}
+
+Value::Value(Id id) : kind_(Kind::kTemp), data_(id) {}
+
+Value::Value(f32 f) : kind_(Kind::kF32), data_(f) {}
+
+Value::Value(f16 f) : kind_(Kind::kF16), data_(f) {}
+
+Value::Value(u32 u) : kind_(Kind::kU32), data_(u) {}
+
+Value::Value(i32 i) : kind_(Kind::kI32), data_(i) {}
+
+Value::Value(bool b) : kind_(Kind::kBool), data_(b) {}
+
+Value::Value(Symbol s, Id id) : kind_(Kind::kVar), data_(VarData{s, id}) {}
+
+Value::~Value() = default;
+
+Value::Value(const Value& o) = default;
+
+Value::Value(Value&& o) = default;
+
+Value& Value::operator=(const Value& o) = default;
+
+Value& Value::operator=(Value&& o) = default;
+
+std::ostream& operator<<(std::ostream& out, const Value& r) {
+    switch (r.GetKind()) {
+        case Value::Kind::kTemp:
+            out << "%" << std::to_string(r.AsId());
+            break;
+        case Value::Kind::kF32:
+            out << std::to_string(r.AsF32().value);
+            break;
+        case Value::Kind::kF16:
+            out << std::to_string(r.AsF16().value);
+            break;
+        case Value::Kind::kI32:
+            out << std::to_string(r.AsI32().value);
+            break;
+        case Value::Kind::kU32:
+            out << std::to_string(r.AsU32().value);
+            break;
+            // TODO(dsinclair): Emit the symbol instead of v
+        case Value::Kind::kVar:
+            out << "%v" << std::to_string(r.AsVarData().id);
+            break;
+        case Value::Kind::kBool:
+            out << (r.AsBool() ? "true" : "false");
+            break;
+        case Value::Kind::kUninitialized:
+            out << "unknown value";
+            break;
+    }
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
new file mode 100644
index 0000000..7c9f8b5
--- /dev/null
+++ b/src/tint/ir/value.h
@@ -0,0 +1,169 @@
+// 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_VALUE_H_
+#define SRC_TINT_IR_VALUE_H_
+
+#include <ostream>
+#include <variant>
+
+#include "src/tint/number.h"
+#include "src/tint/symbol.h"
+
+namespace tint::ir {
+
+/// Value in the IR. The value can be one of several types these include, but aren't limited
+/// to, `f32`, `u32`, `temp`, `var`. The type of the value determines the type of data stored
+/// in the value.
+class Value {
+  public:
+    /// A value id.
+    using Id = uint32_t;
+
+    /// The type of the value
+    enum class Kind {
+        /// A uninitialized value
+        kUninitialized,
+        /// A temporary allocated value
+        kTemp,
+        /// A f32 value
+        kF32,
+        /// A f16 value
+        kF16,
+        /// An i32 value
+        kI32,
+        /// A u32 value
+        kU32,
+        /// A variable value
+        kVar,
+        /// A boolean value
+        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 value will be created and the the `id`
+    /// will be incremented.
+    struct VarData {
+        /// The symbol for the variable
+        Symbol sym;
+        /// The id for the variable.
+        Id id;
+        // TODO(dsinclair): Should var type data be stored here along side the variable info?
+    };
+
+    /// Constructor
+    /// Creates a uninitialized value
+    Value();
+
+    /// Constructor
+    /// @param id the id for the value
+    explicit Value(Id id);
+
+    /// Constructor
+    /// @param s the symbol for the value
+    /// @param id the id for the value
+    Value(Symbol s, Id id);
+
+    /// Constructor
+    /// @param b the `bool` value to store in the value
+    explicit Value(bool b);
+
+    /// Constructor
+    /// @param f the `f32` value to store in the value
+    explicit Value(f32 f);
+
+    /// Constructor
+    /// @param f the `f16` value to store in the value
+    explicit Value(f16 f);
+
+    /// Constructor
+    /// @param u the `u32` value to store in the value
+    explicit Value(u32 u);
+
+    /// Constructor
+    /// @param i the `i32` value to store in the value
+    explicit Value(i32 i);
+
+    /// Destructor
+    ~Value();
+
+    /// Copy constructor
+    /// @param o the value to copy from
+    Value(const Value& o);
+    /// Move constructor
+    /// @param o the value to move from
+    Value(Value&& o);
+
+    /// Copy assign
+    /// @param o the value to copy from
+    /// @returns this
+    Value& operator=(const Value& o);
+    /// Move assign
+    /// @param o the value to move from
+    /// @returns this
+    Value& operator=(Value&& o);
+
+    /// @returns true if this is a temporary value
+    bool IsTemp() const { return kind_ == Kind::kTemp; }
+    /// @returns true if this is a f32 value
+    bool IsF32() const { return kind_ == Kind::kF32; }
+    /// @returns true if this is a f16 value
+    bool IsF16() const { return kind_ == Kind::kF16; }
+    /// @returns true if this is an i32 value
+    bool IsI32() const { return kind_ == Kind::kI32; }
+    /// @returns true if this is a u32 value
+    bool IsU32() const { return kind_ == Kind::kU32; }
+    /// @returns true if this is a var value
+    bool IsVar() const { return kind_ == Kind::kVar; }
+    /// @returns true if this is a bool value
+    bool IsBool() const { return kind_ == Kind::kBool; }
+
+    /// @returns the kind of value
+    Kind GetKind() const { return kind_; }
+
+    /// @returns the value data as a `f32`.
+    /// @note, must only be called if `IsF32()` is true
+    f32 AsF32() const { return std::get<f32>(data_); }
+    /// @returns the value data as a `f16`.
+    /// @note, must only be called if `IsF16()` is true
+    f16 AsF16() const { return std::get<f16>(data_); }
+    /// @returns the value data as an `i32`.
+    /// @note, must only be called if `IsI32()` is true
+    i32 AsI32() const { return std::get<i32>(data_); }
+    /// @returns the value data as a `u32`.
+    /// @note, must only be called if `IsU32()` is true
+    u32 AsU32() const { return std::get<u32>(data_); }
+    /// @returns the value data as an `Id`.
+    /// @note, must only be called if `IsTemp()` is true
+    Id AsId() const { return std::get<Id>(data_); }
+    /// @returns the value data as a `VarData` structure.
+    /// @note, must only be called if `IsVar()` is true
+    VarData AsVarData() const { return std::get<VarData>(data_); }
+    /// @returns the value data as a `bool`.
+    /// @note, must only be called if `IsBool()` is true
+    bool AsBool() const { return std::get<bool>(data_); }
+
+  private:
+    /// The type of data stored in this value
+    Kind kind_;
+    /// The data stored in the value
+    std::variant<Id, f32, f16, u32, i32, VarData, bool> data_;
+};
+
+std::ostream& operator<<(std::ostream& out, const Value& r);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_VALUE_H_
diff --git a/src/tint/ir/value_test.cc b/src/tint/ir/value_test.cc
new file mode 100644
index 0000000..77ee3e4
--- /dev/null
+++ b/src/tint/ir/value_test.cc
@@ -0,0 +1,183 @@
+// 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/test_helper.h"
+#include "src/tint/ir/value.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_ValueTest = TestHelper;
+
+TEST_F(IR_ValueTest, f32) {
+    std::stringstream str;
+
+    Value val(1.2_f);
+    EXPECT_EQ(1.2_f, val.AsF32());
+
+    str << val;
+    EXPECT_EQ("1.200000", str.str());
+
+    EXPECT_TRUE(val.IsF32());
+    EXPECT_FALSE(val.IsF16());
+    EXPECT_FALSE(val.IsI32());
+    EXPECT_FALSE(val.IsU32());
+    EXPECT_FALSE(val.IsTemp());
+    EXPECT_FALSE(val.IsVar());
+    EXPECT_FALSE(val.IsBool());
+}
+
+TEST_F(IR_ValueTest, f16) {
+    std::stringstream str;
+
+    Value val(1.1_h);
+    EXPECT_EQ(1.1_h, val.AsF16());
+
+    str << val;
+    EXPECT_EQ("1.099609", str.str());
+
+    EXPECT_FALSE(val.IsF32());
+    EXPECT_TRUE(val.IsF16());
+    EXPECT_FALSE(val.IsI32());
+    EXPECT_FALSE(val.IsU32());
+    EXPECT_FALSE(val.IsTemp());
+    EXPECT_FALSE(val.IsVar());
+    EXPECT_FALSE(val.IsBool());
+}
+
+TEST_F(IR_ValueTest, i32) {
+    std::stringstream str;
+
+    Value val(1_i);
+    EXPECT_EQ(1_i, val.AsI32());
+
+    str << val;
+    EXPECT_EQ("1", str.str());
+
+    EXPECT_FALSE(val.IsF32());
+    EXPECT_FALSE(val.IsF16());
+    EXPECT_TRUE(val.IsI32());
+    EXPECT_FALSE(val.IsU32());
+    EXPECT_FALSE(val.IsTemp());
+    EXPECT_FALSE(val.IsVar());
+    EXPECT_FALSE(val.IsBool());
+}
+
+TEST_F(IR_ValueTest, u32) {
+    std::stringstream str;
+
+    Value val(2_u);
+    EXPECT_EQ(2_u, val.AsU32());
+
+    str << val;
+    EXPECT_EQ("2", str.str());
+
+    EXPECT_FALSE(val.IsF32());
+    EXPECT_FALSE(val.IsF16());
+    EXPECT_FALSE(val.IsI32());
+    EXPECT_TRUE(val.IsU32());
+    EXPECT_FALSE(val.IsTemp());
+    EXPECT_FALSE(val.IsVar());
+    EXPECT_FALSE(val.IsBool());
+}
+
+TEST_F(IR_ValueTest, id) {
+    std::stringstream str;
+
+    Value val(Value::Id(4));
+    EXPECT_EQ(4u, val.AsId());
+
+    str << val;
+    EXPECT_EQ("%4", str.str());
+
+    EXPECT_FALSE(val.IsF32());
+    EXPECT_FALSE(val.IsF16());
+    EXPECT_FALSE(val.IsI32());
+    EXPECT_FALSE(val.IsU32());
+    EXPECT_TRUE(val.IsTemp());
+    EXPECT_FALSE(val.IsVar());
+    EXPECT_FALSE(val.IsBool());
+}
+
+TEST_F(IR_ValueTest, bool) {
+    std::stringstream str;
+
+    Value val(false);
+    EXPECT_FALSE(val.AsBool());
+
+    str << val;
+    EXPECT_EQ("false", str.str());
+
+    str.str("");
+    val = Value(true);
+    EXPECT_TRUE(val.AsBool());
+
+    str << val;
+    EXPECT_EQ("true", str.str());
+
+    EXPECT_FALSE(val.IsF32());
+    EXPECT_FALSE(val.IsF16());
+    EXPECT_FALSE(val.IsI32());
+    EXPECT_FALSE(val.IsU32());
+    EXPECT_FALSE(val.IsTemp());
+    EXPECT_FALSE(val.IsVar());
+    EXPECT_TRUE(val.IsBool());
+}
+
+TEST_F(IR_ValueTest, var) {
+    std::stringstream str;
+
+    Symbol s;
+    Value val(s, 2);
+    EXPECT_EQ(2u, val.AsVarData().id);
+    EXPECT_EQ(s, val.AsVarData().sym);
+
+    str << val;
+    EXPECT_EQ("%v2", str.str());
+    str.str("");
+
+    val = Value(s, 4);
+    EXPECT_EQ(4u, val.AsVarData().id);
+    EXPECT_EQ(s, val.AsVarData().sym);
+
+    str << val;
+    EXPECT_EQ("%v4", str.str());
+
+    EXPECT_FALSE(val.IsF32());
+    EXPECT_FALSE(val.IsF16());
+    EXPECT_FALSE(val.IsI32());
+    EXPECT_FALSE(val.IsU32());
+    EXPECT_FALSE(val.IsTemp());
+    EXPECT_TRUE(val.IsVar());
+    EXPECT_FALSE(val.IsBool());
+}
+
+TEST_F(IR_ValueTest, uninitialized) {
+    Value val;
+
+    EXPECT_FALSE(val.IsF32());
+    EXPECT_FALSE(val.IsF16());
+    EXPECT_FALSE(val.IsI32());
+    EXPECT_FALSE(val.IsU32());
+    EXPECT_FALSE(val.IsTemp());
+    EXPECT_FALSE(val.IsVar());
+    EXPECT_FALSE(val.IsBool());
+}
+
+}  // namespace
+}  // namespace tint::ir