[ir] Track the operand index for value usages

Switch the usage list to be a hashset to allow direct lookup, which
will make it easier to remove specific usages.

Bug: tint:1718
Change-Id: Ie820a2f05aca11046fa5669247c784eaf1bb54c0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/135761
Reviewed-by: Ben Clayton <bclayton@google.com>
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ir/access_test.cc b/src/tint/ir/access_test.cc
index 68b0306..9e09930 100644
--- a/src/tint/ir/access_test.cc
+++ b/src/tint/ir/access_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/access.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -28,11 +30,8 @@
     auto* idx = b.Constant(u32(1));
     auto* a = b.Access(mod.Types().i32(), var, utils::Vector{idx});
 
-    EXPECT_EQ(1u, idx->Usage().Length());
-    EXPECT_EQ(a, idx->Usage()[0]);
-
-    EXPECT_EQ(1u, var->Usage().Length());
-    EXPECT_EQ(a, var->Usage()[0]);
+    EXPECT_THAT(var->Usages(), testing::UnorderedElementsAre(Usage{a, 0u}));
+    EXPECT_THAT(idx->Usages(), testing::UnorderedElementsAre(Usage{a, 1u}));
 }
 
 TEST_F(IR_AccessTest, Fail_NullType) {
diff --git a/src/tint/ir/binary_test.cc b/src/tint/ir/binary_test.cc
index 05dd2ff..60fd736 100644
--- a/src/tint/ir/binary_test.cc
+++ b/src/tint/ir/binary_test.cc
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
+#include "src/tint/ir/builder.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -344,29 +346,27 @@
 }
 
 TEST_F(IR_BinaryTest, Binary_Usage) {
-    const auto* inst = b.And(mod.Types().i32(), b.Constant(4_i), b.Constant(2_i));
+    auto* inst = b.And(mod.Types().i32(), b.Constant(4_i), b.Constant(2_i));
 
     EXPECT_EQ(inst->Kind(), Binary::Kind::kAnd);
 
     ASSERT_NE(inst->LHS(), nullptr);
-    ASSERT_EQ(inst->LHS()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->LHS()->Usage()[0], inst);
+    EXPECT_THAT(inst->LHS()->Usages(), testing::UnorderedElementsAre(Usage{inst, 0u}));
 
     ASSERT_NE(inst->RHS(), nullptr);
-    ASSERT_EQ(inst->RHS()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->RHS()->Usage()[0], inst);
+    EXPECT_THAT(inst->RHS()->Usages(), testing::UnorderedElementsAre(Usage{inst, 1u}));
 }
 
 TEST_F(IR_BinaryTest, Binary_Usage_DuplicateValue) {
     auto val = b.Constant(4_i);
-    const auto* inst = b.And(mod.Types().i32(), val, val);
+    auto* inst = b.And(mod.Types().i32(), val, val);
 
     EXPECT_EQ(inst->Kind(), Binary::Kind::kAnd);
     ASSERT_EQ(inst->LHS(), inst->RHS());
 
     ASSERT_NE(inst->LHS(), nullptr);
-    ASSERT_EQ(inst->LHS()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->LHS()->Usage()[0], inst);
+    EXPECT_THAT(inst->LHS()->Usages(),
+                testing::UnorderedElementsAre(Usage{inst, 0u}, Usage{inst, 1u}));
 }
 
 }  // namespace
diff --git a/src/tint/ir/bitcast_test.cc b/src/tint/ir/bitcast_test.cc
index cab7065..e8ed473 100644
--- a/src/tint/ir/bitcast_test.cc
+++ b/src/tint/ir/bitcast_test.cc
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
+#include "src/tint/ir/builder.h"
 #include "src/tint/ir/constant.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/ir_test_helper.h"
@@ -39,13 +41,12 @@
 }
 
 TEST_F(IR_BitcastTest, Bitcast_Usage) {
-    const auto* inst = b.Bitcast(mod.Types().i32(), b.Constant(4_i));
+    auto* inst = b.Bitcast(mod.Types().i32(), b.Constant(4_i));
 
     const auto args = inst->Args();
     ASSERT_EQ(args.Length(), 1u);
     ASSERT_NE(args[0], nullptr);
-    ASSERT_EQ(args[0]->Usage().Length(), 1u);
-    EXPECT_EQ(args[0]->Usage()[0], inst);
+    EXPECT_THAT(args[0]->Usages(), testing::UnorderedElementsAre(Usage{inst, 0u}));
 }
 
 TEST_F(IR_BitcastTest, Fail_NullValue) {
diff --git a/src/tint/ir/break_if.cc b/src/tint/ir/break_if.cc
index 7bf61c93..1168c54 100644
--- a/src/tint/ir/break_if.cc
+++ b/src/tint/ir/break_if.cc
@@ -31,7 +31,6 @@
 
     AddOperand(condition);
     if (loop_) {
-        loop_->AddUsage(this);
         loop_->Body()->AddInboundBranch(this);
         loop_->Merge()->AddInboundBranch(this);
     }
diff --git a/src/tint/ir/break_if_test.cc b/src/tint/ir/break_if_test.cc
index 54827bc..96363f1 100644
--- a/src/tint/ir/break_if_test.cc
+++ b/src/tint/ir/break_if_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/break_if.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -29,15 +31,10 @@
     auto* arg2 = b.Constant(2_u);
 
     auto* brk = b.BreakIf(cond, loop, utils::Vector{arg1, arg2});
-    ASSERT_EQ(1u, loop->Usage().Length());
-    ASSERT_EQ(1u, cond->Usage().Length());
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
 
-    EXPECT_EQ(brk, loop->Usage()[0]);
-    EXPECT_EQ(brk, cond->Usage()[0]);
-    EXPECT_EQ(brk, arg1->Usage()[0]);
-    EXPECT_EQ(brk, arg2->Usage()[0]);
+    EXPECT_THAT(cond->Usages(), testing::UnorderedElementsAre(Usage{brk, 0u}));
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{brk, 1u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{brk, 2u}));
 }
 
 TEST_F(IR_BreakIfTest, Fail_NullCondition) {
diff --git a/src/tint/ir/builtin_test.cc b/src/tint/ir/builtin_test.cc
index 13fdb41..d5cf04a 100644
--- a/src/tint/ir/builtin_test.cc
+++ b/src/tint/ir/builtin_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/block_param.h"
 #include "src/tint/ir/ir_test_helper.h"
@@ -28,10 +29,8 @@
     auto* builtin =
         b.Builtin(mod.Types().f32(), builtin::Function::kAbs, utils::Vector{arg1, arg2});
 
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
-    EXPECT_EQ(builtin, arg1->Usage()[0]);
-    EXPECT_EQ(builtin, arg2->Usage()[0]);
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{builtin, 0u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{builtin, 1u}));
 }
 
 TEST_F(IR_BuiltinTest, Fail_NullType) {
diff --git a/src/tint/ir/construct_test.cc b/src/tint/ir/construct_test.cc
index 9a2f92f..cdab810 100644
--- a/src/tint/ir/construct_test.cc
+++ b/src/tint/ir/construct_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/construct.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -27,10 +29,8 @@
     auto* arg2 = b.Constant(false);
     auto* c = b.Construct(mod.Types().f32(), utils::Vector{arg1, arg2});
 
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
-    EXPECT_EQ(c, arg1->Usage()[0]);
-    EXPECT_EQ(c, arg2->Usage()[0]);
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{c, 0u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{c, 1u}));
 }
 
 TEST_F(IR_ConstructTest, Fail_NullType) {
diff --git a/src/tint/ir/continue.cc b/src/tint/ir/continue.cc
index 5e025b9..bf8cbf1 100644
--- a/src/tint/ir/continue.cc
+++ b/src/tint/ir/continue.cc
@@ -27,7 +27,6 @@
     TINT_ASSERT(IR, loop_);
 
     if (loop_) {
-        loop_->AddUsage(this);
         loop_->Continuing()->AddInboundBranch(this);
     }
     AddOperands(std::move(args));
diff --git a/src/tint/ir/continue_test.cc b/src/tint/ir/continue_test.cc
index 1cba56e..631d696 100644
--- a/src/tint/ir/continue_test.cc
+++ b/src/tint/ir/continue_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/continue.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -28,13 +30,9 @@
     auto* arg2 = b.Constant(2_u);
 
     auto* brk = b.Continue(loop, utils::Vector{arg1, arg2});
-    ASSERT_EQ(1u, loop->Usage().Length());
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
 
-    EXPECT_EQ(brk, loop->Usage()[0]);
-    EXPECT_EQ(brk, arg1->Usage()[0]);
-    EXPECT_EQ(brk, arg2->Usage()[0]);
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{brk, 0u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{brk, 1u}));
 }
 
 TEST_F(IR_ContinueTest, Fail_NullLoop) {
diff --git a/src/tint/ir/exit_if.cc b/src/tint/ir/exit_if.cc
index f251d45..11cbd74 100644
--- a/src/tint/ir/exit_if.cc
+++ b/src/tint/ir/exit_if.cc
@@ -26,7 +26,6 @@
     TINT_ASSERT(IR, if_);
 
     if (if_) {
-        if_->AddUsage(this);
         if_->Merge()->AddInboundBranch(this);
     }
     AddOperands(std::move(args));
diff --git a/src/tint/ir/exit_if_test.cc b/src/tint/ir/exit_if_test.cc
index 2383449..13e0cc1 100644
--- a/src/tint/ir/exit_if_test.cc
+++ b/src/tint/ir/exit_if_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/exit_if.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -27,13 +29,9 @@
     auto* arg2 = b.Constant(2_u);
     auto* if_ = b.CreateIf(b.Constant(true));
     auto* e = b.ExitIf(if_, utils::Vector{arg1, arg2});
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
-    ASSERT_EQ(1u, if_->Usage().Length());
 
-    EXPECT_EQ(e, arg1->Usage()[0]);
-    EXPECT_EQ(e, arg2->Usage()[0]);
-    EXPECT_EQ(e, if_->Usage()[0]);
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{e, 0u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 1u}));
 }
 
 TEST_F(IR_ExitIfTest, Fail_NullIf) {
diff --git a/src/tint/ir/exit_loop.cc b/src/tint/ir/exit_loop.cc
index 205a458..01b7b8a 100644
--- a/src/tint/ir/exit_loop.cc
+++ b/src/tint/ir/exit_loop.cc
@@ -27,7 +27,6 @@
     TINT_ASSERT(IR, loop_);
 
     if (loop_) {
-        loop_->AddUsage(this);
         loop_->Merge()->AddInboundBranch(this);
     }
     AddOperands(std::move(args));
diff --git a/src/tint/ir/exit_loop_test.cc b/src/tint/ir/exit_loop_test.cc
index 9a66ccc..11a6b28 100644
--- a/src/tint/ir/exit_loop_test.cc
+++ b/src/tint/ir/exit_loop_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/exit_loop.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -27,13 +29,9 @@
     auto* arg2 = b.Constant(2_u);
     auto* loop = b.CreateLoop();
     auto* e = b.ExitLoop(loop, utils::Vector{arg1, arg2});
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
-    ASSERT_EQ(1u, loop->Usage().Length());
 
-    EXPECT_EQ(e, arg1->Usage()[0]);
-    EXPECT_EQ(e, arg2->Usage()[0]);
-    EXPECT_EQ(e, loop->Usage()[0]);
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{e, 0u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 1u}));
 }
 
 TEST_F(IR_ExitLoopTest, Fail_NullLoop) {
diff --git a/src/tint/ir/exit_switch.cc b/src/tint/ir/exit_switch.cc
index 6689742..f63e791 100644
--- a/src/tint/ir/exit_switch.cc
+++ b/src/tint/ir/exit_switch.cc
@@ -27,7 +27,6 @@
     TINT_ASSERT(IR, switch_);
 
     if (switch_) {
-        switch_->AddUsage(this);
         switch_->Merge()->AddInboundBranch(this);
     }
     AddOperands(std::move(args));
diff --git a/src/tint/ir/exit_switch_test.cc b/src/tint/ir/exit_switch_test.cc
index abdd433..945bb5c 100644
--- a/src/tint/ir/exit_switch_test.cc
+++ b/src/tint/ir/exit_switch_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/exit_switch.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -27,13 +29,9 @@
     auto* arg2 = b.Constant(2_u);
     auto* switch_ = b.CreateSwitch(b.Constant(true));
     auto* e = b.ExitSwitch(switch_, utils::Vector{arg1, arg2});
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
-    ASSERT_EQ(1u, switch_->Usage().Length());
 
-    EXPECT_EQ(e, arg1->Usage()[0]);
-    EXPECT_EQ(e, arg2->Usage()[0]);
-    EXPECT_EQ(e, switch_->Usage()[0]);
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{e, 0u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 1u}));
 }
 
 TEST_F(IR_ExitSwitchTest, Fail_NullSwitch) {
diff --git a/src/tint/ir/if_test.cc b/src/tint/ir/if_test.cc
index d83d062..27f1df7 100644
--- a/src/tint/ir/if_test.cc
+++ b/src/tint/ir/if_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/ir/if.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -25,8 +26,7 @@
 TEST_F(IR_IfTest, Usage) {
     auto* cond = b.Constant(true);
     auto* if_ = b.CreateIf(cond);
-    ASSERT_EQ(1u, cond->Usage().Length());
-    EXPECT_EQ(if_, cond->Usage()[0]);
+    EXPECT_THAT(cond->Usages(), testing::UnorderedElementsAre(Usage{if_, 0u}));
 }
 
 TEST_F(IR_IfTest, Fail_NullCondition) {
diff --git a/src/tint/ir/load_test.cc b/src/tint/ir/load_test.cc
index 440eabf..ad21d13 100644
--- a/src/tint/ir/load_test.cc
+++ b/src/tint/ir/load_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/builder.h"
 #include "src/tint/ir/instruction.h"
@@ -24,7 +25,7 @@
 
 using IR_LoadTest = IRTestHelper;
 
-TEST_F(IR_LoadTest, Creat) {
+TEST_F(IR_LoadTest, Create) {
     auto* store_type = mod.Types().i32();
     auto* var = b.Declare(mod.Types().pointer(store_type, builtin::AddressSpace::kFunction,
                                               builtin::Access::kReadWrite));
@@ -43,11 +44,10 @@
     auto* store_type = mod.Types().i32();
     auto* var = b.Declare(mod.Types().pointer(store_type, builtin::AddressSpace::kFunction,
                                               builtin::Access::kReadWrite));
-    const auto* inst = b.Load(var);
+    auto* inst = b.Load(var);
 
     ASSERT_NE(inst->From(), nullptr);
-    ASSERT_EQ(inst->From()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->From()->Usage()[0], inst);
+    EXPECT_THAT(inst->From()->Usages(), testing::UnorderedElementsAre(Usage{inst, 0u}));
 }
 
 TEST_F(IR_LoadTest, Fail_NullType) {
diff --git a/src/tint/ir/next_iteration.cc b/src/tint/ir/next_iteration.cc
index 8e4b1e2..0d18617 100644
--- a/src/tint/ir/next_iteration.cc
+++ b/src/tint/ir/next_iteration.cc
@@ -27,7 +27,6 @@
     TINT_ASSERT(IR, loop_);
 
     if (loop_) {
-        loop_->AddUsage(this);
         loop_->Body()->AddInboundBranch(this);
     }
     AddOperands(std::move(args));
diff --git a/src/tint/ir/operand_instruction.h b/src/tint/ir/operand_instruction.h
index b60d753..3e8dad3 100644
--- a/src/tint/ir/operand_instruction.h
+++ b/src/tint/ir/operand_instruction.h
@@ -31,7 +31,7 @@
     /// @param value the operand value to append
     void AddOperand(ir::Value* value) {
         if (value) {
-            value->AddUsage(this);
+            value->AddUsage({this, static_cast<uint32_t>(operands_.Length())});
         }
         operands_.Push(value);
     }
diff --git a/src/tint/ir/return.cc b/src/tint/ir/return.cc
index f70af84..73bcde0 100644
--- a/src/tint/ir/return.cc
+++ b/src/tint/ir/return.cc
@@ -26,7 +26,7 @@
     TINT_ASSERT(IR, func_);
 
     if (func_) {
-        func_->AddUsage(this);
+        func_->AddUsage({this, 0u});
     }
     AddOperands(std::move(args));
 }
diff --git a/src/tint/ir/store_test.cc b/src/tint/ir/store_test.cc
index f323612..a28ecad 100644
--- a/src/tint/ir/store_test.cc
+++ b/src/tint/ir/store_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/builder.h"
 #include "src/tint/ir/instruction.h"
@@ -40,15 +41,13 @@
 
 TEST_F(IR_StoreTest, Store_Usage) {
     auto* to = b.Discard();
-    const auto* inst = b.Store(to, b.Constant(4_i));
+    auto* inst = b.Store(to, b.Constant(4_i));
 
     ASSERT_NE(inst->To(), nullptr);
-    ASSERT_EQ(inst->To()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->To()->Usage()[0], inst);
+    EXPECT_THAT(inst->To()->Usages(), testing::UnorderedElementsAre(Usage{inst, 0u}));
 
     ASSERT_NE(inst->From(), nullptr);
-    ASSERT_EQ(inst->From()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->From()->Usage()[0], inst);
+    EXPECT_THAT(inst->From()->Usages(), testing::UnorderedElementsAre(Usage{inst, 1u}));
 }
 
 TEST_F(IR_StoreTest, Fail_NullTo) {
diff --git a/src/tint/ir/switch_test.cc b/src/tint/ir/switch_test.cc
index 4d1a0ec..3ea47fb 100644
--- a/src/tint/ir/switch_test.cc
+++ b/src/tint/ir/switch_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/switch.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -24,9 +26,8 @@
 
 TEST_F(IR_SwitchTest, Usage) {
     auto* cond = b.Constant(true);
-    auto* if_ = b.CreateSwitch(cond);
-    ASSERT_EQ(1u, cond->Usage().Length());
-    EXPECT_EQ(if_, cond->Usage()[0]);
+    auto* switch_ = b.CreateSwitch(cond);
+    EXPECT_THAT(cond->Usages(), testing::UnorderedElementsAre(Usage{switch_, 0u}));
 }
 
 TEST_F(IR_SwitchTest, Fail_NullCondition) {
diff --git a/src/tint/ir/swizzle_test.cc b/src/tint/ir/swizzle_test.cc
index 0aa3bbb..64df5c6 100644
--- a/src/tint/ir/swizzle_test.cc
+++ b/src/tint/ir/swizzle_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/swizzle.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -27,8 +29,7 @@
     auto* var = b.Declare(ty);
     auto* a = b.Swizzle(mod.Types().i32(), var, utils::Vector{1u});
 
-    EXPECT_EQ(1u, var->Usage().Length());
-    EXPECT_EQ(a, var->Usage()[0]);
+    EXPECT_THAT(var->Usages(), testing::UnorderedElementsAre(Usage{a, 0u}));
 }
 
 TEST_F(IR_SwizzleTest, Fail_NullType) {
diff --git a/src/tint/ir/to_program.cc b/src/tint/ir/to_program.cc
index 5ecf7d5..b226236 100644
--- a/src/tint/ir/to_program.cc
+++ b/src/tint/ir/to_program.cc
@@ -322,7 +322,7 @@
         // of usage. Currently a value is inlined if it has a single usage and is unnamed.
         // TODO(crbug.com/tint/1902): This logic needs to check that the sequence of side-effecting
         // expressions is not changed by inlining the expression. This needs fixing.
-        bool create_let = val->Usage().Length() > 1 || mod.NameOf(val).IsValid();
+        bool create_let = val->Usages().Count() > 1 || mod.NameOf(val).IsValid();
         if (create_let) {
             auto* init = Expr(val);  // Must come before giving the value a name
             auto name = AssignNameTo(val);
diff --git a/src/tint/ir/unary_test.cc b/src/tint/ir/unary_test.cc
index 2803527..2f2307e 100644
--- a/src/tint/ir/unary_test.cc
+++ b/src/tint/ir/unary_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/builder.h"
 #include "src/tint/ir/instruction.h"
@@ -54,8 +55,7 @@
     EXPECT_EQ(inst->Kind(), Unary::Kind::kNegation);
 
     ASSERT_NE(inst->Val(), nullptr);
-    ASSERT_EQ(inst->Val()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->Val()->Usage()[0], inst);
+    EXPECT_THAT(inst->Val()->Usages(), testing::UnorderedElementsAre(Usage{inst, 0u}));
 }
 
 TEST_F(IR_UnaryTest, Fail_NullType) {
diff --git a/src/tint/ir/user_call_test.cc b/src/tint/ir/user_call_test.cc
index 8e989a5..2fd9151 100644
--- a/src/tint/ir/user_call_test.cc
+++ b/src/tint/ir/user_call_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/user_call.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -23,15 +25,13 @@
 using IR_UserCallTest = IRTestHelper;
 
 TEST_F(IR_UserCallTest, Usage) {
+    auto* func = b.CreateFunction("myfunc", mod.Types().void_());
     auto* arg1 = b.Constant(1_u);
     auto* arg2 = b.Constant(2_u);
-    auto* e = b.UserCall(mod.Types().void_(), b.CreateFunction("myfunc", mod.Types().void_()),
-                         utils::Vector{arg1, arg2});
-    ASSERT_EQ(1u, arg1->Usage().Length());
-    ASSERT_EQ(1u, arg2->Usage().Length());
-
-    EXPECT_EQ(e, arg1->Usage()[0]);
-    EXPECT_EQ(e, arg2->Usage()[0]);
+    auto* e = b.UserCall(mod.Types().void_(), func, utils::Vector{arg1, arg2});
+    EXPECT_THAT(func->Usages(), testing::UnorderedElementsAre(Usage{e, 0u}));
+    EXPECT_THAT(arg1->Usages(), testing::UnorderedElementsAre(Usage{e, 1u}));
+    EXPECT_THAT(arg2->Usages(), testing::UnorderedElementsAre(Usage{e, 2u}));
 }
 
 TEST_F(IR_UserCallTest, Fail_NullType) {
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index b090ad8..6311691 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -17,7 +17,7 @@
 
 #include "src/tint/type/type.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/unique_vector.h"
+#include "src/tint/utils/hashset.h"
 
 // Forward declarations
 namespace tint::ir {
@@ -26,19 +26,43 @@
 
 namespace tint::ir {
 
+/// A specific usage of a Value in the IR.
+struct Usage {
+    /// The instruction that is using the value;
+    Instruction* instruction = nullptr;
+    /// The index of the operand that is the value being used.
+    uint32_t operand_index = 0u;
+
+    /// A specialization of utils::Hasher for Usage.
+    struct Hasher {
+        /// @param u the usage to hash
+        /// @returns a hash of the usage
+        inline std::size_t operator()(const Usage& u) const {
+            return utils::Hash(u.instruction, u.operand_index);
+        }
+    };
+
+    /// An equality helper for Usage.
+    /// @param other the usage to compare against
+    /// @returns true if the two usages are equal
+    bool operator==(const Usage& other) const {
+        return instruction == other.instruction && operand_index == other.operand_index;
+    }
+};
+
 /// Value in the IR.
 class Value : public utils::Castable<Value> {
   public:
     /// Destructor
     ~Value() override;
 
-    /// Adds an instruction which uses this value.
-    /// @param inst the instruction
-    void AddUsage(const Instruction* inst) { uses_.Add(inst); }
+    /// Adds a usage of this value.
+    /// @param u the usage
+    void AddUsage(Usage u) { uses_.Add(u); }
 
-    /// @returns the vector of instructions which use this value. An instruction will only be
-    /// returned once even if that instruction uses the given value multiple times.
-    utils::VectorRef<const Instruction*> Usage() const { return uses_; }
+    /// @returns the set of usages of this value. An instruction may appear multiple times if it
+    /// uses the value for multiple different operands.
+    const utils::Hashset<Usage, 4, Usage::Hasher>& Usages() const { return uses_; }
 
     /// @returns the type of the value
     virtual const type::Type* Type() const { return nullptr; }
@@ -48,9 +72,8 @@
     Value();
 
   private:
-    utils::UniqueVector<const Instruction*, 4> uses_;
+    utils::Hashset<Usage, 4, Usage::Hasher> uses_;
 };
-
 }  // namespace tint::ir
 
 #endif  // SRC_TINT_IR_VALUE_H_
diff --git a/src/tint/ir/var.cc b/src/tint/ir/var.cc
index 6ebe53f..218d512 100644
--- a/src/tint/ir/var.cc
+++ b/src/tint/ir/var.cc
@@ -31,7 +31,7 @@
 void Var::SetInitializer(Value* initializer) {
     operands_[0] = initializer;
     if (initializer) {
-        initializer->AddUsage(this);
+        initializer->AddUsage({this, 0u});
     }
     // TODO(dsinclair): Probably should do a RemoveUsage on an existing initializer if set
 }
diff --git a/src/tint/ir/var_test.cc b/src/tint/ir/var_test.cc
index 92a2c3c..43b1767 100644
--- a/src/tint/ir/var_test.cc
+++ b/src/tint/ir/var_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "src/tint/ir/var.h"
+
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ir/ir_test_helper.h"
 
@@ -39,8 +41,7 @@
     auto* init = b.Constant(1_f);
     var->SetInitializer(init);
 
-    ASSERT_EQ(1u, init->Usage().Length());
-    EXPECT_EQ(var, init->Usage()[0]);
+    EXPECT_THAT(init->Usages(), testing::UnorderedElementsAre(Usage{var, 0u}));
 }
 
 }  // namespace