[tint][ir] Add 'const' accessors

const methods that return a pointer can only return a const pointee
object.

This will be used by consumers of the IR that will not mutate the IR,
such as printers, validators, disassemblers, serializers, etc.

Change-Id: I5e881126c21f95a0848ad65c317cc8cdc70d1ae9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/161010
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/access.h b/src/tint/lang/core/ir/access.h
index 274fa6b..0bf2caa 100644
--- a/src/tint/lang/core/ir/access.h
+++ b/src/tint/lang/core/ir/access.h
@@ -57,15 +57,23 @@
     /// @returns the object used for the access
     Value* Object() { return operands_[kObjectOperandOffset]; }
 
+    /// @returns the object used for the access
+    const Value* Object() const { return operands_[kObjectOperandOffset]; }
+
     /// Adds the given index to the end of the access chain
     /// @param idx the index to add
     void AddIndex(Value* idx) { AddOperand(operands_.Length(), idx); }
 
     /// @returns the accessor indices
-    tint::Slice<Value*> Indices() { return operands_.Slice().Offset(kIndicesOperandOffset); }
+    tint::Slice<Value* const> Indices() { return operands_.Slice().Offset(kIndicesOperandOffset); }
+
+    /// @returns the accessor indices
+    tint::Slice<const Value* const> Indices() const {
+        return operands_.Slice().Offset(kIndicesOperandOffset);
+    }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "access"; }
+    std::string FriendlyName() const override { return "access"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/binary.h b/src/tint/lang/core/ir/binary.h
index 117dfbf..6490be9 100644
--- a/src/tint/lang/core/ir/binary.h
+++ b/src/tint/lang/core/ir/binary.h
@@ -79,16 +79,22 @@
     Binary* Clone(CloneContext& ctx) override;
 
     /// @returns the binary operator
-    BinaryOp Op() { return op_; }
+    BinaryOp Op() const { return op_; }
 
     /// @returns the left-hand-side value for the instruction
     Value* LHS() { return operands_[kLhsOperandOffset]; }
 
+    /// @returns the left-hand-side value for the instruction
+    const Value* LHS() const { return operands_[kLhsOperandOffset]; }
+
     /// @returns the right-hand-side value for the instruction
     Value* RHS() { return operands_[kRhsOperandOffset]; }
 
+    /// @returns the right-hand-side value for the instruction
+    const Value* RHS() const { return operands_[kRhsOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "binary"; }
+    std::string FriendlyName() const override { return "binary"; }
 
   private:
     BinaryOp op_;
diff --git a/src/tint/lang/core/ir/bitcast.h b/src/tint/lang/core/ir/bitcast.h
index 11998fe..3526681 100644
--- a/src/tint/lang/core/ir/bitcast.h
+++ b/src/tint/lang/core/ir/bitcast.h
@@ -53,8 +53,11 @@
     /// @returns the operand value
     Value* Val() { return operands_[kValueOperandOffset]; }
 
+    /// @returns the operand value
+    const Value* Val() const { return operands_[kValueOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "bitcast"; }
+    std::string FriendlyName() const override { return "bitcast"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/block.h b/src/tint/lang/core/ir/block.h
index 7b13542..2d20d1f 100644
--- a/src/tint/lang/core/ir/block.h
+++ b/src/tint/lang/core/ir/block.h
@@ -62,20 +62,30 @@
     /// a terminator.
     ir::Terminator* Terminator() { return tint::As<ir::Terminator>(instructions_.last); }
 
+    /// @return the terminator instruction for this block, or nullptr if this block does not end in
+    /// a terminator.
+    const ir::Terminator* Terminator() const {
+        return tint::As<ir::Terminator>(instructions_.last);
+    }
+
     /// @returns the instructions in the block
     Instruction* Instructions() { return instructions_.first; }
 
+    /// @returns the instructions in the block
+    const Instruction* Instructions() const { return instructions_.first; }
+
     /// Iterator for the instructions inside a block
+    template <typename T>
     class Iterator {
       public:
         /// Constructor
         /// @param inst the instruction to start iterating from
-        explicit Iterator(Instruction* inst) : inst_(inst) {}
+        explicit Iterator(T* inst) : inst_(inst) {}
         ~Iterator() = default;
 
         /// Dereference operator
         /// @returns the instruction for this iterator
-        Instruction* operator*() const { return inst_; }
+        T* operator*() const { return inst_; }
 
         /// Comparison operator
         /// @param itr to compare against
@@ -95,21 +105,35 @@
         }
 
       private:
-        Instruction* inst_ = nullptr;
+        T* inst_ = nullptr;
     };
 
     /// @returns the iterator pointing to the start of the instruction list
-    Iterator begin() { return Iterator{instructions_.first}; }
+    Iterator<Instruction> begin() { return Iterator<Instruction>{instructions_.first}; }
 
     /// @returns the ending iterator
-    Iterator end() { return Iterator{nullptr}; }
+    Iterator<Instruction> end() { return Iterator<Instruction>{nullptr}; }
+
+    /// @returns the iterator pointing to the start of the instruction list
+    Iterator<const Instruction> begin() const {
+        return Iterator<const Instruction>{instructions_.first};
+    }
+
+    /// @returns the ending iterator
+    Iterator<const Instruction> end() const { return Iterator<const Instruction>{nullptr}; }
 
     /// @returns the first instruction in the instruction list
     Instruction* Front() { return instructions_.first; }
 
+    /// @returns the first instruction in the instruction list
+    const Instruction* Front() const { return instructions_.first; }
+
     /// @returns the last instruction in the instruction list
     Instruction* Back() { return instructions_.last; }
 
+    /// @returns the last instruction in the instruction list
+    const Instruction* Back() const { return instructions_.last; }
+
     /// Adds the instruction to the beginning of the block
     /// @param inst the instruction to add
     /// @returns the instruction to allow calls to be chained
@@ -135,14 +159,17 @@
     void Remove(Instruction* inst);
 
     /// @returns true if the block contains no instructions
-    bool IsEmpty() { return Length() == 0; }
+    bool IsEmpty() const { return Length() == 0; }
 
     /// @returns the number of instructions in the block
-    size_t Length() { return instructions_.count; }
+    size_t Length() const { return instructions_.count; }
 
     /// @return the parent instruction that owns this block
     ControlInstruction* Parent() { return parent_; }
 
+    /// @return the parent instruction that owns this block
+    const ControlInstruction* Parent() const { return parent_; }
+
     /// @param parent the parent instruction that owns this block
     void SetParent(ControlInstruction* parent) { parent_ = parent; }
 
diff --git a/src/tint/lang/core/ir/block_param.h b/src/tint/lang/core/ir/block_param.h
index 3ab0c26..009e022 100644
--- a/src/tint/lang/core/ir/block_param.h
+++ b/src/tint/lang/core/ir/block_param.h
@@ -42,7 +42,7 @@
     ~BlockParam() override;
 
     /// @returns the type of the var
-    const core::type::Type* Type() override { return type_; }
+    const core::type::Type* Type() const override { return type_; }
 
     /// @copydoc Instruction::Clone()
     BlockParam* Clone(CloneContext& ctx) override;
diff --git a/src/tint/lang/core/ir/break_if.h b/src/tint/lang/core/ir/break_if.h
index c5e269e..42b8daf 100644
--- a/src/tint/lang/core/ir/break_if.h
+++ b/src/tint/lang/core/ir/break_if.h
@@ -66,11 +66,17 @@
     /// @returns the break condition
     Value* Condition() { return operands_[kConditionOperandOffset]; }
 
+    /// @returns the break condition
+    const Value* Condition() const { return operands_[kConditionOperandOffset]; }
+
     /// @returns the loop containing the break-if
     ir::Loop* Loop() { return loop_; }
 
+    /// @returns the loop containing the break-if
+    const ir::Loop* Loop() const { return loop_; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "break_if"; }
+    std::string FriendlyName() const override { return "break_if"; }
 
   private:
     ir::Loop* loop_ = nullptr;
diff --git a/src/tint/lang/core/ir/builtin_call.h b/src/tint/lang/core/ir/builtin_call.h
index 73ed85a..0351ff4 100644
--- a/src/tint/lang/core/ir/builtin_call.h
+++ b/src/tint/lang/core/ir/builtin_call.h
@@ -47,10 +47,10 @@
     ~BuiltinCall() override;
 
     /// @returns the identifier for the function
-    virtual size_t FuncId() = 0;
+    virtual size_t FuncId() const = 0;
 
     /// @returns the table data to validate this builtin
-    virtual const core::intrinsic::TableData& TableData() = 0;
+    virtual const core::intrinsic::TableData& TableData() const = 0;
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/call.h b/src/tint/lang/core/ir/call.h
index 78ef290..b56a092 100644
--- a/src/tint/lang/core/ir/call.h
+++ b/src/tint/lang/core/ir/call.h
@@ -44,6 +44,11 @@
     /// @returns the call arguments
     tint::Slice<Value* const> Args() { return operands_.Slice().Offset(ArgsOperandOffset()); }
 
+    /// @returns the call arguments
+    tint::Slice<const Value* const> Args() const {
+        return operands_.Slice().Offset(ArgsOperandOffset());
+    }
+
     /// Append a new argument to the argument list for this call instruction.
     /// @param arg the argument value to append
     void AppendArg(ir::Value* arg) { AddOperand(operands_.Length(), arg); }
diff --git a/src/tint/lang/core/ir/constant.h b/src/tint/lang/core/ir/constant.h
index 3b29da9..c5fb574 100644
--- a/src/tint/lang/core/ir/constant.h
+++ b/src/tint/lang/core/ir/constant.h
@@ -42,10 +42,10 @@
     ~Constant() override;
 
     /// @returns the constants value
-    const core::constant::Value* Value() { return value_; }
+    const core::constant::Value* Value() const { return value_; }
 
     /// @returns the type of the constant
-    const core::type::Type* Type() override { return value_->Type(); }
+    const core::type::Type* Type() const override { return value_->Type(); }
 
     /// @copydoc Value::Clone()
     Constant* Clone(CloneContext& ctx) override;
diff --git a/src/tint/lang/core/ir/construct.h b/src/tint/lang/core/ir/construct.h
index 0bbcbe3..ff9ca22 100644
--- a/src/tint/lang/core/ir/construct.h
+++ b/src/tint/lang/core/ir/construct.h
@@ -51,7 +51,7 @@
     Construct* Clone(CloneContext& ctx) override;
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "construct"; }
+    std::string FriendlyName() const override { return "construct"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/continue.h b/src/tint/lang/core/ir/continue.h
index ff6e999..93eb27a 100644
--- a/src/tint/lang/core/ir/continue.h
+++ b/src/tint/lang/core/ir/continue.h
@@ -58,8 +58,11 @@
     /// @returns the loop owning the continue block
     ir::Loop* Loop() { return loop_; }
 
+    /// @returns the loop owning the continue block
+    const ir::Loop* Loop() const { return loop_; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "continue"; }
+    std::string FriendlyName() const override { return "continue"; }
 
   private:
     ir::Loop* loop_ = nullptr;
diff --git a/src/tint/lang/core/ir/convert.h b/src/tint/lang/core/ir/convert.h
index 7e8d47c..20350aa 100644
--- a/src/tint/lang/core/ir/convert.h
+++ b/src/tint/lang/core/ir/convert.h
@@ -52,7 +52,7 @@
     Convert* Clone(CloneContext& ctx) override;
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "convert"; }
+    std::string FriendlyName() const override { return "convert"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/core_builtin_call.h b/src/tint/lang/core/ir/core_builtin_call.h
index b102071..9e7e9f0 100644
--- a/src/tint/lang/core/ir/core_builtin_call.h
+++ b/src/tint/lang/core/ir/core_builtin_call.h
@@ -54,16 +54,16 @@
     CoreBuiltinCall* Clone(CloneContext& ctx) override;
 
     /// @returns the builtin function
-    core::BuiltinFn Func() { return func_; }
+    core::BuiltinFn Func() const { return func_; }
 
     /// @returns the identifier for the function
-    size_t FuncId() override { return static_cast<size_t>(func_); }
+    size_t FuncId() const override { return static_cast<size_t>(func_); }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return core::str(func_); }
+    std::string FriendlyName() const override { return core::str(func_); }
 
     /// @returns the table data to validate this builtin
-    const core::intrinsic::TableData& TableData() override {
+    const core::intrinsic::TableData& TableData() const override {
         return core::intrinsic::Dialect::kData;
     }
 
diff --git a/src/tint/lang/core/ir/discard.h b/src/tint/lang/core/ir/discard.h
index 31a805d..62e20f7 100644
--- a/src/tint/lang/core/ir/discard.h
+++ b/src/tint/lang/core/ir/discard.h
@@ -46,7 +46,7 @@
     Discard* Clone(CloneContext& ctx) override;
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "discard"; }
+    std::string FriendlyName() const override { return "discard"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit.h b/src/tint/lang/core/ir/exit.h
index 8a3d448..78862c0 100644
--- a/src/tint/lang/core/ir/exit.h
+++ b/src/tint/lang/core/ir/exit.h
@@ -48,6 +48,9 @@
     /// @return the control instruction that this exit is associated with
     ir::ControlInstruction* ControlInstruction() { return ctrl_inst_; }
 
+    /// @return the control instruction that this exit is associated with
+    const ir::ControlInstruction* ControlInstruction() const { return ctrl_inst_; }
+
   protected:
     /// Sets control instruction that this exit is associated with
     /// @param ctrl_inst the new ControlInstruction that this exit is associated with
diff --git a/src/tint/lang/core/ir/exit_if.cc b/src/tint/lang/core/ir/exit_if.cc
index 373a682..74cd394 100644
--- a/src/tint/lang/core/ir/exit_if.cc
+++ b/src/tint/lang/core/ir/exit_if.cc
@@ -59,4 +59,8 @@
     return static_cast<ir::If*>(ControlInstruction());
 }
 
+const ir::If* ExitIf::If() const {
+    return static_cast<const ir::If*>(ControlInstruction());
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_if.h b/src/tint/lang/core/ir/exit_if.h
index be83148..3b6f568 100644
--- a/src/tint/lang/core/ir/exit_if.h
+++ b/src/tint/lang/core/ir/exit_if.h
@@ -62,8 +62,11 @@
     /// @returns the if being exited
     ir::If* If();
 
+    /// @returns the if being exited
+    const ir::If* If() const;
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "exit_if"; }
+    std::string FriendlyName() const override { return "exit_if"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_loop.cc b/src/tint/lang/core/ir/exit_loop.cc
index 1d06680..fe8da1b 100644
--- a/src/tint/lang/core/ir/exit_loop.cc
+++ b/src/tint/lang/core/ir/exit_loop.cc
@@ -60,4 +60,8 @@
     return static_cast<ir::Loop*>(ControlInstruction());
 }
 
+const ir::Loop* ExitLoop::Loop() const {
+    return static_cast<const ir::Loop*>(ControlInstruction());
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_loop.h b/src/tint/lang/core/ir/exit_loop.h
index 83bb20e..0686fc2 100644
--- a/src/tint/lang/core/ir/exit_loop.h
+++ b/src/tint/lang/core/ir/exit_loop.h
@@ -62,8 +62,11 @@
     /// @returns the loop being exited
     ir::Loop* Loop();
 
+    /// @returns the loop being exited
+    const ir::Loop* Loop() const;
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "exit_loop"; }
+    std::string FriendlyName() const override { return "exit_loop"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_switch.cc b/src/tint/lang/core/ir/exit_switch.cc
index fe7ef6c..5d90811 100644
--- a/src/tint/lang/core/ir/exit_switch.cc
+++ b/src/tint/lang/core/ir/exit_switch.cc
@@ -59,4 +59,8 @@
     return static_cast<ir::Switch*>(ControlInstruction());
 }
 
+const ir::Switch* ExitSwitch::Switch() const {
+    return static_cast<const ir::Switch*>(ControlInstruction());
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_switch.h b/src/tint/lang/core/ir/exit_switch.h
index 3b614f1..878f73b 100644
--- a/src/tint/lang/core/ir/exit_switch.h
+++ b/src/tint/lang/core/ir/exit_switch.h
@@ -62,8 +62,11 @@
     /// @returns the switch being exited
     ir::Switch* Switch();
 
+    /// @returns the switch being exited
+    const ir::Switch* Switch() const;
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "exit_switch"; }
+    std::string FriendlyName() const override { return "exit_switch"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/function.h b/src/tint/lang/core/ir/function.h
index 33639bb..92699a3 100644
--- a/src/tint/lang/core/ir/function.h
+++ b/src/tint/lang/core/ir/function.h
@@ -88,7 +88,7 @@
     void SetStage(PipelineStage stage) { pipeline_stage_ = stage; }
 
     /// @returns the function pipeline stage
-    PipelineStage Stage() { return pipeline_stage_; }
+    PipelineStage Stage() const { return pipeline_stage_; }
 
     /// Sets the workgroup size
     /// @param x the x size
@@ -100,10 +100,10 @@
     void ClearWorkgroupSize() { workgroup_size_ = {}; }
 
     /// @returns the workgroup size information
-    std::optional<std::array<uint32_t, 3>> WorkgroupSize() { return workgroup_size_; }
+    std::optional<std::array<uint32_t, 3>> WorkgroupSize() const { return workgroup_size_; }
 
     /// @returns the return type for the function
-    const core::type::Type* ReturnType() { return return_.type; }
+    const core::type::Type* ReturnType() const { return return_.type; }
 
     /// Sets the return attributes
     /// @param builtin the builtin to set
@@ -112,7 +112,7 @@
         return_.builtin = builtin;
     }
     /// @returns the return builtin attribute
-    std::optional<enum ReturnBuiltin> ReturnBuiltin() { return return_.builtin; }
+    std::optional<enum ReturnBuiltin> ReturnBuiltin() const { return return_.builtin; }
     /// Clears the return builtin attribute.
     void ClearReturnBuiltin() { return_.builtin = {}; }
 
@@ -123,7 +123,7 @@
         return_.location = {loc, interp};
     }
     /// @returns the return location
-    std::optional<Location> ReturnLocation() { return return_.location; }
+    std::optional<Location> ReturnLocation() const { return return_.location; }
     /// Clears the return location attribute.
     void ClearReturnLocation() { return_.location = {}; }
 
@@ -131,7 +131,7 @@
     /// @param val the invariant value to set
     void SetReturnInvariant(bool val) { return_.invariant = val; }
     /// @returns the return invariant value
-    bool ReturnInvariant() { return return_.invariant; }
+    bool ReturnInvariant() const { return return_.invariant; }
 
     /// Sets the function parameters
     /// @param params the function parameters
@@ -144,15 +144,22 @@
     /// @returns the function parameters
     const VectorRef<FunctionParam*> Params() { return params_; }
 
+    /// @returns the function parameters
+    VectorRef<const FunctionParam*> Params() const { return params_; }
+
     /// Sets the root block for the function
     /// @param target the root block
     void SetBlock(Block* target) {
         TINT_ASSERT(target != nullptr);
         block_ = target;
     }
+
     /// @returns the function root block
     ir::Block* Block() { return block_; }
 
+    /// @returns the function root block
+    const ir::Block* Block() const { return block_; }
+
     /// Destroys the function and all of its instructions.
     void Destroy() override;
 
diff --git a/src/tint/lang/core/ir/function_param.h b/src/tint/lang/core/ir/function_param.h
index 0955ef35..4f8e22c 100644
--- a/src/tint/lang/core/ir/function_param.h
+++ b/src/tint/lang/core/ir/function_param.h
@@ -78,7 +78,7 @@
     ~FunctionParam() override;
 
     /// @returns the type of the var
-    const core::type::Type* Type() override { return type_; }
+    const core::type::Type* Type() const override { return type_; }
 
     /// @copydoc Value::Clone()
     FunctionParam* Clone(CloneContext& ctx) override;
@@ -90,7 +90,7 @@
         builtin_ = val;
     }
     /// @returns the builtin set for the parameter
-    std::optional<FunctionParam::Builtin> Builtin() { return builtin_; }
+    std::optional<FunctionParam::Builtin> Builtin() const { return builtin_; }
     /// Clears the builtin attribute.
     void ClearBuiltin() { builtin_ = {}; }
 
@@ -98,7 +98,7 @@
     /// @param val the value to set for invariant
     void SetInvariant(bool val) { invariant_ = val; }
     /// @returns true if parameter is invariant
-    bool Invariant() { return invariant_; }
+    bool Invariant() const { return invariant_; }
 
     /// Sets the location
     /// @param loc the location value
@@ -107,7 +107,7 @@
         location_ = {loc, interpolation};
     }
     /// @returns the location if `Attributes` contains `kLocation`
-    std::optional<struct Location> Location() { return location_; }
+    std::optional<struct Location> Location() const { return location_; }
     /// Clears the location attribute.
     void ClearLocation() { location_ = {}; }
 
@@ -116,7 +116,7 @@
     /// @param binding the binding
     void SetBindingPoint(uint32_t group, uint32_t binding) { binding_point_ = {group, binding}; }
     /// @returns the binding points if `Attributes` contains `kBindingPoint`
-    std::optional<struct BindingPoint>& BindingPoint() { return binding_point_; }
+    std::optional<struct BindingPoint> BindingPoint() const { return binding_point_; }
 
   private:
     const core::type::Type* type_ = nullptr;
diff --git a/src/tint/lang/core/ir/if.h b/src/tint/lang/core/ir/if.h
index ccf8c1d..2ac6413 100644
--- a/src/tint/lang/core/ir/if.h
+++ b/src/tint/lang/core/ir/if.h
@@ -76,14 +76,23 @@
     /// @returns the if condition
     Value* Condition() { return operands_[kConditionOperandOffset]; }
 
+    /// @returns the if condition
+    const Value* Condition() const { return operands_[kConditionOperandOffset]; }
+
     /// @returns the true block
     ir::Block* True() { return true_; }
 
+    /// @returns the true block
+    const ir::Block* True() const { return true_; }
+
     /// @returns the false block
     ir::Block* False() { return false_; }
 
+    /// @returns the false block
+    const ir::Block* False() const { return false_; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "if"; }
+    std::string FriendlyName() const override { return "if"; }
 
   private:
     ir::Block* true_ = nullptr;
diff --git a/src/tint/lang/core/ir/instruction.h b/src/tint/lang/core/ir/instruction.h
index c392fff..d08104b 100644
--- a/src/tint/lang/core/ir/instruction.h
+++ b/src/tint/lang/core/ir/instruction.h
@@ -57,15 +57,21 @@
     /// @returns the operands of the instruction
     virtual VectorRef<ir::Value*> Operands() = 0;
 
+    /// @returns the operands of the instruction
+    virtual VectorRef<const ir::Value*> Operands() const = 0;
+
     /// @returns the result values for this instruction
     virtual VectorRef<InstructionResult*> Results() = 0;
 
+    /// @returns the result values for this instruction
+    virtual VectorRef<const InstructionResult*> Results() const = 0;
+
     /// Removes the instruction from the block, and destroys all the result values.
     /// The result values must not be in use.
     virtual void Destroy();
 
     /// @returns the friendly name for the instruction
-    virtual std::string FriendlyName() = 0;
+    virtual std::string FriendlyName() const = 0;
 
     /// @param ctx the CloneContext used to clone this instruction
     /// @returns a clone of this instruction
@@ -85,6 +91,9 @@
     /// @returns the block that owns this instruction
     ir::Block* Block() { return block_; }
 
+    /// @returns the block that owns this instruction
+    const ir::Block* Block() const { return block_; }
+
     /// Adds the new instruction before the given instruction in the owning block
     /// @param before the instruction to insert before
     void InsertBefore(Instruction* before);
@@ -105,6 +114,14 @@
         return idx < res.Length() ? res[idx] : nullptr;
     }
 
+    /// @param idx the index of the operand
+    /// @returns the operand with index @p idx, or `nullptr` if there are no operands or the index
+    /// is out of bounds.
+    const Value* Operand(size_t idx = 0) const {
+        auto res = Operands();
+        return idx < res.Length() ? res[idx] : nullptr;
+    }
+
     /// @param idx the index of the result
     /// @returns the result with index @p idx, or `nullptr` if there are no results or the index is
     /// out of bounds.
@@ -113,6 +130,14 @@
         return idx < res.Length() ? res[idx] : nullptr;
     }
 
+    /// @param idx the index of the result
+    /// @returns the result with index @p idx, or `nullptr` if there are no results or the index is
+    /// out of bounds.
+    const Value* Result(size_t idx = 0) const {
+        auto res = Results();
+        return idx < res.Length() ? res[idx] : nullptr;
+    }
+
     /// Pointer to the next instruction in the list
     Instruction* next = nullptr;
     /// Pointer to the previous instruction in the list
diff --git a/src/tint/lang/core/ir/instruction_result.h b/src/tint/lang/core/ir/instruction_result.h
index 8ba3878..c5a055e 100644
--- a/src/tint/lang/core/ir/instruction_result.h
+++ b/src/tint/lang/core/ir/instruction_result.h
@@ -47,7 +47,7 @@
     void Destroy() override;
 
     /// @returns the type of the value
-    const core::type::Type* Type() override { return type_; }
+    const core::type::Type* Type() const override { return type_; }
 
     /// @copydoc Value::Clone()
     InstructionResult* Clone(CloneContext& ctx) override;
@@ -63,6 +63,9 @@
     /// @returns the source instruction, if any
     Instruction* Source() { return source_; }
 
+    /// @returns the source instruction, if any
+    const Instruction* Source() const { return source_; }
+
   private:
     Instruction* source_ = nullptr;
     const core::type::Type* type_ = nullptr;
diff --git a/src/tint/lang/core/ir/let.h b/src/tint/lang/core/ir/let.h
index f983a2c..c55ed3c 100644
--- a/src/tint/lang/core/ir/let.h
+++ b/src/tint/lang/core/ir/let.h
@@ -52,8 +52,11 @@
     /// @returns the value
     ir::Value* Value() { return operands_[kValueOperandOffset]; }
 
+    /// @returns the value
+    const ir::Value* Value() const { return operands_[kValueOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "let"; }
+    std::string FriendlyName() const override { return "let"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load.h b/src/tint/lang/core/ir/load.h
index daab943..167fa87 100644
--- a/src/tint/lang/core/ir/load.h
+++ b/src/tint/lang/core/ir/load.h
@@ -54,8 +54,11 @@
     /// @returns the value being loaded from
     Value* From() { return operands_[kFromOperandOffset]; }
 
+    /// @returns the value being loaded from
+    const Value* From() const { return operands_[kFromOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "load"; }
+    std::string FriendlyName() const override { return "load"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load_vector_element.h b/src/tint/lang/core/ir/load_vector_element.h
index e883c79..f8cbb37 100644
--- a/src/tint/lang/core/ir/load_vector_element.h
+++ b/src/tint/lang/core/ir/load_vector_element.h
@@ -57,11 +57,17 @@
     /// @returns the vector pointer value
     ir::Value* From() { return operands_[kFromOperandOffset]; }
 
+    /// @returns the vector pointer value
+    const ir::Value* From() const { return operands_[kFromOperandOffset]; }
+
     /// @returns the new vector element index
     ir::Value* Index() { return operands_[kIndexOperandOffset]; }
 
+    /// @returns the new vector element index
+    const ir::Value* Index() const { return operands_[kIndexOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "load_vector_element"; }
+    std::string FriendlyName() const override { return "load_vector_element"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/loop.h b/src/tint/lang/core/ir/loop.h
index 906032f..ee03af0 100644
--- a/src/tint/lang/core/ir/loop.h
+++ b/src/tint/lang/core/ir/loop.h
@@ -87,6 +87,9 @@
     /// @returns the switch initializer block
     ir::Block* Initializer() { return initializer_; }
 
+    /// @returns the switch initializer block
+    const ir::Block* Initializer() const { return initializer_; }
+
     /// @returns true if the loop uses an initializer block. If true, then the Loop first branches
     /// to the initializer block, otherwise it first branches to the body block.
     bool HasInitializer();
@@ -94,11 +97,17 @@
     /// @returns the switch start block
     ir::MultiInBlock* Body() { return body_; }
 
+    /// @returns the switch start block
+    const ir::MultiInBlock* Body() const { return body_; }
+
     /// @returns the switch continuing block
     ir::MultiInBlock* Continuing() { return continuing_; }
 
+    /// @returns the switch continuing block
+    const ir::MultiInBlock* Continuing() const { return continuing_; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "loop"; }
+    std::string FriendlyName() const override { return "loop"; }
 
   private:
     ir::Block* initializer_ = nullptr;
diff --git a/src/tint/lang/core/ir/module.cc b/src/tint/lang/core/ir/module.cc
index d8f5448..8719e2b 100644
--- a/src/tint/lang/core/ir/module.cc
+++ b/src/tint/lang/core/ir/module.cc
@@ -41,14 +41,14 @@
 
 Module& Module::operator=(Module&&) = default;
 
-Symbol Module::NameOf(Instruction* inst) {
+Symbol Module::NameOf(const Instruction* inst) const {
     if (inst->Results().Length() != 1) {
         return Symbol{};
     }
     return NameOf(inst->Result(0));
 }
 
-Symbol Module::NameOf(Value* value) {
+Symbol Module::NameOf(const Value* value) const {
     return value_to_name_.Get(value).value_or(Symbol{});
 }
 
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index 51148dd..1a6f1f7 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -53,7 +53,7 @@
     GenerationID prog_id_;
 
     /// Map of value to name
-    Hashmap<Value*, Symbol, 32> value_to_name_;
+    Hashmap<const Value*, Symbol, 32> value_to_name_;
 
   public:
     /// Constructor
@@ -72,11 +72,11 @@
     /// @param inst the instruction
     /// @return the name of the given instruction, or an invalid symbol if the instruction is not
     /// named or does not have a single return value.
-    Symbol NameOf(Instruction* inst);
+    Symbol NameOf(const Instruction* inst) const;
 
     /// @param value the value
     /// @return the name of the given value, or an invalid symbol if the value is not named.
-    Symbol NameOf(Value* value);
+    Symbol NameOf(const Value* value) const;
 
     /// @param inst the instruction to set the name of
     /// @param name the desired name of the value. May be suffixed on collision.
@@ -94,6 +94,9 @@
     /// @return the type manager for the module
     core::type::Manager& Types() { return constant_values.types; }
 
+    /// @return the type manager for the module
+    const core::type::Manager& Types() const { return constant_values.types; }
+
     /// The block allocator
     BlockAllocator<Block> blocks;
 
diff --git a/src/tint/lang/core/ir/multi_in_block.h b/src/tint/lang/core/ir/multi_in_block.h
index ed2b117..76915f1 100644
--- a/src/tint/lang/core/ir/multi_in_block.h
+++ b/src/tint/lang/core/ir/multi_in_block.h
@@ -31,11 +31,7 @@
 #include <utility>
 
 #include "src/tint/lang/core/ir/block.h"
-
-// Forward declarations
-namespace tint::core::ir {
-class BlockParam;
-}
+#include "src/tint/lang/core/ir/block_param.h"
 
 namespace tint::core::ir {
 
@@ -63,7 +59,10 @@
     void SetParams(std::initializer_list<BlockParam*> params);
 
     /// @returns the params to the block
-    const Vector<BlockParam*, 2>& Params() { return params_; }
+    VectorRef<BlockParam*> Params() { return params_; }
+
+    /// @returns the params to the block
+    VectorRef<const BlockParam*> Params() const { return params_; }
 
     /// @returns branches made to this block by sibling blocks
     const VectorRef<ir::Terminator*> InboundSiblingBranches() { return inbound_sibling_branches_; }
diff --git a/src/tint/lang/core/ir/next_iteration.h b/src/tint/lang/core/ir/next_iteration.h
index f14be17..58cc347 100644
--- a/src/tint/lang/core/ir/next_iteration.h
+++ b/src/tint/lang/core/ir/next_iteration.h
@@ -58,8 +58,11 @@
     /// @returns the loop being iterated
     ir::Loop* Loop() { return loop_; }
 
+    /// @returns the loop being iterated
+    const ir::Loop* Loop() const { return loop_; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "next_iteration"; }
+    std::string FriendlyName() const override { return "next_iteration"; }
 
   private:
     ir::Loop* loop_ = nullptr;
diff --git a/src/tint/lang/core/ir/operand_instruction.h b/src/tint/lang/core/ir/operand_instruction.h
index 24c6e97..c16ee12 100644
--- a/src/tint/lang/core/ir/operand_instruction.h
+++ b/src/tint/lang/core/ir/operand_instruction.h
@@ -94,9 +94,15 @@
     /// @returns the operands of the instruction
     VectorRef<ir::Value*> Operands() override { return operands_; }
 
+    /// @returns the operands of the instruction
+    VectorRef<const ir::Value*> Operands() const override { return operands_; }
+
     /// @returns the result values for this instruction
     VectorRef<InstructionResult*> Results() override { return results_; }
 
+    /// @returns the result values for this instruction
+    VectorRef<const InstructionResult*> Results() const override { return results_; }
+
     /// @param idx the index of the result
     /// @returns the result with index @p idx, or `nullptr` if there are no results or the index is
     /// out of bounds.
@@ -104,6 +110,13 @@
         return idx < results_.Length() ? results_[idx] : nullptr;
     }
 
+    /// @param idx the index of the result
+    /// @returns the result with index @p idx, or `nullptr` if there are no results or the index is
+    /// out of bounds.
+    const InstructionResult* Result(size_t idx = 0) const {
+        return idx < results_.Length() ? results_[idx] : nullptr;
+    }
+
   protected:
     /// Append a new operand to the operand list for this instruction.
     /// @param idx the index the operand should be at
diff --git a/src/tint/lang/core/ir/return.cc b/src/tint/lang/core/ir/return.cc
index 131766d..f105afc 100644
--- a/src/tint/lang/core/ir/return.cc
+++ b/src/tint/lang/core/ir/return.cc
@@ -56,7 +56,11 @@
     return ctx.ir.instructions.Create<Return>(fn);
 }
 
-Function* Return::Func() const {
+Function* Return::Func() {
+    return tint::As<Function>(operands_[kFunctionOperandOffset]);
+}
+
+const Function* Return::Func() const {
     return tint::As<Function>(operands_[kFunctionOperandOffset]);
 }
 
diff --git a/src/tint/lang/core/ir/return.h b/src/tint/lang/core/ir/return.h
index afacf4a..4fcdc86 100644
--- a/src/tint/lang/core/ir/return.h
+++ b/src/tint/lang/core/ir/return.h
@@ -64,7 +64,10 @@
     Return* Clone(CloneContext& ctx) override;
 
     /// @returns the function being returned
-    Function* Func() const;
+    Function* Func();
+
+    /// @returns the function being returned
+    const Function* Func() const;
 
     /// @returns the return value, or nullptr
     ir::Value* Value() const {
@@ -79,7 +82,7 @@
     size_t ArgsOperandOffset() const override { return kArgsOperandOffset; }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "return"; }
+    std::string FriendlyName() const override { return "return"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store.h b/src/tint/lang/core/ir/store.h
index a5d8444..9ed7cc5 100644
--- a/src/tint/lang/core/ir/store.h
+++ b/src/tint/lang/core/ir/store.h
@@ -56,11 +56,17 @@
     /// @returns the value being stored too
     Value* To() { return operands_[kToOperandOffset]; }
 
+    /// @returns the value being stored too
+    const Value* To() const { return operands_[kToOperandOffset]; }
+
     /// @returns the value being stored
     Value* From() { return operands_[kFromOperandOffset]; }
 
+    /// @returns the value being stored
+    const Value* From() const { return operands_[kFromOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "store"; }
+    std::string FriendlyName() const override { return "store"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store_vector_element.h b/src/tint/lang/core/ir/store_vector_element.h
index f76ec79..2ef3895 100644
--- a/src/tint/lang/core/ir/store_vector_element.h
+++ b/src/tint/lang/core/ir/store_vector_element.h
@@ -60,14 +60,23 @@
     /// @returns the vector pointer value
     ir::Value* To() { return operands_[kToOperandOffset]; }
 
+    /// @returns the vector pointer value
+    const ir::Value* To() const { return operands_[kToOperandOffset]; }
+
     /// @returns the new vector element index
     ir::Value* Index() { return operands_[kIndexOperandOffset]; }
 
+    /// @returns the new vector element index
+    const ir::Value* Index() const { return operands_[kIndexOperandOffset]; }
+
     /// @returns the new vector element value
     ir::Value* Value() { return operands_[kValueOperandOffset]; }
 
+    /// @returns the new vector element value
+    const ir::Value* Value() const { return operands_[kValueOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "store_vector_element"; }
+    std::string FriendlyName() const override { return "store_vector_element"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/switch.h b/src/tint/lang/core/ir/switch.h
index ddc9f32..b9edc00 100644
--- a/src/tint/lang/core/ir/switch.h
+++ b/src/tint/lang/core/ir/switch.h
@@ -94,11 +94,17 @@
     /// @returns the switch cases
     Vector<Case, 4>& Cases() { return cases_; }
 
+    /// @returns the switch cases
+    VectorRef<Case> Cases() const { return cases_; }
+
     /// @returns the condition
     Value* Condition() { return operands_[kConditionOperandOffset]; }
 
+    /// @returns the condition
+    const Value* Condition() const { return operands_[kConditionOperandOffset]; }
+
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "switch"; }
+    std::string FriendlyName() const override { return "switch"; }
 
   private:
     Vector<Case, 4> cases_;
diff --git a/src/tint/lang/core/ir/swizzle.h b/src/tint/lang/core/ir/swizzle.h
index afda992..1e5b0fc 100644
--- a/src/tint/lang/core/ir/swizzle.h
+++ b/src/tint/lang/core/ir/swizzle.h
@@ -54,11 +54,14 @@
     /// @returns the object used for the access
     Value* Object() { return operands_[kObjectOperandOffset]; }
 
+    /// @returns the object used for the access
+    const Value* Object() const { return operands_[kObjectOperandOffset]; }
+
     /// @returns the swizzle indices
-    VectorRef<uint32_t> Indices() { return indices_; }
+    VectorRef<uint32_t> Indices() const { return indices_; }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "swizzle"; }
+    std::string FriendlyName() const override { return "swizzle"; }
 
   private:
     Vector<uint32_t, 4> indices_;
diff --git a/src/tint/lang/core/ir/terminate_invocation.h b/src/tint/lang/core/ir/terminate_invocation.h
index 7697c78..b0308bb 100644
--- a/src/tint/lang/core/ir/terminate_invocation.h
+++ b/src/tint/lang/core/ir/terminate_invocation.h
@@ -43,7 +43,7 @@
     TerminateInvocation* Clone(CloneContext& ctx) override;
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "terminate_invocation"; }
+    std::string FriendlyName() const override { return "terminate_invocation"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/terminator.h b/src/tint/lang/core/ir/terminator.h
index e9df9d1..f02c7b2 100644
--- a/src/tint/lang/core/ir/terminator.h
+++ b/src/tint/lang/core/ir/terminator.h
@@ -49,6 +49,11 @@
 
     /// @returns the call arguments
     tint::Slice<Value* const> Args() { return operands_.Slice().Offset(ArgsOperandOffset()); }
+
+    /// @returns the call arguments
+    tint::Slice<const Value* const> Args() const {
+        return operands_.Slice().Offset(ArgsOperandOffset());
+    }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/unary.h b/src/tint/lang/core/ir/unary.h
index c25f5fc..af03473 100644
--- a/src/tint/lang/core/ir/unary.h
+++ b/src/tint/lang/core/ir/unary.h
@@ -60,11 +60,14 @@
     /// @returns the value for the instruction
     Value* Val() { return operands_[kValueOperandOffset]; }
 
+    /// @returns the value for the instruction
+    const Value* Val() const { return operands_[kValueOperandOffset]; }
+
     /// @returns the unary operator
-    UnaryOp Op() { return op_; }
+    UnaryOp Op() const { return op_; }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "unary"; }
+    std::string FriendlyName() const override { return "unary"; }
 
   private:
     UnaryOp op_;
diff --git a/src/tint/lang/core/ir/unreachable.h b/src/tint/lang/core/ir/unreachable.h
index 577b2f9..43cddc9 100644
--- a/src/tint/lang/core/ir/unreachable.h
+++ b/src/tint/lang/core/ir/unreachable.h
@@ -43,7 +43,7 @@
     Unreachable* Clone(CloneContext& ctx) override;
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "unreachable"; }
+    std::string FriendlyName() const override { return "unreachable"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/user_call.h b/src/tint/lang/core/ir/user_call.h
index 775d7a5..4036208 100644
--- a/src/tint/lang/core/ir/user_call.h
+++ b/src/tint/lang/core/ir/user_call.h
@@ -65,12 +65,15 @@
     /// @returns the called function
     Function* Target() { return operands_[kFunctionOperandOffset]->As<ir::Function>(); }
 
+    /// @returns the called function
+    const Function* Target() const { return operands_[kFunctionOperandOffset]->As<ir::Function>(); }
+
     /// Sets called function
     /// @param target the new target of the call
     void SetTarget(Function* target) { SetOperand(kFunctionOperandOffset, target); }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "call"; }
+    std::string FriendlyName() const override { return "call"; }
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/value.h b/src/tint/lang/core/ir/value.h
index b368ff0..55f7463 100644
--- a/src/tint/lang/core/ir/value.h
+++ b/src/tint/lang/core/ir/value.h
@@ -65,7 +65,7 @@
     ~Value() override;
 
     /// @returns the type of the value
-    virtual const core::type::Type* Type() { return nullptr; }
+    virtual const core::type::Type* Type() const { return nullptr; }
 
     /// Destroys the Value. Once called, the Value must not be used again.
     /// The Value must not be in use by any instruction.
@@ -90,6 +90,19 @@
     /// uses the value for multiple different operands.
     const Hashset<Usage, 4>& Usages() { return uses_; }
 
+    /// @returns true if this Value has any usages
+    bool IsUsed() const { return !uses_.IsEmpty(); }
+
+    /// @returns the number of usages of this Value
+    size_t NumUsages() const { return uses_.Count(); }
+
+    /// @returns true if the usages contains the instruction and operand index pair.
+    /// @param instruction the instruction
+    /// @param operand_index the in
+    bool HasUsage(const Instruction* instruction, size_t operand_index) const {
+        return uses_.Contains({const_cast<Instruction*>(instruction), operand_index});
+    }
+
     /// Apply a function to all uses of the value that exist prior to calling this method.
     /// @param func the function will be applied to each use
     void ForEachUse(std::function<void(Usage use)> func);
diff --git a/src/tint/lang/core/ir/var.h b/src/tint/lang/core/ir/var.h
index 29d1ad4..cd72271 100644
--- a/src/tint/lang/core/ir/var.h
+++ b/src/tint/lang/core/ir/var.h
@@ -71,25 +71,27 @@
     void SetInitializer(Value* initializer);
     /// @returns the initializer
     Value* Initializer() { return operands_[kInitializerOperandOffset]; }
+    /// @returns the initializer
+    const Value* Initializer() const { return operands_[kInitializerOperandOffset]; }
 
     /// Sets the binding point
     /// @param group the group
     /// @param binding the binding
     void SetBindingPoint(uint32_t group, uint32_t binding) { binding_point_ = {group, binding}; }
     /// @returns the binding points if `Attributes` contains `kBindingPoint`
-    std::optional<struct BindingPoint> BindingPoint() { return binding_point_; }
+    std::optional<struct BindingPoint> BindingPoint() const { return binding_point_; }
 
     /// Sets the IO attributes
     /// @param attrs the attributes
     void SetAttributes(const IOAttributes& attrs) { attributes_ = attrs; }
     /// @returns the IO attributes
-    const IOAttributes& Attributes() { return attributes_; }
+    const IOAttributes& Attributes() const { return attributes_; }
 
     /// Destroys this instruction along with any assignment instructions, if the var is never read.
     void DestroyIfOnlyAssigned();
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return "var"; }
+    std::string FriendlyName() const override { return "var"; }
 
   private:
     std::optional<struct BindingPoint> binding_point_;
diff --git a/src/tint/lang/spirv/ir/builtin_call.h b/src/tint/lang/spirv/ir/builtin_call.h
index fbc958b..54e357f 100644
--- a/src/tint/lang/spirv/ir/builtin_call.h
+++ b/src/tint/lang/spirv/ir/builtin_call.h
@@ -54,16 +54,16 @@
     BuiltinCall* Clone(core::ir::CloneContext& ctx) override;
 
     /// @returns the builtin function
-    BuiltinFn Func() { return func_; }
+    BuiltinFn Func() const { return func_; }
 
     /// @returns the identifier for the function
-    size_t FuncId() override { return static_cast<size_t>(func_); }
+    size_t FuncId() const override { return static_cast<size_t>(func_); }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return std::string("spirv.") + str(func_); }
+    std::string FriendlyName() const override { return std::string("spirv.") + str(func_); }
 
     /// @returns the table data to validate this builtin
-    const core::intrinsic::TableData& TableData() override {
+    const core::intrinsic::TableData& TableData() const override {
         return spirv::intrinsic::Dialect::kData;
     }
 
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index 942c326..172733e 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -261,13 +261,13 @@
     Hashmap<const core::type::Type*, uint32_t, 4> undef_values_;
 
     /// The map of non-constant values to their result IDs.
-    Hashmap<core::ir::Value*, uint32_t, 8> values_;
+    Hashmap<const core::ir::Value*, uint32_t, 8> values_;
 
     /// The map of blocks to the IDs of their label instructions.
-    Hashmap<core::ir::Block*, uint32_t, 8> block_labels_;
+    Hashmap<const core::ir::Block*, uint32_t, 8> block_labels_;
 
     /// The map of control instructions to the IDs of the label of their SPIR-V merge blocks.
-    Hashmap<core::ir::ControlInstruction*, uint32_t, 8> merge_block_labels_;
+    Hashmap<const core::ir::ControlInstruction*, uint32_t, 8> merge_block_labels_;
 
     /// The map of extended instruction set names to their result IDs.
     Hashmap<std::string_view, uint32_t, 2> imports_;
@@ -555,7 +555,7 @@
     /// Get the ID of the label for `block`.
     /// @param block the block to get the label ID for
     /// @returns the ID of the block's label
-    uint32_t Label(core::ir::Block* block) {
+    uint32_t Label(const core::ir::Block* block) {
         return block_labels_.GetOrCreate(block, [&] { return module_.NextId(); });
     }
 
diff --git a/src/tint/lang/wgsl/ir/builtin_call.h b/src/tint/lang/wgsl/ir/builtin_call.h
index 331c558..043129e 100644
--- a/src/tint/lang/wgsl/ir/builtin_call.h
+++ b/src/tint/lang/wgsl/ir/builtin_call.h
@@ -54,16 +54,18 @@
     BuiltinCall* Clone(core::ir::CloneContext& ctx) override;
 
     /// @returns the builtin function
-    BuiltinFn Func() { return fn_; }
+    BuiltinFn Func() const { return fn_; }
 
     /// @returns the identifier for the function
-    size_t FuncId() override { return static_cast<size_t>(fn_); }
+    size_t FuncId() const override { return static_cast<size_t>(fn_); }
 
     /// @returns the friendly name for the instruction
-    std::string FriendlyName() override { return std::string("wgsl.") + str(fn_); }
+    std::string FriendlyName() const override { return std::string("wgsl.") + str(fn_); }
 
     /// @returns the table data to validate this builtin
-    const core::intrinsic::TableData& TableData() override { return intrinsic::Dialect::kData; }
+    const core::intrinsic::TableData& TableData() const override {
+        return intrinsic::Dialect::kData;
+    }
 
   private:
     BuiltinFn fn_;