[tint][ir] Use 'const' on non-mutating IR consumers

Change-Id: Ieee80631de6c522c7679ca7816726a408b30682d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/161012
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/ir/disassembler.cc b/src/tint/lang/core/ir/disassembler.cc
index 6147550..e505e1f 100644
--- a/src/tint/lang/core/ir/disassembler.cc
+++ b/src/tint/lang/core/ir/disassembler.cc
@@ -87,11 +87,11 @@
 
 }  // namespace
 
-std::string Disassemble(Module& mod) {
+std::string Disassemble(const Module& mod) {
     return Disassembler{mod}.Disassemble();
 }
 
-Disassembler::Disassembler(Module& mod) : mod_(mod) {}
+Disassembler::Disassembler(const Module& mod) : mod_(mod) {}
 
 Disassembler::~Disassembler() = default;
 
@@ -108,12 +108,12 @@
     current_output_start_pos_ = out_.tellp();
 }
 
-size_t Disassembler::IdOf(Block* node) {
+size_t Disassembler::IdOf(const Block* node) {
     TINT_ASSERT(node);
     return block_ids_.GetOrCreate(node, [&] { return block_ids_.Count(); });
 }
 
-std::string Disassembler::IdOf(Value* value) {
+std::string Disassembler::IdOf(const Value* value) {
     TINT_ASSERT(value);
     return value_ids_.GetOrCreate(value, [&] {
         if (auto sym = mod_.NameOf(value)) {
@@ -132,7 +132,7 @@
     });
 }
 
-std::string Disassembler::NameOf(If* inst) {
+std::string Disassembler::NameOf(const If* inst) {
     if (!inst) {
         return "undef";
     }
@@ -140,7 +140,7 @@
     return if_names_.GetOrCreate(inst, [&] { return "if_" + std::to_string(if_names_.Count()); });
 }
 
-std::string Disassembler::NameOf(Loop* inst) {
+std::string Disassembler::NameOf(const Loop* inst) {
     if (!inst) {
         return "undef";
     }
@@ -149,7 +149,7 @@
                                    [&] { return "loop_" + std::to_string(loop_names_.Count()); });
 }
 
-std::string Disassembler::NameOf(Switch* inst) {
+std::string Disassembler::NameOf(const Switch* inst) {
     if (!inst) {
         return "undef";
     }
@@ -180,7 +180,7 @@
     return out_.str();
 }
 
-void Disassembler::EmitBlock(Block* blk, std::string_view comment /* = "" */) {
+void Disassembler::EmitBlock(const Block* blk, std::string_view comment /* = "" */) {
     Indent();
 
     SourceMarker sm(this);
@@ -229,7 +229,7 @@
     }
 }
 
-void Disassembler::EmitParamAttributes(FunctionParam* p) {
+void Disassembler::EmitParamAttributes(const FunctionParam* p) {
     if (!p->Invariant() && !p->Location().has_value() && !p->BindingPoint().has_value() &&
         !p->Builtin().has_value()) {
         return;
@@ -266,7 +266,7 @@
     out_ << "]";
 }
 
-void Disassembler::EmitReturnAttributes(Function* func) {
+void Disassembler::EmitReturnAttributes(const Function* func) {
     if (!func->ReturnInvariant() && !func->ReturnLocation().has_value() &&
         !func->ReturnBuiltin().has_value()) {
         return;
@@ -298,7 +298,7 @@
     out_ << "]";
 }
 
-void Disassembler::EmitFunction(Function* func) {
+void Disassembler::EmitFunction(const Function* func) {
     in_function_ = true;
 
     std::string fn_id = IdOf(func);
@@ -358,17 +358,17 @@
     EmitLine();
 }
 
-void Disassembler::EmitValueWithType(Instruction* val) {
+void Disassembler::EmitValueWithType(const Instruction* val) {
     SourceMarker sm(this);
     if (val->Result(0)) {
         EmitValueWithType(val->Result(0));
     } else {
         out_ << "undef";
     }
-    sm.StoreResult(Usage{val, 0});
+    sm.StoreResult(IndexedValue{val, 0});
 }
 
-void Disassembler::EmitValueWithType(Value* val) {
+void Disassembler::EmitValueWithType(const Value* val) {
     if (!val) {
         out_ << "undef";
         return;
@@ -378,10 +378,10 @@
     out_ << ":" << val->Type()->FriendlyName();
 }
 
-void Disassembler::EmitValue(Value* val) {
+void Disassembler::EmitValue(const Value* val) {
     tint::Switch(
         val,
-        [&](ir::Constant* constant) {
+        [&](const ir::Constant* constant) {
             std::function<void(const core::constant::Value*)> emit =
                 [&](const core::constant::Value* c) {
                     tint::Switch(
@@ -427,10 +427,12 @@
                 };
             emit(constant->Value());
         },
-        [&](ir::InstructionResult* rv) { out_ << "%" << IdOf(rv); },
-        [&](ir::BlockParam* p) { out_ << "%" << IdOf(p) << ":" << p->Type()->FriendlyName(); },
-        [&](ir::FunctionParam* p) { out_ << "%" << IdOf(p); },
-        [&](ir::Function* f) { out_ << "%" << IdOf(f); },
+        [&](const ir::InstructionResult* rv) { out_ << "%" << IdOf(rv); },
+        [&](const ir::BlockParam* p) {
+            out_ << "%" << IdOf(p) << ":" << p->Type()->FriendlyName();
+        },
+        [&](const ir::FunctionParam* p) { out_ << "%" << IdOf(p); },
+        [&](const ir::Function* f) { out_ << "%" << IdOf(f); },
         [&](Default) {
             if (val == nullptr) {
                 out_ << "undef";
@@ -440,13 +442,13 @@
         });
 }
 
-void Disassembler::EmitInstructionName(Instruction* inst) {
+void Disassembler::EmitInstructionName(const Instruction* inst) {
     SourceMarker sm(this);
     out_ << inst->FriendlyName();
     sm.Store(inst);
 }
 
-void Disassembler::EmitInstruction(Instruction* inst) {
+void Disassembler::EmitInstruction(const Instruction* inst) {
     TINT_DEFER(EmitLine());
 
     if (!inst->Alive()) {
@@ -456,26 +458,26 @@
         return;
     }
     tint::Switch(
-        inst,                               //
-        [&](Switch* s) { EmitSwitch(s); },  //
-        [&](If* i) { EmitIf(i); },          //
-        [&](Loop* l) { EmitLoop(l); },      //
-        [&](Binary* b) { EmitBinary(b); },  //
-        [&](Unary* u) { EmitUnary(u); },    //
-        [&](Discard* d) { EmitInstructionName(d); },
-        [&](Store* s) {
+        inst,                                     //
+        [&](const Switch* s) { EmitSwitch(s); },  //
+        [&](const If* i) { EmitIf(i); },          //
+        [&](const Loop* l) { EmitLoop(l); },      //
+        [&](const Binary* b) { EmitBinary(b); },  //
+        [&](const Unary* u) { EmitUnary(u); },    //
+        [&](const Discard* d) { EmitInstructionName(d); },
+        [&](const Store* s) {
             EmitInstructionName(s);
             out_ << " ";
             EmitOperand(s, Store::kToOperandOffset);
             out_ << ", ";
             EmitOperand(s, Store::kFromOperandOffset);
         },
-        [&](StoreVectorElement* s) {
+        [&](const StoreVectorElement* s) {
             EmitInstructionName(s);
             out_ << " ";
             EmitOperandList(s);
         },
-        [&](UserCall* uc) {
+        [&](const UserCall* uc) {
             EmitValueWithType(uc);
             out_ << " = ";
             EmitInstructionName(uc);
@@ -486,7 +488,7 @@
             }
             EmitOperandList(uc, UserCall::kArgsOperandOffset);
         },
-        [&](Var* v) {
+        [&](const Var* v) {
             EmitValueWithType(v);
             out_ << " = ";
             EmitInstructionName(v);
@@ -519,7 +521,7 @@
                 out_ << " @builtin(" << v->Attributes().builtin.value() << ")";
             }
         },
-        [&](Swizzle* s) {
+        [&](const Swizzle* s) {
             EmitValueWithType(s);
             out_ << " = ";
             EmitInstructionName(s);
@@ -543,7 +545,7 @@
                 }
             }
         },
-        [&](Terminator* b) { EmitTerminator(b); },
+        [&](const Terminator* b) { EmitTerminator(b); },
         [&](Default) {
             EmitValueWithType(inst);
             out_ << " = ";
@@ -572,13 +574,13 @@
     }
 }
 
-void Disassembler::EmitOperand(Instruction* inst, size_t index) {
+void Disassembler::EmitOperand(const Instruction* inst, size_t index) {
     SourceMarker marker(this);
     EmitValue(inst->Operands()[index]);
-    marker.Store(Usage{inst, static_cast<uint32_t>(index)});
+    marker.Store(IndexedValue{inst, static_cast<uint32_t>(index)});
 }
 
-void Disassembler::EmitOperandList(Instruction* inst, size_t start_index /* = 0 */) {
+void Disassembler::EmitOperandList(const Instruction* inst, size_t start_index /* = 0 */) {
     for (size_t i = start_index, n = inst->Operands().Length(); i < n; i++) {
         if (i != start_index) {
             out_ << ", ";
@@ -587,7 +589,7 @@
     }
 }
 
-void Disassembler::EmitIf(If* if_) {
+void Disassembler::EmitIf(const If* if_) {
     SourceMarker sm(this);
     if (auto results = if_->Results(); !results.IsEmpty()) {
         for (size_t i = 0; i < results.Length(); ++i) {
@@ -596,7 +598,7 @@
             }
             SourceMarker rs(this);
             EmitValueWithType(results[i]);
-            rs.StoreResult(Usage{if_, i});
+            rs.StoreResult(IndexedValue{if_, i});
         }
         out_ << " = ";
     }
@@ -638,7 +640,7 @@
     out_ << "}";
 }
 
-void Disassembler::EmitLoop(Loop* l) {
+void Disassembler::EmitLoop(const Loop* l) {
     Vector<std::string, 3> parts;
     if (!l->Initializer()->IsEmpty()) {
         parts.Push("i: %b" + std::to_string(IdOf(l->Initializer())));
@@ -656,7 +658,7 @@
             }
             SourceMarker rs(this);
             EmitValueWithType(results[i]);
-            rs.StoreResult(Usage{l, i});
+            rs.StoreResult(IndexedValue{l, i});
         }
         out_ << " = ";
     }
@@ -686,7 +688,7 @@
     out_ << "}";
 }
 
-void Disassembler::EmitSwitch(Switch* s) {
+void Disassembler::EmitSwitch(const Switch* s) {
     SourceMarker sm(this);
     if (auto results = s->Results(); !results.IsEmpty()) {
         for (size_t i = 0; i < results.Length(); ++i) {
@@ -695,7 +697,7 @@
             }
             SourceMarker rs(this);
             EmitValueWithType(results[i]);
-            rs.StoreResult(Usage{s, i});
+            rs.StoreResult(IndexedValue{s, i});
         }
         out_ << " = ";
     }
@@ -735,43 +737,43 @@
     out_ << "}";
 }
 
-void Disassembler::EmitTerminator(Terminator* b) {
+void Disassembler::EmitTerminator(const Terminator* b) {
     SourceMarker sm(this);
     size_t args_offset = 0;
     tint::Switch(
         b,
-        [&](ir::Return*) {
+        [&](const ir::Return*) {
             out_ << "ret";
             args_offset = ir::Return::kArgsOperandOffset;
         },
-        [&](ir::Continue* cont) {
+        [&](const ir::Continue* cont) {
             out_ << "continue %b" << IdOf(cont->Loop()->Continuing());
             args_offset = ir::Continue::kArgsOperandOffset;
         },
-        [&](ir::ExitIf*) {
+        [&](const ir::ExitIf*) {
             out_ << "exit_if";
             args_offset = ir::ExitIf::kArgsOperandOffset;
         },
-        [&](ir::ExitSwitch*) {
+        [&](const ir::ExitSwitch*) {
             out_ << "exit_switch";
             args_offset = ir::ExitSwitch::kArgsOperandOffset;
         },
-        [&](ir::ExitLoop*) {
+        [&](const ir::ExitLoop*) {
             out_ << "exit_loop";
             args_offset = ir::ExitLoop::kArgsOperandOffset;
         },
-        [&](ir::NextIteration* ni) {
+        [&](const ir::NextIteration* ni) {
             out_ << "next_iteration %b" << IdOf(ni->Loop()->Body());
             args_offset = ir::NextIteration::kArgsOperandOffset;
         },
-        [&](ir::Unreachable*) { out_ << "unreachable"; },
-        [&](ir::BreakIf* bi) {
+        [&](const ir::Unreachable*) { out_ << "unreachable"; },
+        [&](const ir::BreakIf* bi) {
             out_ << "break_if ";
             EmitValue(bi->Condition());
             out_ << " %b" << IdOf(bi->Loop()->Body());
             args_offset = ir::BreakIf::kArgsOperandOffset;
         },
-        [&](ir::TerminateInvocation*) { out_ << "terminate_invocation"; },
+        [&](const ir::TerminateInvocation*) { out_ << "terminate_invocation"; },
         [&](Default) { out_ << "unknown terminator " << b->TypeInfo().name; });
 
     if (!b->Args().IsEmpty()) {
@@ -781,14 +783,14 @@
     sm.Store(b);
 
     tint::Switch(
-        b,                                                                  //
-        [&](ir::ExitIf* e) { out_ << "  # " << NameOf(e->If()); },          //
-        [&](ir::ExitSwitch* e) { out_ << "  # " << NameOf(e->Switch()); },  //
-        [&](ir::ExitLoop* e) { out_ << "  # " << NameOf(e->Loop()); }       //
+        b,                                                                        //
+        [&](const ir::ExitIf* e) { out_ << "  # " << NameOf(e->If()); },          //
+        [&](const ir::ExitSwitch* e) { out_ << "  # " << NameOf(e->Switch()); },  //
+        [&](const ir::ExitLoop* e) { out_ << "  # " << NameOf(e->Loop()); }       //
     );
 }
 
-void Disassembler::EmitValueList(tint::Slice<Value* const> values) {
+void Disassembler::EmitValueList(tint::Slice<const Value* const> values) {
     for (size_t i = 0, n = values.Length(); i < n; i++) {
         if (i > 0) {
             out_ << ", ";
@@ -797,7 +799,7 @@
     }
 }
 
-void Disassembler::EmitBinary(Binary* b) {
+void Disassembler::EmitBinary(const Binary* b) {
     SourceMarker sm(this);
     EmitValueWithType(b);
     out_ << " = ";
@@ -857,7 +859,7 @@
     sm.Store(b);
 }
 
-void Disassembler::EmitUnary(Unary* u) {
+void Disassembler::EmitUnary(const Unary* u) {
     SourceMarker sm(this);
     EmitValueWithType(u);
     out_ << " = ";
diff --git a/src/tint/lang/core/ir/disassembler.h b/src/tint/lang/core/ir/disassembler.h
index a22182d..8f15e95 100644
--- a/src/tint/lang/core/ir/disassembler.h
+++ b/src/tint/lang/core/ir/disassembler.h
@@ -51,14 +51,32 @@
 
 /// @returns the disassembly for the module @p mod
 /// @param mod the module to disassemble
-std::string Disassemble(Module& mod);
+std::string Disassemble(const Module& mod);
 
 /// Helper class to disassemble the IR
 class Disassembler {
   public:
+    /// A reference to an instruction's operand or result.
+    struct IndexedValue {
+        /// The instruction that is using the value;
+        const Instruction* instruction = nullptr;
+        /// The index of the operand that is the value being used.
+        size_t index = 0u;
+
+        /// @returns the hash code of the IndexedValue
+        size_t HashCode() const { return Hash(instruction, index); }
+
+        /// An equality helper for IndexedValue.
+        /// @param other the IndexedValue to compare against
+        /// @returns true if the two IndexedValues are equal
+        bool operator==(const IndexedValue& other) const {
+            return instruction == other.instruction && index == other.index;
+        }
+    };
+
     /// Constructor
     /// @param mod the module
-    explicit Disassembler(Module& mod);
+    explicit Disassembler(const Module& mod);
     ~Disassembler();
 
     /// Returns the module as a string
@@ -70,41 +88,45 @@
 
     /// @param inst the instruction to retrieve
     /// @returns the source for the instruction
-    Source InstructionSource(Instruction* inst) {
+    Source InstructionSource(const Instruction* inst) {
         return instruction_to_src_.Get(inst).value_or(Source{});
     }
 
     /// @param operand the operand to retrieve
     /// @returns the source for the operand
-    Source OperandSource(Usage operand) { return operand_to_src_.Get(operand).value_or(Source{}); }
+    Source OperandSource(IndexedValue operand) {
+        return operand_to_src_.Get(operand).value_or(Source{});
+    }
 
     /// @param result the result to retrieve
     /// @returns the source for the result
-    Source ResultSource(Usage result) { return result_to_src_.Get(result).value_or(Source{}); }
+    Source ResultSource(IndexedValue result) {
+        return result_to_src_.Get(result).value_or(Source{});
+    }
 
     /// @param blk teh block to retrieve
     /// @returns the source for the block
-    Source BlockSource(Block* blk) { return block_to_src_.Get(blk).value_or(Source{}); }
+    Source BlockSource(const Block* blk) { return block_to_src_.Get(blk).value_or(Source{}); }
 
     /// Stores the given @p src location for @p inst instruction
     /// @param inst the instruction to store
     /// @param src the source location
-    void SetSource(Instruction* inst, Source src) { instruction_to_src_.Add(inst, src); }
+    void SetSource(const Instruction* inst, Source src) { instruction_to_src_.Add(inst, src); }
 
     /// Stores the given @p src location for @p blk block
     /// @param blk the block to store
     /// @param src the source location
-    void SetSource(Block* blk, Source src) { block_to_src_.Add(blk, src); }
+    void SetSource(const Block* blk, Source src) { block_to_src_.Add(blk, src); }
 
     /// Stores the given @p src location for @p op operand
     /// @param op the operand to store
     /// @param src the source location
-    void SetSource(Usage op, Source src) { operand_to_src_.Add(op, src); }
+    void SetSource(IndexedValue op, Source src) { operand_to_src_.Add(op, src); }
 
     /// Stores the given @p src location for @p result
     /// @param result the result to store
     /// @param src the source location
-    void SetResultSource(Usage result, Source src) { result_to_src_.Add(result, src); }
+    void SetResultSource(IndexedValue result, Source src) { result_to_src_.Add(result, src); }
 
     /// @returns the source location for the current emission location
     Source::Location MakeCurrentLocation();
@@ -115,13 +137,13 @@
         explicit SourceMarker(Disassembler* d) : dis_(d), begin_(dis_->MakeCurrentLocation()) {}
         ~SourceMarker() = default;
 
-        void Store(Instruction* inst) { dis_->SetSource(inst, MakeSource()); }
+        void Store(const Instruction* inst) { dis_->SetSource(inst, MakeSource()); }
 
-        void Store(Block* blk) { dis_->SetSource(blk, MakeSource()); }
+        void Store(const Block* blk) { dis_->SetSource(blk, MakeSource()); }
 
-        void Store(Usage operand) { dis_->SetSource(operand, MakeSource()); }
+        void Store(IndexedValue operand) { dis_->SetSource(operand, MakeSource()); }
 
-        void StoreResult(Usage result) { dis_->SetResultSource(result, MakeSource()); }
+        void StoreResult(IndexedValue result) { dis_->SetResultSource(result, MakeSource()); }
 
         Source MakeSource() const {
             return Source(Source::Range(begin_, dis_->MakeCurrentLocation()));
@@ -134,39 +156,39 @@
 
     StringStream& Indent();
 
-    size_t IdOf(Block* blk);
-    std::string IdOf(Value* node);
-    std::string NameOf(If* inst);
-    std::string NameOf(Loop* inst);
-    std::string NameOf(Switch* inst);
+    size_t IdOf(const Block* blk);
+    std::string IdOf(const Value* node);
+    std::string NameOf(const If* inst);
+    std::string NameOf(const Loop* inst);
+    std::string NameOf(const Switch* inst);
 
-    void EmitBlock(Block* blk, std::string_view comment = "");
-    void EmitFunction(Function* func);
-    void EmitParamAttributes(FunctionParam* p);
-    void EmitReturnAttributes(Function* func);
+    void EmitBlock(const Block* blk, std::string_view comment = "");
+    void EmitFunction(const Function* func);
+    void EmitParamAttributes(const FunctionParam* p);
+    void EmitReturnAttributes(const Function* func);
     void EmitBindingPoint(BindingPoint p);
     void EmitLocation(Location loc);
-    void EmitInstruction(Instruction* inst);
-    void EmitValueWithType(Instruction* val);
-    void EmitValueWithType(Value* val);
-    void EmitValue(Value* val);
-    void EmitValueList(tint::Slice<ir::Value* const> values);
-    void EmitBinary(Binary* b);
-    void EmitUnary(Unary* b);
-    void EmitTerminator(Terminator* b);
-    void EmitSwitch(Switch* s);
-    void EmitLoop(Loop* l);
-    void EmitIf(If* i);
+    void EmitInstruction(const Instruction* inst);
+    void EmitValueWithType(const Instruction* val);
+    void EmitValueWithType(const Value* val);
+    void EmitValue(const Value* val);
+    void EmitValueList(tint::Slice<const ir::Value* const> values);
+    void EmitBinary(const Binary* b);
+    void EmitUnary(const Unary* b);
+    void EmitTerminator(const Terminator* b);
+    void EmitSwitch(const Switch* s);
+    void EmitLoop(const Loop* l);
+    void EmitIf(const If* i);
     void EmitStructDecl(const core::type::Struct* str);
     void EmitLine();
-    void EmitOperand(Instruction* inst, size_t index);
-    void EmitOperandList(Instruction* inst, size_t start_index = 0);
-    void EmitInstructionName(Instruction* inst);
+    void EmitOperand(const Instruction* inst, size_t index);
+    void EmitOperandList(const Instruction* inst, size_t start_index = 0);
+    void EmitInstructionName(const Instruction* inst);
 
-    Module& mod_;
+    const Module& mod_;
     StringStream out_;
-    Hashmap<Block*, size_t, 32> block_ids_;
-    Hashmap<Value*, std::string, 32> value_ids_;
+    Hashmap<const Block*, size_t, 32> block_ids_;
+    Hashmap<const Value*, std::string, 32> value_ids_;
     Hashset<std::string, 32> ids_;
     uint32_t indent_size_ = 0;
     bool in_function_ = false;
@@ -174,13 +196,13 @@
     uint32_t current_output_line_ = 1;
     uint32_t current_output_start_pos_ = 0;
 
-    Hashmap<Block*, Source, 8> block_to_src_;
-    Hashmap<Instruction*, Source, 8> instruction_to_src_;
-    Hashmap<Usage, Source, 8> operand_to_src_;
-    Hashmap<Usage, Source, 8> result_to_src_;
-    Hashmap<If*, std::string, 8> if_names_;
-    Hashmap<Loop*, std::string, 8> loop_names_;
-    Hashmap<Switch*, std::string, 8> switch_names_;
+    Hashmap<const Block*, Source, 8> block_to_src_;
+    Hashmap<const Instruction*, Source, 8> instruction_to_src_;
+    Hashmap<IndexedValue, Source, 8> operand_to_src_;
+    Hashmap<IndexedValue, Source, 8> result_to_src_;
+    Hashmap<const If*, std::string, 8> if_names_;
+    Hashmap<const Loop*, std::string, 8> loop_names_;
+    Hashmap<const Switch*, std::string, 8> switch_names_;
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 02f6d98..f149306 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -90,7 +90,7 @@
   public:
     /// Create a core validator
     /// @param mod the module to be validated
-    explicit Validator(Module& mod);
+    explicit Validator(const Module& mod);
 
     /// Destructor
     ~Validator();
@@ -103,47 +103,47 @@
     /// @param inst the instruction
     /// @param err the error message
     /// @returns a string with the instruction name name and error message formatted
-    std::string InstError(Instruction* inst, std::string err);
+    std::string InstError(const Instruction* inst, std::string err);
 
     /// Adds an error for the @p inst and highlights the instruction in the disassembly
     /// @param inst the instruction
     /// @param err the error string
-    void AddError(Instruction* inst, std::string err);
+    void AddError(const Instruction* inst, std::string err);
 
     /// Adds an error for the @p inst operand at @p idx and highlights the operand in the
     /// disassembly
     /// @param inst the instaruction
     /// @param idx the operand index
     /// @param err the error string
-    void AddError(Instruction* inst, size_t idx, std::string err);
+    void AddError(const Instruction* inst, size_t idx, std::string err);
 
     /// Adds an error for the @p inst result at @p idx and highlgihts the result in the disassembly
     /// @param inst the instruction
     /// @param idx the result index
     /// @param err the error string
-    void AddResultError(Instruction* inst, size_t idx, std::string err);
+    void AddResultError(const Instruction* inst, size_t idx, std::string err);
 
     /// Adds an error the @p block and highlights the block header in the disassembly
     /// @param blk the block
     /// @param err the error string
-    void AddError(Block* blk, std::string err);
+    void AddError(const Block* blk, std::string err);
 
     /// Adds a note to @p inst and highlights the instruction in the disassembly
     /// @param inst the instruction
     /// @param err the message to emit
-    void AddNote(Instruction* inst, std::string err);
+    void AddNote(const Instruction* inst, std::string err);
 
     /// Adds a note to @p inst for operand @p idx and highlights the operand in the
     /// disassembly
     /// @param inst the instruction
     /// @param idx the operand index
     /// @param err the message string
-    void AddNote(Instruction* inst, size_t idx, std::string err);
+    void AddNote(const Instruction* inst, size_t idx, std::string err);
 
     /// Adds a note to @p blk and highlights the block in the disassembly
     /// @param blk the block
     /// @param err the message to emit
-    void AddNote(Block* blk, std::string err);
+    void AddNote(const Block* blk, std::string err);
 
     /// Adds an error to the diagnostics
     /// @param err the message to emit
@@ -157,141 +157,143 @@
 
     /// @param v the value to get the name for
     /// @returns the name for the given value
-    std::string Name(Value* v);
+    std::string Name(const Value* v);
 
     /// Checks the given operand is not null
     /// @param inst the instruction
     /// @param operand the operand
     /// @param idx the operand index
-    void CheckOperandNotNull(ir::Instruction* inst, ir::Value* operand, size_t idx);
+    void CheckOperandNotNull(const ir::Instruction* inst, const ir::Value* operand, size_t idx);
 
     /// Checks all operands in the given range (inclusive) for @p inst are not null
     /// @param inst the instruction
     /// @param start_operand the first operand to check
     /// @param end_operand the last operand to check
-    void CheckOperandsNotNull(ir::Instruction* inst, size_t start_operand, size_t end_operand);
+    void CheckOperandsNotNull(const ir::Instruction* inst,
+                              size_t start_operand,
+                              size_t end_operand);
 
     /// Validates the root block
     /// @param blk the block
-    void CheckRootBlock(Block* blk);
+    void CheckRootBlock(const Block* blk);
 
     /// Validates the given function
     /// @param func the function validate
-    void CheckFunction(Function* func);
+    void CheckFunction(const Function* func);
 
     /// Validates the given block
     /// @param blk the block to validate
-    void CheckBlock(Block* blk);
+    void CheckBlock(const Block* blk);
 
     /// Validates the given instruction
     /// @param inst the instruction to validate
-    void CheckInstruction(Instruction* inst);
+    void CheckInstruction(const Instruction* inst);
 
     /// Validates the given var
     /// @param var the var to validate
-    void CheckVar(Var* var);
+    void CheckVar(const Var* var);
 
     /// Validates the given let
     /// @param let the let to validate
-    void CheckLet(Let* let);
+    void CheckLet(const Let* let);
 
     /// Validates the given call
     /// @param call the call to validate
-    void CheckCall(Call* call);
+    void CheckCall(const Call* call);
 
     /// Validates the given builtin call
     /// @param call the call to validate
-    void CheckBuiltinCall(BuiltinCall* call);
+    void CheckBuiltinCall(const BuiltinCall* call);
 
     /// Validates the given user call
     /// @param call the call to validate
-    void CheckUserCall(UserCall* call);
+    void CheckUserCall(const UserCall* call);
 
     /// Validates the given access
     /// @param a the access to validate
-    void CheckAccess(Access* a);
+    void CheckAccess(const Access* a);
 
     /// Validates the given binary
     /// @param b the binary to validate
-    void CheckBinary(Binary* b);
+    void CheckBinary(const Binary* b);
 
     /// Validates the given unary
     /// @param u the unary to validate
-    void CheckUnary(Unary* u);
+    void CheckUnary(const Unary* u);
 
     /// Validates the given if
     /// @param if_ the if to validate
-    void CheckIf(If* if_);
+    void CheckIf(const If* if_);
 
     /// Validates the given loop
     /// @param l the loop to validate
-    void CheckLoop(Loop* l);
+    void CheckLoop(const Loop* l);
 
     /// Validates the given switch
     /// @param s the switch to validate
-    void CheckSwitch(Switch* s);
+    void CheckSwitch(const Switch* s);
 
     /// Validates the given terminator
     /// @param b the terminator to validate
-    void CheckTerminator(Terminator* b);
+    void CheckTerminator(const Terminator* b);
 
     /// Validates the given exit
     /// @param e the exit to validate
-    void CheckExit(Exit* e);
+    void CheckExit(const Exit* e);
 
     /// Validates the given exit if
     /// @param e the exit if to validate
-    void CheckExitIf(ExitIf* e);
+    void CheckExitIf(const ExitIf* e);
 
     /// Validates the given return
     /// @param r the return to validate
-    void CheckReturn(Return* r);
+    void CheckReturn(const Return* r);
 
     /// Validates the @p exit targets a valid @p control instruction where the instruction may jump
     /// over if control instructions.
     /// @param exit the exit to validate
     /// @param control the control instruction targeted
-    void CheckControlsAllowingIf(Exit* exit, Instruction* control);
+    void CheckControlsAllowingIf(const Exit* exit, const Instruction* control);
 
     /// Validates the given exit switch
     /// @param s the exit switch to validate
-    void CheckExitSwitch(ExitSwitch* s);
+    void CheckExitSwitch(const ExitSwitch* s);
 
     /// Validates the given exit loop
     /// @param l the exit loop to validate
-    void CheckExitLoop(ExitLoop* l);
+    void CheckExitLoop(const ExitLoop* l);
 
     /// Validates the given store
     /// @param s the store to validate
-    void CheckStore(Store* s);
+    void CheckStore(const Store* s);
 
     /// Validates the given load vector element
     /// @param l the load vector element to validate
-    void CheckLoadVectorElement(LoadVectorElement* l);
+    void CheckLoadVectorElement(const LoadVectorElement* l);
 
     /// Validates the given store vector element
     /// @param s the store vector element to validate
-    void CheckStoreVectorElement(StoreVectorElement* s);
+    void CheckStoreVectorElement(const StoreVectorElement* s);
 
     /// @param inst the instruction
     /// @param idx the operand index
     /// @returns the vector pointer type for the given instruction operand
-    const core::type::Type* GetVectorPtrElementType(Instruction* inst, size_t idx);
+    const core::type::Type* GetVectorPtrElementType(const Instruction* inst, size_t idx);
 
   private:
-    Module& mod_;
+    const Module& mod_;
     std::shared_ptr<Source::File> disassembly_file;
     diag::List diagnostics_;
     Disassembler dis_{mod_};
-    Block* current_block_ = nullptr;
-    Hashset<Function*, 4> all_functions_;
-    Hashset<Instruction*, 4> visited_instructions_;
-    Vector<ControlInstruction*, 8> control_stack_;
+    const Block* current_block_ = nullptr;
+    Hashset<const Function*, 4> all_functions_;
+    Hashset<const Instruction*, 4> visited_instructions_;
+    Vector<const ControlInstruction*, 8> control_stack_;
 
     void DisassembleIfNeeded();
 };
 
-Validator::Validator(Module& mod) : mod_(mod) {}
+Validator::Validator(const Module& mod) : mod_(mod) {}
 
 Validator::~Validator() = default;
 
@@ -333,11 +335,11 @@
     return Success;
 }
 
-std::string Validator::InstError(Instruction* inst, std::string err) {
+std::string Validator::InstError(const Instruction* inst, std::string err) {
     return std::string(inst->FriendlyName()) + ": " + err;
 }
 
-void Validator::AddError(Instruction* inst, std::string err) {
+void Validator::AddError(const Instruction* inst, std::string err) {
     DisassembleIfNeeded();
     auto src = dis_.InstructionSource(inst);
     AddError(std::move(err), src);
@@ -347,9 +349,9 @@
     }
 }
 
-void Validator::AddError(Instruction* inst, size_t idx, std::string err) {
+void Validator::AddError(const Instruction* inst, size_t idx, std::string err) {
     DisassembleIfNeeded();
-    auto src = dis_.OperandSource(Usage{inst, static_cast<uint32_t>(idx)});
+    auto src = dis_.OperandSource(Disassembler::IndexedValue{inst, static_cast<uint32_t>(idx)});
     AddError(std::move(err), src);
 
     if (current_block_) {
@@ -357,9 +359,9 @@
     }
 }
 
-void Validator::AddResultError(Instruction* inst, size_t idx, std::string err) {
+void Validator::AddResultError(const Instruction* inst, size_t idx, std::string err) {
     DisassembleIfNeeded();
-    auto src = dis_.ResultSource(Usage{inst, static_cast<uint32_t>(idx)});
+    auto src = dis_.ResultSource(Disassembler::IndexedValue{inst, static_cast<uint32_t>(idx)});
     AddError(std::move(err), src);
 
     if (current_block_) {
@@ -367,25 +369,25 @@
     }
 }
 
-void Validator::AddError(Block* blk, std::string err) {
+void Validator::AddError(const Block* blk, std::string err) {
     DisassembleIfNeeded();
     auto src = dis_.BlockSource(blk);
     AddError(std::move(err), src);
 }
 
-void Validator::AddNote(Instruction* inst, std::string err) {
+void Validator::AddNote(const Instruction* inst, std::string err) {
     DisassembleIfNeeded();
     auto src = dis_.InstructionSource(inst);
     AddNote(std::move(err), src);
 }
 
-void Validator::AddNote(Instruction* inst, size_t idx, std::string err) {
+void Validator::AddNote(const Instruction* inst, size_t idx, std::string err) {
     DisassembleIfNeeded();
-    auto src = dis_.OperandSource(Usage{inst, static_cast<uint32_t>(idx)});
+    auto src = dis_.OperandSource(Disassembler::IndexedValue{inst, static_cast<uint32_t>(idx)});
     AddNote(std::move(err), src);
 }
 
-void Validator::AddNote(Block* blk, std::string err) {
+void Validator::AddNote(const Block* blk, std::string err) {
     DisassembleIfNeeded();
     auto src = dis_.BlockSource(blk);
     AddNote(std::move(err), src);
@@ -407,24 +409,26 @@
     }
 }
 
-std::string Validator::Name(Value* v) {
+std::string Validator::Name(const Value* v) {
     return mod_.NameOf(v).Name();
 }
 
-void Validator::CheckOperandNotNull(Instruction* inst, ir::Value* operand, size_t idx) {
+void Validator::CheckOperandNotNull(const Instruction* inst, const ir::Value* operand, size_t idx) {
     if (operand == nullptr) {
         AddError(inst, idx, InstError(inst, "operand is undefined"));
     }
 }
 
-void Validator::CheckOperandsNotNull(Instruction* inst, size_t start_operand, size_t end_operand) {
+void Validator::CheckOperandsNotNull(const Instruction* inst,
+                                     size_t start_operand,
+                                     size_t end_operand) {
     auto operands = inst->Operands();
     for (size_t i = start_operand; i <= end_operand; i++) {
         CheckOperandNotNull(inst, operands[i], i);
     }
 }
 
-void Validator::CheckRootBlock(Block* blk) {
+void Validator::CheckRootBlock(const Block* blk) {
     TINT_SCOPED_ASSIGNMENT(current_block_, blk);
 
     for (auto* inst : *blk) {
@@ -444,11 +448,11 @@
     }
 }
 
-void Validator::CheckFunction(Function* func) {
+void Validator::CheckFunction(const Function* func) {
     CheckBlock(func->Block());
 }
 
-void Validator::CheckBlock(Block* blk) {
+void Validator::CheckBlock(const Block* blk) {
     TINT_SCOPED_ASSIGNMENT(current_block_, blk);
 
     if (!blk->Terminator()) {
@@ -470,7 +474,7 @@
     }
 }
 
-void Validator::CheckInstruction(Instruction* inst) {
+void Validator::CheckInstruction(const Instruction* inst) {
     visited_instructions_.Add(inst);
     if (!inst->Alive()) {
         AddError(inst, InstError(inst, "destroyed instruction found in instruction list"));
@@ -506,7 +510,7 @@
                      InstError(inst, "instruction operand " + std::to_string(i) + " is not alive"));
         }
 
-        if (!op->Usages().Contains({inst, i})) {
+        if (!op->HasUsage(inst, i)) {
             AddError(
                 inst, i,
                 InstError(inst, "instruction operand " + std::to_string(i) + " missing usage"));
@@ -514,34 +518,34 @@
     }
 
     tint::Switch(
-        inst,                                                        //
-        [&](Access* a) { CheckAccess(a); },                          //
-        [&](Binary* b) { CheckBinary(b); },                          //
-        [&](Call* c) { CheckCall(c); },                              //
-        [&](If* if_) { CheckIf(if_); },                              //
-        [&](Let* let) { CheckLet(let); },                            //
-        [&](Load*) {},                                               //
-        [&](LoadVectorElement* l) { CheckLoadVectorElement(l); },    //
-        [&](Loop* l) { CheckLoop(l); },                              //
-        [&](Store* s) { CheckStore(s); },                            //
-        [&](StoreVectorElement* s) { CheckStoreVectorElement(s); },  //
-        [&](Switch* s) { CheckSwitch(s); },                          //
-        [&](Swizzle*) {},                                            //
-        [&](Terminator* b) { CheckTerminator(b); },                  //
-        [&](Unary* u) { CheckUnary(u); },                            //
-        [&](Var* var) { CheckVar(var); },                            //
-        [&](Default) { AddError(inst, InstError(inst, "missing validation")); });
+        inst,                                                              //
+        [&](const Access* a) { CheckAccess(a); },                          //
+        [&](const Binary* b) { CheckBinary(b); },                          //
+        [&](const Call* c) { CheckCall(c); },                              //
+        [&](const If* if_) { CheckIf(if_); },                              //
+        [&](const Let* let) { CheckLet(let); },                            //
+        [&](const Load*) {},                                               //
+        [&](const LoadVectorElement* l) { CheckLoadVectorElement(l); },    //
+        [&](const Loop* l) { CheckLoop(l); },                              //
+        [&](const Store* s) { CheckStore(s); },                            //
+        [&](const StoreVectorElement* s) { CheckStoreVectorElement(s); },  //
+        [&](const Switch* s) { CheckSwitch(s); },                          //
+        [&](const Swizzle*) {},                                            //
+        [&](const Terminator* b) { CheckTerminator(b); },                  //
+        [&](const Unary* u) { CheckUnary(u); },                            //
+        [&](const Var* var) { CheckVar(var); },                            //
+        [&](const Default) { AddError(inst, InstError(inst, "missing validation")); });
 }
 
-void Validator::CheckVar(Var* var) {
-    if (var->Result(0) && var->Initializer()) {
+void Validator::CheckVar(const Var* var) {
+    if (var->Result() && var->Initializer()) {
         if (var->Initializer()->Type() != var->Result(0)->Type()->UnwrapPtr()) {
             AddError(var, InstError(var, "initializer has incorrect type"));
         }
     }
 }
 
-void Validator::CheckLet(Let* let) {
+void Validator::CheckLet(const Let* let) {
     CheckOperandNotNull(let, let->Value(), Let::kValueOperandOffset);
 
     if (let->Result(0) && let->Value()) {
@@ -551,23 +555,31 @@
     }
 }
 
-void Validator::CheckCall(Call* call) {
+void Validator::CheckCall(const Call* call) {
     tint::Switch(
-        call,                                          //
-        [&](Bitcast*) {},                              //
-        [&](BuiltinCall* c) { CheckBuiltinCall(c); },  //
-        [&](Construct*) {},                            //
-        [&](Convert*) {},                              //
-        [&](Discard*) {},                              //
-        [&](UserCall* c) { CheckUserCall(c); },        //
+        call,                                                //
+        [&](const Bitcast*) {},                              //
+        [&](const BuiltinCall* c) { CheckBuiltinCall(c); },  //
+        [&](const Construct*) {},                            //
+        [&](const Convert*) {},                              //
+        [&](const Discard*) {},                              //
+        [&](const UserCall* c) { CheckUserCall(c); },        //
         [&](Default) {
             // Validation of custom IR instructions
         });
 }
 
-void Validator::CheckBuiltinCall(BuiltinCall* call) {
-    auto args = Transform<8>(call->Args(), [&](ir::Value* v) { return v->Type(); });
-    intrinsic::Context context{call->TableData(), mod_.Types(), mod_.symbols, diagnostics_};
+void Validator::CheckBuiltinCall(const BuiltinCall* call) {
+    auto symbols = SymbolTable::Wrap(mod_.symbols);
+    auto type_mgr = type::Manager::Wrap(mod_.Types());
+
+    auto args = Transform<8>(call->Args(), [&](const ir::Value* v) { return v->Type(); });
+    intrinsic::Context context{
+        call->TableData(),
+        type_mgr,
+        symbols,
+        diagnostics_,
+    };
 
     auto result = core::intrinsic::LookupFn(context, call->FriendlyName().c_str(), call->FuncId(),
                                             args, core::EvaluationStage::kRuntime, Source{});
@@ -578,14 +590,14 @@
     }
 }
 
-void Validator::CheckUserCall(UserCall* call) {
+void Validator::CheckUserCall(const UserCall* call) {
     if (!all_functions_.Contains(call->Target())) {
         AddError(call, UserCall::kFunctionOperandOffset,
                  InstError(call, "call target is not part of the module"));
     }
 }
 
-void Validator::CheckAccess(Access* a) {
+void Validator::CheckAccess(const Access* a) {
     bool is_ptr = a->Object()->Type()->Is<core::type::Pointer>();
     auto* ty = a->Object()->Type()->UnwrapPtr();
 
@@ -654,11 +666,11 @@
     }
 }
 
-void Validator::CheckBinary(Binary* b) {
+void Validator::CheckBinary(const Binary* b) {
     CheckOperandsNotNull(b, Binary::kLhsOperandOffset, Binary::kRhsOperandOffset);
 }
 
-void Validator::CheckUnary(Unary* u) {
+void Validator::CheckUnary(const Unary* u) {
     CheckOperandNotNull(u, u->Val(), Unary::kValueOperandOffset);
 
     if (u->Result(0) && u->Val()) {
@@ -668,7 +680,7 @@
     }
 }
 
-void Validator::CheckIf(If* if_) {
+void Validator::CheckIf(const If* if_) {
     CheckOperandNotNull(if_, if_->Condition(), If::kConditionOperandOffset);
 
     if (if_->Condition() && !if_->Condition()->Type()->Is<core::type::Bool>()) {
@@ -685,7 +697,7 @@
     }
 }
 
-void Validator::CheckLoop(Loop* l) {
+void Validator::CheckLoop(const Loop* l) {
     control_stack_.Push(l);
     TINT_DEFER(control_stack_.Pop());
 
@@ -699,7 +711,7 @@
     }
 }
 
-void Validator::CheckSwitch(Switch* s) {
+void Validator::CheckSwitch(const Switch* s) {
     control_stack_.Push(s);
     TINT_DEFER(control_stack_.Pop());
 
@@ -708,23 +720,23 @@
     }
 }
 
-void Validator::CheckTerminator(Terminator* b) {
+void Validator::CheckTerminator(const Terminator* b) {
     // Note, transforms create `undef` terminator arguments (this is done in MergeReturn and
     // DemoteToHelper) so we can't add validation.
 
     tint::Switch(
-        b,                                           //
-        [&](ir::BreakIf*) {},                        //
-        [&](ir::Continue*) {},                       //
-        [&](ir::Exit* e) { CheckExit(e); },          //
-        [&](ir::NextIteration*) {},                  //
-        [&](ir::Return* ret) { CheckReturn(ret); },  //
-        [&](ir::TerminateInvocation*) {},            //
-        [&](ir::Unreachable*) {},                    //
+        b,                                                 //
+        [&](const ir::BreakIf*) {},                        //
+        [&](const ir::Continue*) {},                       //
+        [&](const ir::Exit* e) { CheckExit(e); },          //
+        [&](const ir::NextIteration*) {},                  //
+        [&](const ir::Return* ret) { CheckReturn(ret); },  //
+        [&](const ir::TerminateInvocation*) {},            //
+        [&](const ir::Unreachable*) {},                    //
         [&](Default) { AddError(b, InstError(b, "missing validation")); });
 }
 
-void Validator::CheckExit(Exit* e) {
+void Validator::CheckExit(const Exit* e) {
     if (e->ControlInstruction() == nullptr) {
         AddError(e, InstError(e, "has no parent control instruction"));
         return;
@@ -757,21 +769,21 @@
     }
 
     tint::Switch(
-        e,                                               //
-        [&](ir::ExitIf* i) { CheckExitIf(i); },          //
-        [&](ir::ExitLoop* l) { CheckExitLoop(l); },      //
-        [&](ir::ExitSwitch* s) { CheckExitSwitch(s); },  //
+        e,                                                     //
+        [&](const ir::ExitIf* i) { CheckExitIf(i); },          //
+        [&](const ir::ExitLoop* l) { CheckExitLoop(l); },      //
+        [&](const ir::ExitSwitch* s) { CheckExitSwitch(s); },  //
         [&](Default) { AddError(e, InstError(e, "missing validation")); });
 }
 
-void Validator::CheckExitIf(ExitIf* e) {
+void Validator::CheckExitIf(const ExitIf* e) {
     if (control_stack_.Back() != e->If()) {
         AddError(e, InstError(e, "if target jumps over other control instructions"));
         AddNote(control_stack_.Back(), "first control instruction jumped");
     }
 }
 
-void Validator::CheckReturn(Return* ret) {
+void Validator::CheckReturn(const Return* ret) {
     auto* func = ret->Func();
     if (func == nullptr) {
         AddError(ret, InstError(ret, "undefined function"));
@@ -790,7 +802,7 @@
     }
 }
 
-void Validator::CheckControlsAllowingIf(Exit* exit, Instruction* control) {
+void Validator::CheckControlsAllowingIf(const Exit* exit, const Instruction* control) {
     bool found = false;
     for (auto ctrl : tint::Reverse(control_stack_)) {
         if (ctrl == control) {
@@ -811,15 +823,15 @@
     }
 }
 
-void Validator::CheckExitSwitch(ExitSwitch* s) {
+void Validator::CheckExitSwitch(const ExitSwitch* s) {
     CheckControlsAllowingIf(s, s->ControlInstruction());
 }
 
-void Validator::CheckExitLoop(ExitLoop* l) {
+void Validator::CheckExitLoop(const ExitLoop* l) {
     CheckControlsAllowingIf(l, l->ControlInstruction());
 
-    Instruction* inst = l;
-    Loop* control = l->Loop();
+    const Instruction* inst = l;
+    const Loop* control = l->Loop();
     while (inst) {
         // Found parent loop
         if (inst->Block()->Parent() == control) {
@@ -840,7 +852,7 @@
     }
 }
 
-void Validator::CheckStore(Store* s) {
+void Validator::CheckStore(const Store* s) {
     CheckOperandsNotNull(s, Store::kToOperandOffset, Store::kFromOperandOffset);
 
     if (auto* from = s->From()) {
@@ -853,7 +865,7 @@
     }
 }
 
-void Validator::CheckLoadVectorElement(LoadVectorElement* l) {
+void Validator::CheckLoadVectorElement(const LoadVectorElement* l) {
     CheckOperandsNotNull(l,  //
                          LoadVectorElement::kFromOperandOffset,
                          LoadVectorElement::kIndexOperandOffset);
@@ -867,7 +879,7 @@
     }
 }
 
-void Validator::CheckStoreVectorElement(StoreVectorElement* s) {
+void Validator::CheckStoreVectorElement(const StoreVectorElement* s) {
     CheckOperandsNotNull(s,  //
                          StoreVectorElement::kToOperandOffset,
                          StoreVectorElement::kValueOperandOffset);
@@ -882,7 +894,7 @@
     }
 }
 
-const core::type::Type* Validator::GetVectorPtrElementType(Instruction* inst, size_t idx) {
+const core::type::Type* Validator::GetVectorPtrElementType(const Instruction* inst, size_t idx) {
     auto* operand = inst->Operands()[idx];
     if (TINT_UNLIKELY(!operand)) {
         return nullptr;
@@ -907,12 +919,12 @@
 
 }  // namespace
 
-Result<SuccessType> Validate(Module& mod) {
+Result<SuccessType> Validate(const Module& mod) {
     Validator v(mod);
     return v.Run();
 }
 
-Result<SuccessType> ValidateAndDumpIfNeeded([[maybe_unused]] Module& ir,
+Result<SuccessType> ValidateAndDumpIfNeeded([[maybe_unused]] const Module& ir,
                                             [[maybe_unused]] const char* msg) {
 #if TINT_DUMP_IR_WHEN_VALIDATING
     std::cout << "=========================================================" << std::endl;
diff --git a/src/tint/lang/core/ir/validator.h b/src/tint/lang/core/ir/validator.h
index cfc6a81..d6b0843 100644
--- a/src/tint/lang/core/ir/validator.h
+++ b/src/tint/lang/core/ir/validator.h
@@ -42,13 +42,13 @@
 /// Validates that a given IR module is correctly formed
 /// @param mod the module to validate
 /// @returns success or failure
-Result<SuccessType> Validate(Module& mod);
+Result<SuccessType> Validate(const Module& mod);
 
 /// Validates the module @p ir and dumps its contents if required by the build configuration.
 /// @param ir the module to transform
 /// @param msg the msg to accompany the output
 /// @returns success or failure
-Result<SuccessType> ValidateAndDumpIfNeeded(Module& ir, const char* msg);
+Result<SuccessType> ValidateAndDumpIfNeeded(const Module& ir, const char* msg);
 
 }  // namespace tint::core::ir
 
diff --git a/src/tint/lang/glsl/writer/printer/printer.cc b/src/tint/lang/glsl/writer/printer/printer.cc
index f3dda8c..b25799e 100644
--- a/src/tint/lang/glsl/writer/printer/printer.cc
+++ b/src/tint/lang/glsl/writer/printer/printer.cc
@@ -50,7 +50,7 @@
   public:
     /// Constructor
     /// @param module the Tint IR module to generate
-    explicit Printer(core::ir::Module& module) : ir_(module) {}
+    explicit Printer(const core::ir::Module& module) : ir_(module) {}
 
     /// @param version the GLSL version information
     /// @returns the generated GLSL shader
@@ -84,7 +84,7 @@
     }
 
   private:
-    core::ir::Module& ir_;
+    const core::ir::Module& ir_;
 
     /// The buffer holding preamble text
     TextBuffer preamble_buffer_;
@@ -169,7 +169,7 @@
 };
 }  // namespace
 
-Result<std::string> Print(core::ir::Module& module, const Version& version) {
+Result<std::string> Print(const core::ir::Module& module, const Version& version) {
     return Printer{module}.Generate(version);
 }
 
diff --git a/src/tint/lang/glsl/writer/printer/printer.h b/src/tint/lang/glsl/writer/printer/printer.h
index 49c6301..6f75b39 100644
--- a/src/tint/lang/glsl/writer/printer/printer.h
+++ b/src/tint/lang/glsl/writer/printer/printer.h
@@ -45,7 +45,7 @@
 /// @returns the generated GLSL shader on success, or failure
 /// @param module the Tint IR module to generate
 /// @param version the GLSL version information
-Result<std::string> Print(core::ir::Module& module, const Version& version);
+Result<std::string> Print(const core::ir::Module& module, const Version& version);
 
 }  // namespace tint::glsl::writer
 
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index 08a44bb..c0f22f6 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -101,7 +101,7 @@
 
 class State {
   public:
-    explicit State(core::ir::Module& m) : mod(m) {}
+    explicit State(const core::ir::Module& m) : mod(m) {}
 
     Program Run() {
         if (auto res = core::ir::Validate(mod); !res) {
@@ -127,7 +127,7 @@
     };
 
     /// The source IR module
-    core::ir::Module& mod;
+    const core::ir::Module& mod;
 
     /// The target ProgramBuilder
     ProgramBuilder b;
@@ -152,10 +152,10 @@
     using ValueBinding = std::variant<VariableValue, InlinedValue, ConsumedValue>;
 
     /// IR values to their representation
-    Hashmap<core::ir::Value*, ValueBinding, 32> bindings_;
+    Hashmap<const core::ir::Value*, ValueBinding, 32> bindings_;
 
     /// Names for values
-    Hashmap<core::ir::Value*, Symbol, 32> names_;
+    Hashmap<const core::ir::Value*, Symbol, 32> names_;
 
     /// The nesting depth of the currently generated AST
     /// 0  is module scope
@@ -168,10 +168,10 @@
     StatementList* statements_ = nullptr;
 
     /// The current switch case block
-    core::ir::Block* current_switch_case_ = nullptr;
+    const core::ir::Block* current_switch_case_ = nullptr;
 
     /// Values that can be inlined.
-    Hashset<core::ir::Value*, 64> can_inline_;
+    Hashset<const core::ir::Value*, 64> can_inline_;
 
     /// Set of enable directives emitted.
     Hashset<wgsl::Extension, 4> enables_;
@@ -182,20 +182,20 @@
     /// True if 'diagnostic(off, derivative_uniformity)' has been emitted
     bool disabled_derivative_uniformity_ = false;
 
-    void RootBlock(core::ir::Block* root) {
+    void RootBlock(const core::ir::Block* root) {
         for (auto* inst : *root) {
             tint::Switch(
-                inst,                                   //
-                [&](core::ir::Var* var) { Var(var); },  //
+                inst,                                         //
+                [&](const core::ir::Var* var) { Var(var); },  //
                 TINT_ICE_ON_NO_MATCH);
         }
     }
-    const ast::Function* Fn(core::ir::Function* fn) {
+    const ast::Function* Fn(const core::ir::Function* fn) {
         SCOPED_NESTING();
 
         // TODO(crbug.com/tint/1915): Properly implement this when we've fleshed out Function
         static constexpr size_t N = decltype(ast::Function::params)::static_length;
-        auto params = tint::Transform<N>(fn->Params(), [&](core::ir::FunctionParam* param) {
+        auto params = tint::Transform<N>(fn->Params(), [&](const core::ir::FunctionParam* param) {
             auto ty = Type(param->Type());
             auto name = NameFor(param);
             Bind(param, name, PtrKind::kPtr);
@@ -215,12 +215,12 @@
                       std::move(ret_attrs));
     }
 
-    const ast::BlockStatement* Block(core::ir::Block* block) {
+    const ast::BlockStatement* Block(const core::ir::Block* block) {
         // TODO(crbug.com/tint/1902): Handle block arguments.
         return b.Block(Statements(block));
     }
 
-    StatementList Statements(core::ir::Block* block) {
+    StatementList Statements(const core::ir::Block* block) {
         StatementList stmts;
         if (block) {
             MarkInlinable(block);
@@ -232,10 +232,10 @@
         return stmts;
     }
 
-    void MarkInlinable(core::ir::Block* block) {
+    void MarkInlinable(const core::ir::Block* block) {
         // An ordered list of possibly-inlinable values returned by sequenced instructions that have
         // not yet been marked-for or ruled-out-for inlining.
-        UniqueVector<core::ir::Value*, 32> pending_resolution;
+        UniqueVector<const core::ir::Value*, 32> pending_resolution;
 
         // Walk the instructions of the block starting with the first.
         for (auto* inst : *block) {
@@ -276,7 +276,7 @@
                 auto* result = inst->Result(0);
                 // Only values with a single usage can be inlined.
                 // Named values are not inlined, as we want to emit the name for a let.
-                if (result->Usages().Count() == 1 && !mod.NameOf(result).IsValid()) {
+                if (result->NumUsages() == 1 && !mod.NameOf(result).IsValid()) {
                     if (sequenced) {
                         // The value comes from a sequenced instruction. We need to ensure
                         // instruction ordering so add it to 'pending_resolution'.
@@ -301,35 +301,35 @@
 
     void Append(const ast::Statement* inst) { statements_->Push(inst); }
 
-    void Instruction(core::ir::Instruction* inst) {
+    void Instruction(const core::ir::Instruction* inst) {
         tint::Switch(
-            inst,                                                             //
-            [&](core::ir::Access* i) { Access(i); },                          //
-            [&](core::ir::Binary* i) { Binary(i); },                          //
-            [&](core::ir::BreakIf* i) { BreakIf(i); },                        //
-            [&](core::ir::Call* i) { Call(i); },                              //
-            [&](core::ir::Continue*) {},                                      //
-            [&](core::ir::ExitIf*) {},                                        //
-            [&](core::ir::ExitLoop* i) { ExitLoop(i); },                      //
-            [&](core::ir::ExitSwitch* i) { ExitSwitch(i); },                  //
-            [&](core::ir::If* i) { If(i); },                                  //
-            [&](core::ir::Let* i) { Let(i); },                                //
-            [&](core::ir::Load* l) { Load(l); },                              //
-            [&](core::ir::LoadVectorElement* i) { LoadVectorElement(i); },    //
-            [&](core::ir::Loop* l) { Loop(l); },                              //
-            [&](core::ir::NextIteration*) {},                                 //
-            [&](core::ir::Return* i) { Return(i); },                          //
-            [&](core::ir::Store* i) { Store(i); },                            //
-            [&](core::ir::StoreVectorElement* i) { StoreVectorElement(i); },  //
-            [&](core::ir::Switch* i) { Switch(i); },                          //
-            [&](core::ir::Swizzle* i) { Swizzle(i); },                        //
-            [&](core::ir::Unary* i) { Unary(i); },                            //
-            [&](core::ir::Unreachable*) {},                                   //
-            [&](core::ir::Var* i) { Var(i); },                                //
+            inst,                                                                   //
+            [&](const core::ir::Access* i) { Access(i); },                          //
+            [&](const core::ir::Binary* i) { Binary(i); },                          //
+            [&](const core::ir::BreakIf* i) { BreakIf(i); },                        //
+            [&](const core::ir::Call* i) { Call(i); },                              //
+            [&](const core::ir::Continue*) {},                                      //
+            [&](const core::ir::ExitIf*) {},                                        //
+            [&](const core::ir::ExitLoop* i) { ExitLoop(i); },                      //
+            [&](const core::ir::ExitSwitch* i) { ExitSwitch(i); },                  //
+            [&](const core::ir::If* i) { If(i); },                                  //
+            [&](const core::ir::Let* i) { Let(i); },                                //
+            [&](const core::ir::Load* l) { Load(l); },                              //
+            [&](const core::ir::LoadVectorElement* i) { LoadVectorElement(i); },    //
+            [&](const core::ir::Loop* l) { Loop(l); },                              //
+            [&](const core::ir::NextIteration*) {},                                 //
+            [&](const core::ir::Return* i) { Return(i); },                          //
+            [&](const core::ir::Store* i) { Store(i); },                            //
+            [&](const core::ir::StoreVectorElement* i) { StoreVectorElement(i); },  //
+            [&](const core::ir::Switch* i) { Switch(i); },                          //
+            [&](const core::ir::Swizzle* i) { Swizzle(i); },                        //
+            [&](const core::ir::Unary* i) { Unary(i); },                            //
+            [&](const core::ir::Unreachable*) {},                                   //
+            [&](const core::ir::Var* i) { Var(i); },                                //
             TINT_ICE_ON_NO_MATCH);
     }
 
-    void If(core::ir::If* if_) {
+    void If(const core::ir::If* if_) {
         SCOPED_NESTING();
 
         auto true_stmts = Statements(if_->True());
@@ -357,7 +357,7 @@
         Append(b.If(cond, true_block, b.Else(false_block)));
     }
 
-    void Loop(core::ir::Loop* l) {
+    void Loop(const core::ir::Loop* l) {
         SCOPED_NESTING();
 
         // Build all the initializer statements
@@ -454,14 +454,14 @@
         statements_->Push(loop);
     }
 
-    void Switch(core::ir::Switch* s) {
+    void Switch(const core::ir::Switch* s) {
         SCOPED_NESTING();
 
         auto* cond = Expr(s->Condition());
 
-        auto cases = tint::Transform(
+        auto cases = tint::Transform<4>(
             s->Cases(),  //
-            [&](core::ir::Switch::Case c) -> const tint::ast::CaseStatement* {
+            [&](const core::ir::Switch::Case& c) -> const tint::ast::CaseStatement* {
                 SCOPED_NESTING();
 
                 const ast::BlockStatement* body = nullptr;
@@ -471,7 +471,7 @@
                 }
 
                 auto selectors = tint::Transform(c.selectors,  //
-                                                 [&](core::ir::Switch::CaseSelector cs) {
+                                                 [&](const core::ir::Switch::CaseSelector& cs) {
                                                      return cs.IsDefault()
                                                                 ? b.DefaultCaseSelector()
                                                                 : b.CaseSelector(Expr(cs.val));
@@ -491,9 +491,9 @@
 
     void ExitLoop(const core::ir::ExitLoop*) { Append(b.Break()); }
 
-    void BreakIf(core::ir::BreakIf* i) { Append(b.BreakIf(Expr(i->Condition()))); }
+    void BreakIf(const core::ir::BreakIf* i) { Append(b.BreakIf(Expr(i->Condition()))); }
 
-    void Return(core::ir::Return* ret) {
+    void Return(const core::ir::Return* ret) {
         if (ret->Args().IsEmpty()) {
             // Return has no arguments.
             // If this block is nested withing some control flow, then we must
@@ -514,7 +514,7 @@
         Append(b.Return(Expr(ret->Args().Front())));
     }
 
-    void Var(core::ir::Var* var) {
+    void Var(const core::ir::Var* var) {
         auto* val = var->Result();
         auto* ptr = As<core::type::Pointer>(val->Type());
         auto ty = Type(ptr->StoreType());
@@ -547,32 +547,32 @@
         }
     }
 
-    void Let(core::ir::Let* let) {
+    void Let(const core::ir::Let* let) {
         Symbol name = NameFor(let->Result());
         Append(b.Decl(b.Let(name, Expr(let->Value(), PtrKind::kPtr))));
         Bind(let->Result(), name, PtrKind::kPtr);
     }
 
-    void Store(core::ir::Store* store) {
+    void Store(const core::ir::Store* store) {
         auto* dst = Expr(store->To());
         auto* src = Expr(store->From());
         Append(b.Assign(dst, src));
     }
 
-    void StoreVectorElement(core::ir::StoreVectorElement* store) {
+    void StoreVectorElement(const core::ir::StoreVectorElement* store) {
         auto* ptr = Expr(store->To());
         auto* val = Expr(store->Value());
         Append(b.Assign(VectorMemberAccess(ptr, store->Index()), val));
     }
 
-    void Call(core::ir::Call* call) {
-        auto args = tint::Transform<4>(call->Args(), [&](core::ir::Value* arg) {
+    void Call(const core::ir::Call* call) {
+        auto args = tint::Transform<4>(call->Args(), [&](const core::ir::Value* arg) {
             // Pointer-like arguments are passed by pointer, never reference.
             return Expr(arg, PtrKind::kPtr);
         });
         tint::Switch(
             call,  //
-            [&](core::ir::UserCall* c) {
+            [&](const core::ir::UserCall* c) {
                 for (auto* arg : call->Args()) {
                     if (ArgRequiresFullPtrParameters(arg)) {
                         Enable(wgsl::Extension::kChromiumExperimentalFullPtrParameters);
@@ -580,13 +580,13 @@
                     }
                 }
                 auto* expr = b.Call(NameFor(c->Target()), std::move(args));
-                if (call->Results().IsEmpty() || call->Result()->Usages().IsEmpty()) {
+                if (call->Results().IsEmpty() || !call->Result()->IsUsed()) {
                     Append(b.CallStmt(expr));
                     return;
                 }
                 Bind(c->Result(), expr, PtrKind::kPtr);
             },
-            [&](wgsl::ir::BuiltinCall* c) {
+            [&](const wgsl::ir::BuiltinCall* c) {
                 if (!disabled_derivative_uniformity_ && RequiresDerivativeUniformity(c->Func())) {
                     // TODO(crbug.com/tint/1985): Be smarter about disabling derivative uniformity.
                     b.DiagnosticDirective(wgsl::DiagnosticSeverity::kOff,
@@ -610,30 +610,30 @@
                 }
                 Bind(c->Result(), expr, PtrKind::kPtr);
             },
-            [&](core::ir::Construct* c) {
+            [&](const core::ir::Construct* c) {
                 auto ty = Type(c->Result()->Type());
                 Bind(c->Result(), b.Call(ty, std::move(args)), PtrKind::kPtr);
             },
-            [&](core::ir::Convert* c) {
+            [&](const core::ir::Convert* c) {
                 auto ty = Type(c->Result()->Type());
                 Bind(c->Result(), b.Call(ty, std::move(args)), PtrKind::kPtr);
             },
-            [&](core::ir::Bitcast* c) {
+            [&](const core::ir::Bitcast* c) {
                 auto ty = Type(c->Result()->Type());
                 Bind(c->Result(), b.Bitcast(ty, args[0]), PtrKind::kPtr);
             },
-            [&](core::ir::Discard*) { Append(b.Discard()); },  //
+            [&](const core::ir::Discard*) { Append(b.Discard()); },  //
             TINT_ICE_ON_NO_MATCH);
     }
 
-    void Load(core::ir::Load* l) { Bind(l->Result(), Expr(l->From())); }
+    void Load(const core::ir::Load* l) { Bind(l->Result(), Expr(l->From())); }
 
-    void LoadVectorElement(core::ir::LoadVectorElement* load) {
+    void LoadVectorElement(const core::ir::LoadVectorElement* load) {
         auto* ptr = Expr(load->From());
         Bind(load->Result(), VectorMemberAccess(ptr, load->Index()));
     }
 
-    void Unary(core::ir::Unary* u) {
+    void Unary(const core::ir::Unary* u) {
         const ast::Expression* expr = nullptr;
         switch (u->Op()) {
             case core::ir::UnaryOp::kComplement:
@@ -646,7 +646,7 @@
         Bind(u->Result(), expr);
     }
 
-    void Access(core::ir::Access* a) {
+    void Access(const core::ir::Access* a) {
         auto* expr = Expr(a->Object());
         auto* obj_ty = a->Object()->Type()->UnwrapPtr();
         for (auto* index : a->Indices()) {
@@ -680,7 +680,7 @@
         Bind(a->Result(), expr);
     }
 
-    void Swizzle(core::ir::Swizzle* s) {
+    void Swizzle(const core::ir::Swizzle* s) {
         auto* vec = Expr(s->Object());
         Vector<char, 4> components;
         for (uint32_t i : s->Indices()) {
@@ -695,7 +695,7 @@
         Bind(s->Result(), swizzle);
     }
 
-    void Binary(core::ir::Binary* e) {
+    void Binary(const core::ir::Binary* e) {
         if (e->Op() == core::ir::BinaryOp::kEqual) {
             auto* rhs = e->RHS()->As<core::ir::Constant>();
             if (rhs && rhs->Type()->Is<core::type::Bool>() &&
@@ -763,12 +763,13 @@
 
     TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
 
-    const ast::Expression* Expr(core::ir::Value* value, PtrKind want_ptr_kind = PtrKind::kRef) {
+    const ast::Expression* Expr(const core::ir::Value* value,
+                                PtrKind want_ptr_kind = PtrKind::kRef) {
         using ExprAndPtrKind = std::pair<const ast::Expression*, PtrKind>;
 
         auto [expr, got_ptr_kind] = tint::Switch(
             value,
-            [&](core::ir::Constant* c) -> ExprAndPtrKind {
+            [&](const core::ir::Constant* c) -> ExprAndPtrKind {
                 return {Constant(c), PtrKind::kRef};
             },
             [&](Default) -> ExprAndPtrKind {
@@ -819,7 +820,7 @@
 
     TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
 
-    const ast::Expression* Constant(core::ir::Constant* c) { return Constant(c->Value()); }
+    const ast::Expression* Constant(const core::ir::Constant* c) { return Constant(c->Value()); }
 
     const ast::Expression* Constant(const core::constant::Value* c) {
         auto composite = [&](bool can_splat) {
@@ -1010,7 +1011,7 @@
 
     /// @returns the AST name for the given value, creating and returning a new name on the first
     /// call.
-    Symbol NameFor(core::ir::Value* value, std::string_view suggested = {}) {
+    Symbol NameFor(const core::ir::Value* value, std::string_view suggested = {}) {
         return names_.GetOrCreate(value, [&] {
             if (!suggested.empty()) {
                 return b.Symbols().Register(suggested);
@@ -1024,7 +1025,7 @@
 
     /// Associates the IR value @p value with the AST expression @p expr.
     /// @p ptr_kind defines how pointer values are represented by @p expr.
-    void Bind(core::ir::Value* value,
+    void Bind(const core::ir::Value* value,
               const ast::Expression* expr,
               PtrKind ptr_kind = PtrKind::kRef) {
         TINT_ASSERT(value);
@@ -1038,7 +1039,7 @@
                 expr = ToPtrKind(expr, ptr_kind, PtrKind::kPtr);
             }
             auto mod_name = mod.NameOf(value);
-            if (value->Usages().IsEmpty() && !mod_name.IsValid()) {
+            if (!value->IsUsed() && !mod_name.IsValid()) {
                 // Value has no usages and no name.
                 // Assign to a phony. These support more data types than a 'let', and avoids
                 // allocation of unused names.
@@ -1057,7 +1058,7 @@
     /// Associates the IR value @p value with the AST 'var', 'let' or parameter with the name @p
     /// name.
     /// @p ptr_kind defines how pointer values are represented by @p expr.
-    void Bind(core::ir::Value* value, Symbol name, PtrKind ptr_kind) {
+    void Bind(const core::ir::Value* value, Symbol name, PtrKind ptr_kind) {
         TINT_ASSERT(value);
 
         bool added = bindings_.Add(value, VariableValue{name, ptr_kind});
@@ -1069,7 +1070,7 @@
     ////////////////////////////////////////////////////////////////////////////////////////////////
     // Helpers
     ////////////////////////////////////////////////////////////////////////////////////////////////
-    bool AsShortCircuit(core::ir::If* i,
+    bool AsShortCircuit(const core::ir::If* i,
                         const StatementList& true_stmts,
                         const StatementList& false_stmts) {
         if (i->Results().IsEmpty()) {
@@ -1130,7 +1131,7 @@
         return false;
     }
 
-    bool IsConstant(core::ir::Value* val, bool value) {
+    bool IsConstant(const core::ir::Value* val, bool value) {
         if (auto* c = val->As<core::ir::Constant>()) {
             if (c->Type()->Is<core::type::Bool>()) {
                 return c->Value()->ValueAs<bool>() == value;
@@ -1139,7 +1140,8 @@
         return false;
     }
 
-    const ast::Expression* VectorMemberAccess(const ast::Expression* expr, core::ir::Value* index) {
+    const ast::Expression* VectorMemberAccess(const ast::Expression* expr,
+                                              const core::ir::Value* index) {
         if (auto* c = index->As<core::ir::Constant>()) {
             switch (c->Value()->ValueAs<int>()) {
                 case 0:
@@ -1205,7 +1207,7 @@
 
     /// @returns true if the argument @p arg requires the kChromiumExperimentalFullPtrParameters
     /// extension to be enabled.
-    bool ArgRequiresFullPtrParameters(core::ir::Value* arg) {
+    bool ArgRequiresFullPtrParameters(const core::ir::Value* arg) {
         if (!arg->Type()->Is<core::type::Pointer>()) {
             return false;
         }
@@ -1228,7 +1230,7 @@
 
 }  // namespace
 
-Program IRToProgram(core::ir::Module& i) {
+Program IRToProgram(const core::ir::Module& i) {
     return State{i}.Run();
 }
 
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h
index 267bb29..089a1e7 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h
@@ -40,7 +40,7 @@
 /// @param module the IR module
 /// @return the tint::Program.
 /// @note Check the returned Program::Diagnostics() for any errors.
-Program IRToProgram(core::ir::Module& module);
+Program IRToProgram(const core::ir::Module& module);
 
 }  // namespace tint::wgsl::writer