[ir] Add Result objects into the instructions

This CL updates `OperandInstruction` with the ability to store the
`Result` values for a given instruction.

Bug: tint:1718
Change-Id: Ia7289cfe106716e6941c6a07917b7eb94697a047
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/137480
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ir/access.cc b/src/tint/ir/access.cc
index bce9abd..9933c90 100644
--- a/src/tint/ir/access.cc
+++ b/src/tint/ir/access.cc
@@ -29,6 +29,8 @@
 
     AddOperand(Access::kObjectOperandOffset, object);
     AddOperands(Access::kIndicesOperandOffset, std::move(indices));
+
+    AddResult(this);
 }
 
 Access::~Access() = default;
diff --git a/src/tint/ir/access.h b/src/tint/ir/access.h
index f3f03c8..3cf3e74 100644
--- a/src/tint/ir/access.h
+++ b/src/tint/ir/access.h
@@ -21,7 +21,7 @@
 namespace tint::ir {
 
 /// An access instruction in the IR.
-class Access : public utils::Castable<Access, OperandInstruction<3>> {
+class Access : public utils::Castable<Access, OperandInstruction<3, 1>> {
   public:
     /// The offset in Operands() for the object being accessed
     static constexpr size_t kObjectOperandOffset = 0;
diff --git a/src/tint/ir/access_test.cc b/src/tint/ir/access_test.cc
index ee19623..5b588d9 100644
--- a/src/tint/ir/access_test.cc
+++ b/src/tint/ir/access_test.cc
@@ -35,6 +35,18 @@
     EXPECT_THAT(idx->Usages(), testing::UnorderedElementsAre(Usage{a, 1u}));
 }
 
+TEST_F(IR_AccessTest, Result) {
+    auto* type = ty.ptr<function, i32>();
+    auto* var = b.Var(type);
+    auto* idx = b.Constant(u32(1));
+    auto* a = b.Access(ty.i32(), var, idx);
+
+    auto results = a->Results();
+    EXPECT_TRUE(a->HasResults());
+    EXPECT_FALSE(a->HasMultiResults());
+    EXPECT_EQ(a, results[0]);
+}
+
 TEST_F(IR_AccessTest, Fail_NullType) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/binary.cc b/src/tint/ir/binary.cc
index 0b65966..dd8dc24 100644
--- a/src/tint/ir/binary.cc
+++ b/src/tint/ir/binary.cc
@@ -25,6 +25,8 @@
 
     AddOperand(Binary::kLhsOperandOffset, lhs);
     AddOperand(Binary::kRhsOperandOffset, rhs);
+
+    AddResult(this);
 }
 
 Binary::~Binary() = default;
diff --git a/src/tint/ir/binary.h b/src/tint/ir/binary.h
index c759c12..291031d 100644
--- a/src/tint/ir/binary.h
+++ b/src/tint/ir/binary.h
@@ -21,7 +21,7 @@
 namespace tint::ir {
 
 /// A binary instruction in the IR.
-class Binary : public utils::Castable<Binary, OperandInstruction<2>> {
+class Binary : public utils::Castable<Binary, OperandInstruction<2, 1>> {
   public:
     /// The offset in Operands() for the LHS
     static constexpr size_t kLhsOperandOffset = 0;
diff --git a/src/tint/ir/binary_test.cc b/src/tint/ir/binary_test.cc
index d45296c..c4edc22 100644
--- a/src/tint/ir/binary_test.cc
+++ b/src/tint/ir/binary_test.cc
@@ -35,6 +35,15 @@
         "");
 }
 
+TEST_F(IR_BinaryTest, Result) {
+    auto* a = b.Add(mod.Types().i32(), 4_i, 2_i);
+
+    auto results = a->Results();
+    EXPECT_TRUE(a->HasResults());
+    EXPECT_FALSE(a->HasMultiResults());
+    EXPECT_EQ(a, results[0]);
+}
+
 TEST_F(IR_BinaryTest, CreateAnd) {
     auto* inst = b.And(mod.Types().i32(), 4_i, 2_i);
 
diff --git a/src/tint/ir/bitcast.cc b/src/tint/ir/bitcast.cc
index 262d20c..04197f3 100644
--- a/src/tint/ir/bitcast.cc
+++ b/src/tint/ir/bitcast.cc
@@ -21,6 +21,8 @@
 
 Bitcast::Bitcast(const type::Type* ty, Value* val) : Base(ty) {
     AddOperand(Bitcast::kValueOperandOffset, val);
+
+    AddResult(this);
 }
 
 Bitcast::~Bitcast() = default;
diff --git a/src/tint/ir/bitcast_test.cc b/src/tint/ir/bitcast_test.cc
index 44c8d90..aac09c9 100644
--- a/src/tint/ir/bitcast_test.cc
+++ b/src/tint/ir/bitcast_test.cc
@@ -40,6 +40,15 @@
     EXPECT_EQ(4_i, val->As<constant::Scalar<i32>>()->ValueAs<i32>());
 }
 
+TEST_F(IR_BitcastTest, Result) {
+    auto* a = b.Bitcast(mod.Types().i32(), 4_i);
+
+    auto results = a->Results();
+    EXPECT_TRUE(a->HasResults());
+    EXPECT_FALSE(a->HasMultiResults());
+    EXPECT_EQ(a, results[0]);
+}
+
 TEST_F(IR_BitcastTest, Bitcast_Usage) {
     auto* inst = b.Bitcast(mod.Types().i32(), 4_i);
 
diff --git a/src/tint/ir/branch.h b/src/tint/ir/branch.h
index 15a7d42..1069529 100644
--- a/src/tint/ir/branch.h
+++ b/src/tint/ir/branch.h
@@ -27,7 +27,7 @@
 namespace tint::ir {
 
 /// A branch instruction.
-class Branch : public utils::Castable<Branch, OperandInstruction<1>> {
+class Branch : public utils::Castable<Branch, OperandInstruction<1, 0>> {
   public:
     ~Branch() override;
 
diff --git a/src/tint/ir/break_if_test.cc b/src/tint/ir/break_if_test.cc
index 84c29db..2ce17ae 100644
--- a/src/tint/ir/break_if_test.cc
+++ b/src/tint/ir/break_if_test.cc
@@ -37,6 +37,17 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{brk, 2u}));
 }
 
+TEST_F(IR_BreakIfTest, Results) {
+    auto* loop = b.Loop();
+    auto* cond = b.Constant(true);
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+
+    auto* brk = b.BreakIf(cond, loop, arg1, arg2);
+    EXPECT_FALSE(brk->HasResults());
+    EXPECT_FALSE(brk->HasMultiResults());
+}
+
 TEST_F(IR_BreakIfTest, Fail_NullLoop) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/builtin_call.cc b/src/tint/ir/builtin_call.cc
index fb1e708..f59f9da 100644
--- a/src/tint/ir/builtin_call.cc
+++ b/src/tint/ir/builtin_call.cc
@@ -30,6 +30,8 @@
     TINT_ASSERT(IR, func != builtin::Function::kTintMaterialize);
 
     AddOperands(BuiltinCall::kArgsOperandOffset, std::move(arguments));
+
+    AddResult(this);
 }
 
 BuiltinCall::~BuiltinCall() = default;
diff --git a/src/tint/ir/builtin_call_test.cc b/src/tint/ir/builtin_call_test.cc
index d885f88..194c984 100644
--- a/src/tint/ir/builtin_call_test.cc
+++ b/src/tint/ir/builtin_call_test.cc
@@ -32,6 +32,18 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{builtin, 1u}));
 }
 
+TEST_F(IR_BuiltinCallTest, Result) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* builtin = b.Call(mod.Types().f32(), builtin::Function::kAbs, arg1, arg2);
+
+    EXPECT_TRUE(builtin->HasResults());
+    EXPECT_FALSE(builtin->HasMultiResults());
+
+    auto results = builtin->Results();
+    EXPECT_EQ(results[0], builtin);
+}
+
 TEST_F(IR_BuiltinCallTest, Fail_NullType) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/call.h b/src/tint/ir/call.h
index c26aa5d..55817a5 100644
--- a/src/tint/ir/call.h
+++ b/src/tint/ir/call.h
@@ -21,7 +21,7 @@
 namespace tint::ir {
 
 /// A Call instruction in the IR.
-class Call : public utils::Castable<Call, OperandInstruction<4>> {
+class Call : public utils::Castable<Call, OperandInstruction<4, 1>> {
   public:
     ~Call() override;
 
diff --git a/src/tint/ir/construct.cc b/src/tint/ir/construct.cc
index 06adf2d..4e75722 100644
--- a/src/tint/ir/construct.cc
+++ b/src/tint/ir/construct.cc
@@ -24,6 +24,8 @@
 
 Construct::Construct(const type::Type* ty, utils::VectorRef<Value*> arguments) : Base(ty) {
     AddOperands(Construct::kArgsOperandOffset, std::move(arguments));
+
+    AddResult(this);
 }
 
 Construct::~Construct() = default;
diff --git a/src/tint/ir/construct_test.cc b/src/tint/ir/construct_test.cc
index 13ec4c0..385b0bf 100644
--- a/src/tint/ir/construct_test.cc
+++ b/src/tint/ir/construct_test.cc
@@ -33,6 +33,18 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{c, 1u}));
 }
 
+TEST_F(IR_ConstructTest, Result) {
+    auto* arg1 = b.Constant(true);
+    auto* arg2 = b.Constant(false);
+    auto* c = b.Construct(mod.Types().f32(), arg1, arg2);
+
+    EXPECT_TRUE(c->HasResults());
+    EXPECT_FALSE(c->HasMultiResults());
+
+    auto results = c->Results();
+    EXPECT_EQ(c, results[0]);
+}
+
 TEST_F(IR_ConstructTest, Fail_NullType) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/continue_test.cc b/src/tint/ir/continue_test.cc
index 7a8df1b..d16defb 100644
--- a/src/tint/ir/continue_test.cc
+++ b/src/tint/ir/continue_test.cc
@@ -35,6 +35,17 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{brk, 1u}));
 }
 
+TEST_F(IR_ContinueTest, Results) {
+    auto* loop = b.Loop();
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+
+    auto* brk = b.Continue(loop, arg1, arg2);
+
+    EXPECT_FALSE(brk->HasResults());
+    EXPECT_FALSE(brk->HasMultiResults());
+}
+
 TEST_F(IR_ContinueTest, Fail_NullLoop) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/convert.cc b/src/tint/ir/convert.cc
index 4681668..5f9fe6d 100644
--- a/src/tint/ir/convert.cc
+++ b/src/tint/ir/convert.cc
@@ -24,6 +24,8 @@
 
 Convert::Convert(const type::Type* to_type, Value* value) : Base(to_type) {
     AddOperand(Convert::kValueOperandOffset, value);
+
+    AddResult(this);
 }
 
 Convert::~Convert() = default;
diff --git a/src/tint/ir/convert_test.cc b/src/tint/ir/convert_test.cc
index d5047e5..df1acec 100644
--- a/src/tint/ir/convert_test.cc
+++ b/src/tint/ir/convert_test.cc
@@ -32,5 +32,15 @@
         "");
 }
 
+TEST_F(IR_ConvertTest, Results) {
+    auto* c = b.Convert(mod.Types().i32(), 1_u);
+
+    EXPECT_TRUE(c->HasResults());
+    EXPECT_FALSE(c->HasMultiResults());
+
+    auto results = c->Results();
+    EXPECT_EQ(results[0], c);
+}
+
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/ir/discard_test.cc b/src/tint/ir/discard_test.cc
index 91e1ef1..4a5b059 100644
--- a/src/tint/ir/discard_test.cc
+++ b/src/tint/ir/discard_test.cc
@@ -27,6 +27,13 @@
     ASSERT_TRUE(inst->Is<ir::Discard>());
 }
 
+TEST_F(IR_DiscardTest, Result) {
+    auto* inst = b.Discard();
+
+    EXPECT_FALSE(inst->HasResults());
+    EXPECT_FALSE(inst->HasMultiResults());
+}
+
 TEST_F(IR_DiscardTest, Fail_NullType) {
     EXPECT_FATAL_FAILURE({ Discard d(nullptr); }, "");
 }
diff --git a/src/tint/ir/exit_if_test.cc b/src/tint/ir/exit_if_test.cc
index 68c9012..d112138 100644
--- a/src/tint/ir/exit_if_test.cc
+++ b/src/tint/ir/exit_if_test.cc
@@ -34,6 +34,16 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 1u}));
 }
 
+TEST_F(IR_ExitIfTest, Result) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* if_ = b.If(true);
+    auto* e = b.ExitIf(if_, arg1, arg2);
+
+    EXPECT_FALSE(e->HasResults());
+    EXPECT_FALSE(e->HasMultiResults());
+}
+
 TEST_F(IR_ExitIfTest, Fail_NullIf) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/exit_loop_test.cc b/src/tint/ir/exit_loop_test.cc
index a234793..fddfe0e 100644
--- a/src/tint/ir/exit_loop_test.cc
+++ b/src/tint/ir/exit_loop_test.cc
@@ -34,6 +34,16 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 1u}));
 }
 
+TEST_F(IR_ExitLoopTest, Result) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* loop = b.Loop();
+    auto* e = b.ExitLoop(loop, arg1, arg2);
+
+    EXPECT_FALSE(e->HasResults());
+    EXPECT_FALSE(e->HasMultiResults());
+}
+
 TEST_F(IR_ExitLoopTest, Fail_NullLoop) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/exit_switch_test.cc b/src/tint/ir/exit_switch_test.cc
index df2b5a1..38cce6e 100644
--- a/src/tint/ir/exit_switch_test.cc
+++ b/src/tint/ir/exit_switch_test.cc
@@ -34,6 +34,16 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 1u}));
 }
 
+TEST_F(IR_ExitSwitchTest, Result) {
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* switch_ = b.Switch(true);
+    auto* e = b.ExitSwitch(switch_, arg1, arg2);
+
+    EXPECT_FALSE(e->HasResults());
+    EXPECT_FALSE(e->HasMultiResults());
+}
+
 TEST_F(IR_ExitSwitchTest, Fail_NullSwitch) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/if_test.cc b/src/tint/ir/if_test.cc
index 039a398..a53aee6 100644
--- a/src/tint/ir/if_test.cc
+++ b/src/tint/ir/if_test.cc
@@ -29,6 +29,13 @@
     EXPECT_THAT(cond->Usages(), testing::UnorderedElementsAre(Usage{if_, 0u}));
 }
 
+TEST_F(IR_IfTest, Result) {
+    auto* if_ = b.If(b.Constant(true));
+
+    EXPECT_FALSE(if_->HasResults());
+    EXPECT_FALSE(if_->HasMultiResults());
+}
+
 TEST_F(IR_IfTest, Parent) {
     auto* cond = b.Constant(true);
     auto* if_ = b.If(cond);
diff --git a/src/tint/ir/load.cc b/src/tint/ir/load.cc
index aa91056..b34807d 100644
--- a/src/tint/ir/load.cc
+++ b/src/tint/ir/load.cc
@@ -26,6 +26,8 @@
     result_type_ = from->Type()->UnwrapPtr();
 
     AddOperand(Load::kFromOperandOffset, from);
+
+    AddResult(this);
 }
 
 Load::~Load() = default;
diff --git a/src/tint/ir/load.h b/src/tint/ir/load.h
index 12efb31..8178589 100644
--- a/src/tint/ir/load.h
+++ b/src/tint/ir/load.h
@@ -21,7 +21,7 @@
 namespace tint::ir {
 
 /// A load instruction in the IR.
-class Load : public utils::Castable<Load, OperandInstruction<1>> {
+class Load : public utils::Castable<Load, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the from value
     static constexpr size_t kFromOperandOffset = 0;
diff --git a/src/tint/ir/load_test.cc b/src/tint/ir/load_test.cc
index 7ed92ed..51bdb13 100644
--- a/src/tint/ir/load_test.cc
+++ b/src/tint/ir/load_test.cc
@@ -48,6 +48,17 @@
     EXPECT_THAT(inst->From()->Usages(), testing::UnorderedElementsAre(Usage{inst, 0u}));
 }
 
+TEST_F(IR_LoadTest, Results) {
+    auto* var = b.Var(ty.ptr<function, i32>());
+    auto* inst = b.Load(var);
+
+    EXPECT_TRUE(inst->HasResults());
+    EXPECT_FALSE(inst->HasMultiResults());
+
+    auto results = inst->Results();
+    EXPECT_EQ(results[0], inst);
+}
+
 TEST_F(IR_LoadTest, Fail_NonPtr_Builder) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/loop_test.cc b/src/tint/ir/loop_test.cc
index 38fecfa..a92e320 100644
--- a/src/tint/ir/loop_test.cc
+++ b/src/tint/ir/loop_test.cc
@@ -30,6 +30,12 @@
     EXPECT_EQ(loop->Merge()->Parent(), loop);
 }
 
+TEST_F(IR_LoopTest, Result) {
+    auto* loop = b.Loop();
+    EXPECT_FALSE(loop->HasResults());
+    EXPECT_FALSE(loop->HasMultiResults());
+}
+
 TEST_F(IR_LoopTest, Fail_NullInitializerBlock) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/next_iteration_test.cc b/src/tint/ir/next_iteration_test.cc
index 657411c..5c6d16d 100644
--- a/src/tint/ir/next_iteration_test.cc
+++ b/src/tint/ir/next_iteration_test.cc
@@ -32,5 +32,12 @@
         "");
 }
 
+TEST_F(IR_NextIterationTest, Result) {
+    auto* inst = b.NextIteration(b.Loop());
+
+    EXPECT_FALSE(inst->HasResults());
+    EXPECT_FALSE(inst->HasMultiResults());
+}
+
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/ir/operand_instruction.cc b/src/tint/ir/operand_instruction.cc
index 7d56ed2..9a1f4aa 100644
--- a/src/tint/ir/operand_instruction.cc
+++ b/src/tint/ir/operand_instruction.cc
@@ -14,10 +14,18 @@
 
 #include "src/tint/ir/operand_instruction.h"
 
-TINT_INSTANTIATE_TYPEINFO(tint::ir::OperandInstruction<1>);
-TINT_INSTANTIATE_TYPEINFO(tint::ir::OperandInstruction<2>);
-TINT_INSTANTIATE_TYPEINFO(tint::ir::OperandInstruction<3>);
-TINT_INSTANTIATE_TYPEINFO(tint::ir::OperandInstruction<4>);
-TINT_INSTANTIATE_TYPEINFO(tint::ir::OperandInstruction<8>);
+using Op10 = tint::ir::OperandInstruction<1, 0>;
+using Op11 = tint::ir::OperandInstruction<1, 1>;
+using Op20 = tint::ir::OperandInstruction<2, 0>;
+using Op21 = tint::ir::OperandInstruction<2, 1>;
+using Op31 = tint::ir::OperandInstruction<3, 1>;
+using Op41 = tint::ir::OperandInstruction<4, 1>;
+
+TINT_INSTANTIATE_TYPEINFO(Op10);
+TINT_INSTANTIATE_TYPEINFO(Op11);
+TINT_INSTANTIATE_TYPEINFO(Op20);
+TINT_INSTANTIATE_TYPEINFO(Op21);
+TINT_INSTANTIATE_TYPEINFO(Op31);
+TINT_INSTANTIATE_TYPEINFO(Op41);
 
 namespace tint::ir {}  // namespace tint::ir
diff --git a/src/tint/ir/operand_instruction.h b/src/tint/ir/operand_instruction.h
index a607155..249d31d 100644
--- a/src/tint/ir/operand_instruction.h
+++ b/src/tint/ir/operand_instruction.h
@@ -20,8 +20,10 @@
 namespace tint::ir {
 
 /// An instruction in the IR that expects one or more operands.
-template <unsigned N>
-class OperandInstruction : public utils::Castable<OperandInstruction<N>, Instruction> {
+/// @tparam N the default number of operands
+/// @tparam R the default number of result values
+template <unsigned N, unsigned R>
+class OperandInstruction : public utils::Castable<OperandInstruction<N, R>, Instruction> {
   public:
     /// Destructor
     ~OperandInstruction() override = default;
@@ -41,6 +43,13 @@
         return;
     }
 
+    /// @returns true if the instruction has result values
+    bool HasResults() { return !results_.IsEmpty(); }
+    /// @returns true if the instruction has multiple values
+    bool HasMultiResults() { return results_.Length() > 1; }
+    /// @returns the result values for this instruction
+    utils::VectorRef<Value*> Results() { return results_; }
+
   protected:
     /// Append a new operand to the operand list for this instruction.
     /// @param idx the index the operand should be at
@@ -65,8 +74,19 @@
         }
     }
 
+    /// Appends a result value to the instruction
+    /// @param value the value to append
+    void AddResult(Value* value) {
+        if (value) {
+            value->SetSource(this);
+        }
+        results_.Push(value);
+    }
+
     /// The operands to this instruction.
     utils::Vector<ir::Value*, N> operands_;
+    /// The results of this instruction.
+    utils::Vector<ir::Value*, R> results_;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/return_test.cc b/src/tint/ir/return_test.cc
index f731c6b..159e94d 100644
--- a/src/tint/ir/return_test.cc
+++ b/src/tint/ir/return_test.cc
@@ -45,5 +45,22 @@
     EXPECT_THAT(val->Usages(), testing::UnorderedElementsAre(Usage{ret, 1u}));
 }
 
+TEST_F(IR_ReturnTest, Result) {
+    auto* vfunc = b.Function("vfunc", ty.void_());
+    auto* ifunc = b.Function("ifunc", ty.i32());
+
+    {
+        auto* ret1 = b.Return(vfunc);
+        EXPECT_FALSE(ret1->HasResults());
+        EXPECT_FALSE(ret1->HasMultiResults());
+    }
+
+    {
+        auto* ret2 = b.Return(ifunc, b.Constant(42_i));
+        EXPECT_FALSE(ret2->HasResults());
+        EXPECT_FALSE(ret2->HasMultiResults());
+    }
+}
+
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/ir/store.h b/src/tint/ir/store.h
index 7c97aee..e14a58f 100644
--- a/src/tint/ir/store.h
+++ b/src/tint/ir/store.h
@@ -21,7 +21,7 @@
 namespace tint::ir {
 
 /// A store instruction in the IR.
-class Store : public utils::Castable<Store, OperandInstruction<2>> {
+class Store : public utils::Castable<Store, OperandInstruction<2, 0>> {
   public:
     /// The offset in Operands() for the `to` value
     static constexpr size_t kToOperandOffset = 0;
diff --git a/src/tint/ir/store_test.cc b/src/tint/ir/store_test.cc
index 1d543ed..0ab2f46 100644
--- a/src/tint/ir/store_test.cc
+++ b/src/tint/ir/store_test.cc
@@ -39,7 +39,7 @@
     EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 }
 
-TEST_F(IR_StoreTest, Store_Usage) {
+TEST_F(IR_StoreTest, Usage) {
     auto* to = b.Discard();
     auto* inst = b.Store(to, 4_i);
 
@@ -50,5 +50,13 @@
     EXPECT_THAT(inst->From()->Usages(), testing::UnorderedElementsAre(Usage{inst, 1u}));
 }
 
+TEST_F(IR_StoreTest, Result) {
+    auto* to = b.Discard();
+    auto* inst = b.Store(to, 4_i);
+
+    EXPECT_FALSE(inst->HasResults());
+    EXPECT_FALSE(inst->HasMultiResults());
+}
+
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/ir/switch_test.cc b/src/tint/ir/switch_test.cc
index ab88556..781e0fc 100644
--- a/src/tint/ir/switch_test.cc
+++ b/src/tint/ir/switch_test.cc
@@ -30,6 +30,13 @@
     EXPECT_THAT(cond->Usages(), testing::UnorderedElementsAre(Usage{switch_, 0u}));
 }
 
+TEST_F(IR_SwitchTest, Results) {
+    auto* cond = b.Constant(true);
+    auto* switch_ = b.Switch(cond);
+    EXPECT_FALSE(switch_->HasResults());
+    EXPECT_FALSE(switch_->HasMultiResults());
+}
+
 TEST_F(IR_SwitchTest, Parent) {
     auto* switch_ = b.Switch(1_i);
     b.Case(switch_, {Switch::CaseSelector{nullptr}});
diff --git a/src/tint/ir/swizzle.cc b/src/tint/ir/swizzle.cc
index 48c26cf..915a682 100644
--- a/src/tint/ir/swizzle.cc
+++ b/src/tint/ir/swizzle.cc
@@ -29,6 +29,7 @@
     TINT_ASSERT(IR, indices.Length() <= 4);
 
     AddOperand(Swizzle::kObjectOperandOffset, object);
+    AddResult(this);
 
     for (auto idx : indices_) {
         TINT_ASSERT(IR, idx < 4);
diff --git a/src/tint/ir/swizzle.h b/src/tint/ir/swizzle.h
index 1613bfb..347f946 100644
--- a/src/tint/ir/swizzle.h
+++ b/src/tint/ir/swizzle.h
@@ -21,7 +21,7 @@
 namespace tint::ir {
 
 /// A swizzle instruction in the IR.
-class Swizzle : public utils::Castable<Swizzle, OperandInstruction<1>> {
+class Swizzle : public utils::Castable<Swizzle, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the object being swizzled
     static constexpr size_t kObjectOperandOffset = 0;
diff --git a/src/tint/ir/swizzle_test.cc b/src/tint/ir/swizzle_test.cc
index 43dd54b..5a9cd1a 100644
--- a/src/tint/ir/swizzle_test.cc
+++ b/src/tint/ir/swizzle_test.cc
@@ -32,6 +32,17 @@
     EXPECT_THAT(var->Usages(), testing::UnorderedElementsAre(Usage{a, 0u}));
 }
 
+TEST_F(IR_SwizzleTest, Results) {
+    auto* var = b.Var(ty.ptr<function, i32>());
+    auto* a = b.Swizzle(mod.Types().i32(), var, {1u});
+
+    EXPECT_TRUE(a->HasResults());
+    EXPECT_FALSE(a->HasMultiResults());
+
+    auto results = a->Results();
+    EXPECT_EQ(results[0], a);
+}
+
 TEST_F(IR_SwizzleTest, Fail_NullType) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/unary.cc b/src/tint/ir/unary.cc
index 5d448f7..b311149 100644
--- a/src/tint/ir/unary.cc
+++ b/src/tint/ir/unary.cc
@@ -23,6 +23,7 @@
     TINT_ASSERT(IR, result_type_ != nullptr);
 
     AddOperand(Unary::kValueOperandOffset, val);
+    AddResult(this);
 }
 
 Unary::~Unary() = default;
diff --git a/src/tint/ir/unary.h b/src/tint/ir/unary.h
index f94f1bc..379d904 100644
--- a/src/tint/ir/unary.h
+++ b/src/tint/ir/unary.h
@@ -21,7 +21,7 @@
 namespace tint::ir {
 
 /// A unary instruction in the IR.
-class Unary : public utils::Castable<Unary, OperandInstruction<1>> {
+class Unary : public utils::Castable<Unary, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the value
     static constexpr size_t kValueOperandOffset = 0;
diff --git a/src/tint/ir/unary_test.cc b/src/tint/ir/unary_test.cc
index 397a883..9692f9b 100644
--- a/src/tint/ir/unary_test.cc
+++ b/src/tint/ir/unary_test.cc
@@ -49,7 +49,7 @@
     EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 }
 
-TEST_F(IR_UnaryTest, Unary_Usage) {
+TEST_F(IR_UnaryTest, Usage) {
     auto* inst = b.Negation(mod.Types().i32(), 4_i);
 
     EXPECT_EQ(inst->Kind(), Unary::Kind::kNegation);
@@ -58,6 +58,15 @@
     EXPECT_THAT(inst->Val()->Usages(), testing::UnorderedElementsAre(Usage{inst, 0u}));
 }
 
+TEST_F(IR_UnaryTest, Result) {
+    auto* inst = b.Negation(mod.Types().i32(), 4_i);
+    EXPECT_TRUE(inst->HasResults());
+    EXPECT_FALSE(inst->HasMultiResults());
+
+    auto results = inst->Results();
+    EXPECT_EQ(results[0], inst);
+}
+
 TEST_F(IR_UnaryTest, Fail_NullType) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/user_call.cc b/src/tint/ir/user_call.cc
index d5bded9..24cf612 100644
--- a/src/tint/ir/user_call.cc
+++ b/src/tint/ir/user_call.cc
@@ -26,6 +26,8 @@
     : Base(ty) {
     AddOperand(UserCall::kFunctionOperandOffset, func);
     AddOperands(UserCall::kArgsOperandOffset, std::move(arguments));
+
+    AddResult(this);
 }
 
 UserCall::~UserCall() = default;
diff --git a/src/tint/ir/user_call_test.cc b/src/tint/ir/user_call_test.cc
index 82746d9..c06a398 100644
--- a/src/tint/ir/user_call_test.cc
+++ b/src/tint/ir/user_call_test.cc
@@ -34,6 +34,19 @@
     EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 2u}));
 }
 
+TEST_F(IR_UserCallTest, Results) {
+    auto* func = b.Function("myfunc", mod.Types().void_());
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+    auto* e = b.Call(mod.Types().void_(), func, utils::Vector{arg1, arg2});
+
+    EXPECT_TRUE(e->HasResults());
+    EXPECT_FALSE(e->HasMultiResults());
+
+    auto results = e->Results();
+    EXPECT_EQ(results[0], e);
+}
+
 TEST_F(IR_UserCallTest, Fail_NullType) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index 819e3bc..65a654e 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -75,11 +75,19 @@
     /// @param replacer a function which returns a replacement for a given use
     void ReplaceAllUsesWith(std::function<Value*(Usage use)> replacer);
 
+    /// Sets the source instruction for this value
+    /// @param inst the instruction to set
+    void SetSource(Instruction* inst) { source_ = inst; }
+
+    /// @returns the source instruction, if any
+    Instruction* Source() { return source_; }
+
   protected:
     /// Constructor
     Value();
 
   private:
+    Instruction* source_ = nullptr;
     utils::Hashset<Usage, 4, Usage::Hasher> uses_;
 };
 }  // namespace tint::ir
diff --git a/src/tint/ir/var.cc b/src/tint/ir/var.cc
index 2978f24..981dad3 100644
--- a/src/tint/ir/var.cc
+++ b/src/tint/ir/var.cc
@@ -24,6 +24,8 @@
 
     // Default to no initializer.
     AddOperand(Var::kInitializerOperandOffset, nullptr);
+
+    AddResult(this);
 }
 
 Var::~Var() = default;
diff --git a/src/tint/ir/var.h b/src/tint/ir/var.h
index 435d123..7284e2b 100644
--- a/src/tint/ir/var.h
+++ b/src/tint/ir/var.h
@@ -26,7 +26,7 @@
 namespace tint::ir {
 
 /// A var instruction in the IR.
-class Var : public utils::Castable<Var, OperandInstruction<1>> {
+class Var : public utils::Castable<Var, OperandInstruction<1, 1>> {
   public:
     /// The offset in Operands() for the initializer
     static constexpr size_t kInitializerOperandOffset = 0;
diff --git a/src/tint/ir/var_test.cc b/src/tint/ir/var_test.cc
index 1896e05..7dd0fcc 100644
--- a/src/tint/ir/var_test.cc
+++ b/src/tint/ir/var_test.cc
@@ -38,6 +38,12 @@
         "");
 }
 
+TEST_F(IR_VarTest, Results) {
+    auto* var = b.Var(ty.ptr<function, f32>());
+    EXPECT_TRUE(var->HasResults());
+    EXPECT_FALSE(var->HasMultiResults());
+}
+
 TEST_F(IR_VarTest, Initializer_Usage) {
     Module mod;
     Builder b{mod};