[ir] Add concept of overrides to the IR.

This CL adds an `override` instruction into the IR. The `program_to_ir`
will convert an `Override` as needed into the given instruction.

Note, this will then immediately fail validation as only `var` is
currently permitted in the root block.

Bug: 374971092
Change-Id: I40b4bd33e020a222707a8cb9c3584abe8b327112
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/212094
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/BUILD.bazel b/src/tint/lang/core/ir/BUILD.bazel
index fe29cfa..b12f3ee 100644
--- a/src/tint/lang/core/ir/BUILD.bazel
+++ b/src/tint/lang/core/ir/BUILD.bazel
@@ -77,6 +77,7 @@
     "multi_in_block.cc",
     "next_iteration.cc",
     "operand_instruction.cc",
+    "override.cc",
     "return.cc",
     "store.cc",
     "store_vector_element.cc",
@@ -132,6 +133,7 @@
     "multi_in_block.h",
     "next_iteration.h",
     "operand_instruction.h",
+    "override.h",
     "referenced_functions.h",
     "referenced_module_vars.h",
     "return.h",
@@ -209,6 +211,7 @@
     "multi_in_block_test.cc",
     "next_iteration_test.cc",
     "operand_instruction_test.cc",
+    "override_test.cc",
     "referenced_functions_test.cc",
     "referenced_module_vars_test.cc",
     "return_test.cc",
diff --git a/src/tint/lang/core/ir/BUILD.cmake b/src/tint/lang/core/ir/BUILD.cmake
index 3fec311..c833689 100644
--- a/src/tint/lang/core/ir/BUILD.cmake
+++ b/src/tint/lang/core/ir/BUILD.cmake
@@ -119,6 +119,8 @@
   lang/core/ir/next_iteration.h
   lang/core/ir/operand_instruction.cc
   lang/core/ir/operand_instruction.h
+  lang/core/ir/override.cc
+  lang/core/ir/override.h
   lang/core/ir/referenced_functions.h
   lang/core/ir/referenced_module_vars.h
   lang/core/ir/return.cc
@@ -213,6 +215,7 @@
   lang/core/ir/multi_in_block_test.cc
   lang/core/ir/next_iteration_test.cc
   lang/core/ir/operand_instruction_test.cc
+  lang/core/ir/override_test.cc
   lang/core/ir/referenced_functions_test.cc
   lang/core/ir/referenced_module_vars_test.cc
   lang/core/ir/return_test.cc
diff --git a/src/tint/lang/core/ir/BUILD.gn b/src/tint/lang/core/ir/BUILD.gn
index 6e2695c..c4c2f29 100644
--- a/src/tint/lang/core/ir/BUILD.gn
+++ b/src/tint/lang/core/ir/BUILD.gn
@@ -122,6 +122,8 @@
     "next_iteration.h",
     "operand_instruction.cc",
     "operand_instruction.h",
+    "override.cc",
+    "override.h",
     "referenced_functions.h",
     "referenced_module_vars.h",
     "return.cc",
@@ -210,6 +212,7 @@
       "multi_in_block_test.cc",
       "next_iteration_test.cc",
       "operand_instruction_test.cc",
+      "override_test.cc",
       "referenced_functions_test.cc",
       "referenced_module_vars_test.cc",
       "return_test.cc",
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h
index a72b4f9..ec9b23a 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -59,6 +59,7 @@
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/lang/core/ir/next_iteration.h"
+#include "src/tint/lang/core/ir/override.h"
 #include "src/tint/lang/core/ir/return.h"
 #include "src/tint/lang/core/ir/store.h"
 #include "src/tint/lang/core/ir/store_vector_element.h"
@@ -1744,6 +1745,32 @@
         });
     }
 
+    /// Creates a new `override` declaration
+    /// @param name the override name
+    /// @param value the override value
+    /// @returns the instruction
+    template <typename VALUE>
+    ir::Override* Override(std::string_view name, VALUE&& value) {
+        auto* val = Value(std::forward<VALUE>(value));
+        if (DAWN_UNLIKELY(!val)) {
+            TINT_ASSERT(val);
+            return nullptr;
+        }
+        auto* override = Append(ir.CreateInstruction<ir::Override>(InstructionResult(val->Type())));
+        override->SetInitializer(val);
+        ir.SetName(override->Result(0), name);
+        return override;
+    }
+
+    /// Creates a new `override` declaration, with an unassigned value
+    /// @param type the override type
+    /// @returns the instruction
+    ir::Override* Override(const type::Type* type) {
+        auto* override = ir.CreateInstruction<ir::Override>(InstructionResult(type));
+        Append(override);
+        return override;
+    }
+
     /// The IR module.
     Module& ir;
 
diff --git a/src/tint/lang/core/ir/disassembler.cc b/src/tint/lang/core/ir/disassembler.cc
index dac70e0..4d9091a 100644
--- a/src/tint/lang/core/ir/disassembler.cc
+++ b/src/tint/lang/core/ir/disassembler.cc
@@ -53,6 +53,7 @@
 #include "src/tint/lang/core/ir/member_builtin_call.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/lang/core/ir/next_iteration.h"
+#include "src/tint/lang/core/ir/override.h"
 #include "src/tint/lang/core/ir/return.h"
 #include "src/tint/lang/core/ir/store.h"
 #include "src/tint/lang/core/ir/store_vector_element.h"
@@ -515,6 +516,16 @@
                 EmitOperandList(c, UserCall::kArgsOperandOffset);
             }
         },
+        [&](const Override* o) {
+            EmitValueWithType(o);
+            out_ << " = ";
+            EmitInstructionName(o);
+            if (o->Initializer()) {
+                out_ << ", ";
+                EmitOperand(o, Var::kInitializerOperandOffset);
+            }
+            out_ << " @id(" << o->OverrideId().value << ")";
+        },
         [&](const Var* v) {
             EmitValueWithType(v);
             out_ << " = ";
diff --git a/src/tint/lang/core/ir/instruction_result.h b/src/tint/lang/core/ir/instruction_result.h
index 2b6b442..dc48935 100644
--- a/src/tint/lang/core/ir/instruction_result.h
+++ b/src/tint/lang/core/ir/instruction_result.h
@@ -29,7 +29,6 @@
 #define SRC_TINT_LANG_CORE_IR_INSTRUCTION_RESULT_H_
 
 #include "src/tint/lang/core/ir/value.h"
-#include "src/tint/utils/text/string_stream.h"
 
 namespace tint::core::ir {
 
@@ -46,9 +45,6 @@
     /// @copydoc Value::Destroy
     void Destroy() override;
 
-    /// @returns the type of the value
-    const core::type::Type* Type() const override { return type_; }
-
     /// @copydoc Value::Clone()
     InstructionResult* Clone(CloneContext& ctx) override;
 
@@ -56,6 +52,9 @@
     /// @param type the new type of the value
     void SetType(const core::type::Type* type) { type_ = type; }
 
+    /// @returns the type of the value
+    const core::type::Type* Type() const override { return type_; }
+
     /// Sets the instruction for this value
     /// @param inst the instruction to set
     void SetInstruction(Instruction* inst) { instruction_ = inst; }
diff --git a/src/tint/lang/core/ir/override.cc b/src/tint/lang/core/ir/override.cc
new file mode 100644
index 0000000..cee6ede
--- /dev/null
+++ b/src/tint/lang/core/ir/override.cc
@@ -0,0 +1,74 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/override.h"
+
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/store.h"
+#include "src/tint/lang/core/type/pointer.h"
+#include "src/tint/utils/ice/ice.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Override);
+
+namespace tint::core::ir {
+
+Override::Override(Id id) : Base(id) {}
+
+Override::Override(Id id, InstructionResult* result) : Base(id) {
+    // Default to no initializer.
+    AddOperand(Override::kInitializerOperandOffset, nullptr);
+    AddResult(result);
+}
+
+Override::~Override() = default;
+
+void Override::SetInitializer(Value* initializer) {
+    SetOperand(Override::kInitializerOperandOffset, initializer);
+}
+
+Override* Override::Clone(CloneContext& ctx) {
+    auto* new_result = ctx.Clone(Result(0));
+    TINT_ASSERT(new_result);
+
+    auto* new_override = ctx.ir.CreateInstruction<Override>(new_result);
+
+    new_override->id_ = id_;
+
+    if (auto* init = Initializer()) {
+        new_override->SetInitializer(ctx.Clone(init));
+    }
+    new_override->SetOverrideId(override_id_);
+
+    auto name = ctx.ir.NameOf(this);
+    if (name.IsValid()) {
+        ctx.ir.SetName(new_override, name.Name());
+    }
+    return new_override;
+}
+
+}  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/override.h b/src/tint/lang/core/ir/override.h
new file mode 100644
index 0000000..f69cc46
--- /dev/null
+++ b/src/tint/lang/core/ir/override.h
@@ -0,0 +1,85 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_CORE_IR_OVERRIDE_H_
+#define SRC_TINT_LANG_CORE_IR_OVERRIDE_H_
+
+#include <string>
+
+#include "src/tint/api/common/override_id.h"
+#include "src/tint/lang/core/ir/operand_instruction.h"
+#include "src/tint/utils/rtti/castable.h"
+
+namespace tint::core::ir {
+
+/// A var instruction in the IR.
+class Override final : public Castable<Override, OperandInstruction<1, 1>> {
+  public:
+    /// The offset in Operands() for the initializer
+    static constexpr size_t kInitializerOperandOffset = 0;
+
+    /// The fixed number of results returned by this instruction
+    static constexpr size_t kNumResults = 1;
+
+    /// Constructor (no results, no operands)
+    /// @param id the instruction id
+    explicit Override(Id id);
+
+    /// Constructor
+    /// @param id the instruction id
+    /// @param result the result value
+    Override(Id id, InstructionResult* result);
+
+    ~Override() override;
+
+    /// @copydoc Instruction::Clone()
+    Override* Clone(CloneContext& ctx) override;
+
+    /// Sets the var initializer
+    /// @param initializer the initializer
+    void SetInitializer(Value* initializer);
+    /// @returns the initializer
+    Value* Initializer() { return Operand(kInitializerOperandOffset); }
+    /// @returns the initializer
+    const Value* Initializer() const { return Operand(kInitializerOperandOffset); }
+
+    /// Sets the override Id attribute
+    /// @param id the override id
+    void SetOverrideId(OverrideId id) { override_id_ = id; }
+    /// @returns the override ID
+    tint::OverrideId OverrideId() const { return override_id_; }
+
+    /// @returns the friendly name for the instruction
+    std::string FriendlyName() const override { return "override"; }
+
+  private:
+    tint::OverrideId override_id_;
+};
+
+}  // namespace tint::core::ir
+
+#endif  // SRC_TINT_LANG_CORE_IR_OVERRIDE_H_
diff --git a/src/tint/lang/core/ir/override_test.cc b/src/tint/lang/core/ir/override_test.cc
new file mode 100644
index 0000000..638a14b
--- /dev/null
+++ b/src/tint/lang/core/ir/override_test.cc
@@ -0,0 +1,100 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/var.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/instruction.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+
+namespace tint::core::ir {
+namespace {
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+using IR_OverrideTest = IRTestHelper;
+using IR_OverrideDeathTest = IR_OverrideTest;
+
+TEST_F(IR_OverrideDeathTest, Fail_NullType) {
+    EXPECT_DEATH_IF_SUPPORTED(
+        {
+            Module mod;
+            Builder b{mod};
+            b.Override(nullptr);
+        },
+        "internal compiler error");
+}
+
+TEST_F(IR_OverrideTest, Results) {
+    auto* var = b.Override(ty.i32());
+    EXPECT_EQ(var->Results().Length(), 1u);
+    EXPECT_TRUE(var->Result(0)->Is<InstructionResult>());
+    EXPECT_EQ(var->Result(0)->Instruction(), var);
+}
+
+TEST_F(IR_OverrideTest, Initializer_Usage) {
+    Module mod;
+    Builder b{mod};
+    auto* var = b.Override(ty.f32());
+    auto* init = b.Constant(1_f);
+    var->SetInitializer(init);
+
+    EXPECT_THAT(init->UsagesUnsorted(), testing::UnorderedElementsAre(Usage{var, 0u}));
+    var->SetInitializer(nullptr);
+    EXPECT_FALSE(init->IsUsed());
+}
+
+TEST_F(IR_OverrideTest, Clone) {
+    auto* v = b.Override(ty.f32());
+    v->SetInitializer(b.Constant(4_f));
+    v->SetOverrideId(OverrideId{2});
+
+    auto* new_v = clone_ctx.Clone(v);
+
+    EXPECT_NE(v, new_v);
+    ASSERT_NE(nullptr, new_v->Result(0));
+    EXPECT_NE(v->Result(0), new_v->Result(0));
+    EXPECT_EQ(new_v->Result(0)->Type(), mod.Types().f32());
+
+    ASSERT_NE(nullptr, new_v->Initializer());
+    auto new_val = new_v->Initializer()->As<Constant>()->Value();
+    ASSERT_TRUE(new_val->Is<core::constant::Scalar<f32>>());
+    EXPECT_FLOAT_EQ(4_f, new_val->As<core::constant::Scalar<f32>>()->ValueAs<f32>());
+    EXPECT_EQ(OverrideId{2u}, new_v->OverrideId());
+}
+
+TEST_F(IR_OverrideTest, CloneWithName) {
+    auto* v = b.Override("v", b.Constant(1_f));
+    auto* new_v = clone_ctx.Clone(v);
+
+    EXPECT_EQ(std::string("v"), mod.NameOf(new_v).Name());
+}
+
+}  // namespace
+}  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 6cc6af1..5c3b6f5 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -64,6 +64,7 @@
 #include "src/tint/lang/core/ir/member_builtin_call.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/lang/core/ir/next_iteration.h"
+#include "src/tint/lang/core/ir/override.h"
 #include "src/tint/lang/core/ir/referenced_module_vars.h"
 #include "src/tint/lang/core/ir/return.h"
 #include "src/tint/lang/core/ir/store.h"
@@ -874,6 +875,10 @@
     /// @param blk the block
     void CheckRootBlock(const Block* blk);
 
+    /// Validates the given instruction is only used in the root block.
+    /// @param inst the instruction
+    void CheckOnlyUsedInRootBlock(const Instruction* inst);
+
     /// Validates the given function
     /// @param func the function to validate
     void CheckFunction(const Function* func);
@@ -974,6 +979,10 @@
     /// @param inst the instruction to validate
     void CheckInstruction(const Instruction* inst);
 
+    /// Validates the given override
+    /// @param o the override to validate
+    void CheckOverride(const Override* o);
+
     /// Validates the given var
     /// @param var the var to validate
     void CheckVar(const Var* var);
@@ -1221,6 +1230,7 @@
     Hashmap<const ir::Function*, Hashset<const ir::UserCall*, 4>, 4> user_func_calls_;
     Hashset<const ir::Discard*, 4> discards_;
     core::ir::ReferencedModuleVars<const Module> referenced_module_vars_;
+    Hashset<OverrideId, 8> seen_override_ids_;
 
     Hashset<ValidatedType, 16> validated_types_{};
 };
@@ -1731,6 +1741,13 @@
 
         tint::Switch(
             inst,  //
+            [&](const core::ir::Override* o) {
+                if (capabilities_.Contains(Capability::kAllowOverrides)) {
+                    CheckInstruction(o);
+                } else {
+                    AddError(inst) << "root block: invalid instruction: " << inst->TypeInfo().name;
+                }
+            },
             [&](const core::ir::Var* var) { CheckInstruction(var); },
             [&](const core::ir::Let* let) {
                 if (capabilities_.Contains(Capability::kAllowModuleScopeLets)) {
@@ -1747,11 +1764,31 @@
                 }
             },
             [&](Default) {
-                AddError(inst) << "root block: invalid instruction: " << inst->TypeInfo().name;
+                // Note, this validation is looser then it could be. There are only certain
+                // expressions and builtins which can be used in an override. We can tighten this up
+                // later to a constrained set of instructions and builtins if necessary.
+                if (capabilities_.Contains(Capability::kAllowOverrides)) {
+                    // If overrides are allowed we can have regular instructions in the root block,
+                    // with the caveat that those instructions can _only_ be used in the root block.
+                    CheckOnlyUsedInRootBlock(inst);
+                } else {
+                    AddError(inst) << "root block: invalid instruction: " << inst->TypeInfo().name;
+                }
             });
     }
 }
 
+void Validator::CheckOnlyUsedInRootBlock(const Instruction* inst) {
+    for (auto& usage : inst->Result(0)->UsagesSorted()) {
+        if (usage.instruction->Block() != mod_.root_block) {
+            AddError(inst) << "root block: instruction used outside of root block "
+                           << inst->TypeInfo().name;
+        }
+    }
+
+    CheckInstruction(inst);
+}
+
 void Validator::CheckFunction(const Function* func) {
     // Scope holds the parameters and block
     scope_stack_.Push();
@@ -2083,6 +2120,7 @@
         [&](const Swizzle* s) { CheckSwizzle(s); },                        //
         [&](const Terminator* b) { CheckTerminator(b); },                  //
         [&](const Unary* u) { CheckUnary(u); },                            //
+        [&](const Override* o) { CheckOverride(o); },                      //
         [&](const Var* var) { CheckVar(var); },                            //
         [&](const Default) { AddError(inst) << "missing validation"; });
 
@@ -2091,6 +2129,36 @@
     }
 }
 
+void Validator::CheckOverride(const Override* o) {
+    // Intentionally not checking operands, since Override may have a null operand
+    if (!CheckResults(o, Override::kNumResults)) {
+        return;
+    }
+
+    if (!seen_override_ids_.Add(o->OverrideId())) {
+        AddError(o) << "duplicate override id encountered: " << o->OverrideId().value;
+        return;
+    }
+
+    if (!o->Result(0)->Type()->IsScalar()) {
+        AddError(o) << "override type " << style::Type(o->Result(0)->Type()->FriendlyName())
+                    << " is not a scalar";
+        return;
+    }
+
+    if (o->Initializer()) {
+        if (!CheckOperand(o, ir::Var::kInitializerOperandOffset)) {
+            return;
+        }
+        if (o->Initializer()->Type() != o->Result(0)->Type()) {
+            AddError(o) << "override type " << style::Type(o->Result(0)->Type()->FriendlyName())
+                        << " does not match initializer type "
+                        << style::Type(o->Initializer()->Type()->FriendlyName());
+            return;
+        }
+    }
+}
+
 void Validator::CheckVar(const Var* var) {
     // Intentionally not checking operands, since Var may have a null operand
     if (!CheckResults(var, Var::kNumResults)) {
diff --git a/src/tint/lang/core/ir/validator.h b/src/tint/lang/core/ir/validator.h
index 4681e03..cf4809a 100644
--- a/src/tint/lang/core/ir/validator.h
+++ b/src/tint/lang/core/ir/validator.h
@@ -44,18 +44,20 @@
 enum class Capability : uint8_t {
     /// Allows 8-bit integer types.
     kAllow8BitIntegers,
-    /// Allows access instructions to create pointers to vector elements.
-    kAllowVectorElementPointer,
-    /// Allows ref types
-    kAllowRefTypes,
-    /// Allows module scoped lets
-    kAllowModuleScopeLets,
-    /// Allows pointers inside structures.
-    kAllowPointersInStructures,
-    /// Allows handle vars to not have binding points
-    kAllowHandleVarsWithoutBindings,
     /// Allows ClipDistances on f32 parameters
     kAllowClipDistancesOnF32,
+    /// Allows handle vars to not have binding points
+    kAllowHandleVarsWithoutBindings,
+    /// Allows module scoped lets
+    kAllowModuleScopeLets,
+    /// Allow overrides
+    kAllowOverrides,
+    /// Allows pointers inside structures.
+    kAllowPointersInStructures,
+    /// Allows ref types
+    kAllowRefTypes,
+    /// Allows access instructions to create pointers to vector elements.
+    kAllowVectorElementPointer,
 };
 
 /// Capabilities is a set of Capability
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index e944db2..3060f7c 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -9660,5 +9660,189 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, OverrideWithoutCapability) {
+    b.Append(mod.root_block, [&] { b.Override("a", 1_u); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:2:12 error: override: root block: invalid instruction: tint::core::ir::Override
+  %a:u32 = override, 1u @id(0)
+           ^^^^^^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %a:u32 = override, 1u @id(0)
+}
+
+)");
+}
+
+TEST_F(IR_ValidatorTest, InstructionInRootBlockWithoutOverrideCap) {
+    b.Append(mod.root_block, [&] { b.Add(ty.u32(), 3_u, 2_u); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:2:3 error: binary: root block: invalid instruction: tint::core::ir::CoreBinary
+  %1:u32 = add 3u, 2u
+  ^^^^^^^^^^^^^^^^^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %1:u32 = add 3u, 2u
+}
+
+)");
+}
+
+TEST_F(IR_ValidatorTest, OverrideWithCapability) {
+    b.Append(mod.root_block, [&] { b.Override(ty.u32()); });
+
+    auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, OverrideWithValue) {
+    b.Append(mod.root_block, [&] {
+        auto* z = b.Override(ty.u32());
+        z->SetOverrideId(OverrideId{2});
+        auto* init = b.Add(ty.u32(), z, 2_u);
+
+        b.Override("a", init);
+    });
+
+    auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, OverrideWithInvalidType) {
+    b.Append(mod.root_block, [&] { b.Override(ty.vec3<u32>()); });
+
+    auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:2:18 error: override: override type 'vec3<u32>' is not a scalar
+  %1:vec3<u32> = override @id(0)
+                 ^^^^^^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %1:vec3<u32> = override @id(0)
+}
+
+)");
+}
+
+TEST_F(IR_ValidatorTest, OverrideWithMismatchedInitializerType) {
+    b.Append(mod.root_block, [&] {
+        auto* init = b.Constant(1_i);
+        auto* o = b.Override(ty.u32());
+        o->SetInitializer(init);
+    });
+
+    auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:2:12 error: override: override type 'u32' does not match initializer type 'i32'
+  %1:u32 = override, 1i @id(0)
+           ^^^^^^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %1:u32 = override, 1i @id(0)
+}
+
+)");
+}
+
+TEST_F(IR_ValidatorTest, OverrideDuplicateId) {
+    b.Append(mod.root_block, [&] {
+        auto* o = b.Override(ty.u32());
+        o->SetOverrideId(OverrideId{2});
+
+        auto* o2 = b.Override(ty.i32());
+        o2->SetOverrideId(OverrideId{2});
+    });
+
+    auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:12 error: override: duplicate override id encountered: 2
+  %2:i32 = override @id(2)
+           ^^^^^^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %1:u32 = override @id(2)
+  %2:i32 = override @id(2)
+}
+
+)");
+}
+
+TEST_F(IR_ValidatorTest, InstructionInRootBlockOnlyUsedInRootBlock) {
+    core::ir::Value* init = nullptr;
+    b.Append(mod.root_block, [&] {
+        auto* z = b.Override(ty.u32());
+        z->SetOverrideId(OverrideId{2});
+        init = b.Add(ty.u32(), z, 2_u)->Result(0);
+        b.Override("a", init);
+    });
+
+    auto* f = b.Function("my_func", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Add(ty.u32(), init, 2_u);
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(
+        res.Failure().reason.Str(),
+        R"(:3:3 error: binary: root block: instruction used outside of root block tint::core::ir::CoreBinary
+  %2:u32 = add %1, 2u
+  ^^^^^^^^^^^^^^^^^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %1:u32 = override @id(2)
+  %2:u32 = add %1, 2u
+  %a:u32 = override, %2 @id(0)
+}
+
+%my_func = func():void {
+  $B2: {
+    %5:u32 = add %2, 2u
+    ret
+  }
+}
+)");
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/type/type.cc b/src/tint/lang/core/type/type.cc
index 29069bd..cf77682 100644
--- a/src/tint/lang/core/type/type.cc
+++ b/src/tint/lang/core/type/type.cc
@@ -88,6 +88,10 @@
     return 0;
 }
 
+bool Type::IsScalar() const {
+    return IsFloatScalar() || IsIntegerScalar() || IsAnyOf<AbstractInt, Bool>();
+}
+
 bool Type::IsFloatScalar() const {
     return IsAnyOf<F16, F32, AbstractFloat>();
 }
diff --git a/src/tint/lang/core/type/type.h b/src/tint/lang/core/type/type.h
index d263428..d4b7647 100644
--- a/src/tint/lang/core/type/type.h
+++ b/src/tint/lang/core/type/type.h
@@ -129,6 +129,8 @@
     /// @see https://www.w3.org/TR/WGSL/#fixed-footprint-types
     inline bool HasFixedFootprint() const { return flags_.Contains(Flag::kFixedFootprint); }
 
+    /// @returns true if the type is a scalar
+    bool IsScalar() const;
     /// @returns true if this type is a float scalar
     bool IsFloatScalar() const;
     /// @returns true if this type is a float matrix
diff --git a/src/tint/lang/wgsl/reader/lower/lower.cc b/src/tint/lang/wgsl/reader/lower/lower.cc
index 02ba26e..2fe327e 100644
--- a/src/tint/lang/wgsl/reader/lower/lower.cc
+++ b/src/tint/lang/wgsl/reader/lower/lower.cc
@@ -35,6 +35,7 @@
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/wgsl/builtin_fn.h"
 #include "src/tint/lang/wgsl/ir/builtin_call.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/ice/ice.h"
 
 namespace tint::wgsl::reader {
@@ -204,7 +205,9 @@
 }  // namespace
 
 Result<SuccessType> Lower(core::ir::Module& mod) {
-    if (auto res = core::ir::ValidateAndDumpIfNeeded(mod, "wgsl.Lower"); res != Success) {
+    auto res = core::ir::ValidateAndDumpIfNeeded(
+        mod, "wgsl.Lower", core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    if (res != Success) {
         return res.Failure();
     }
 
diff --git a/src/tint/lang/wgsl/reader/lower/lower.h b/src/tint/lang/wgsl/reader/lower/lower.h
index 91a647e..b11a139 100644
--- a/src/tint/lang/wgsl/reader/lower/lower.h
+++ b/src/tint/lang/wgsl/reader/lower/lower.h
@@ -29,7 +29,6 @@
 #define SRC_TINT_LANG_WGSL_READER_LOWER_LOWER_H_
 
 #include "src/tint/lang/core/ir/module.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/result/result.h"
 
 namespace tint::wgsl::reader {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h b/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h
index 7bdf81a..2ce447c 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h
+++ b/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h
@@ -66,7 +66,9 @@
             return lower.Failure();
         }
 
-        if (auto validate = core::ir::Validate(result.Get()); validate != Success) {
+        auto validate = core::ir::Validate(
+            result.Get(), core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+        if (validate != Success) {
             return validate.Failure();
         }
         return result;
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index 0baa2a4..673889e 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -1218,19 +1218,34 @@
                 // Store the results of the initialization
                 scopes_.Set(l->name->symbol, let->Result(0));
             },
-            [&](const ast::Override*) {
-                AddError(var->source) << "found an `Override` variable. The SubstituteOverrides "
-                                         "transform must be run before converting to IR";
+            [&](const ast::Override* o) {
+                auto* o_sem = program_.Sem().Get(o);
+                auto* ty = sem->Type()->Clone(clone_ctx_.type_ctx);
+
+                auto* override = builder_.Override(ty);
+                if (o->initializer) {
+                    auto init = EmitValueExpression(o->initializer);
+                    if (!init) {
+                        return;
+                    }
+                    override->SetInitializer(init);
+                }
+                current_block_->Append(override);
+
+                TINT_ASSERT(o_sem->Attributes().override_id.has_value());
+                override->SetOverrideId(o_sem->Attributes().override_id.value());
+
+                // Store the declaration so we can get the instruction to store too
+                scopes_.Set(o->name->symbol, override->Result(0));
+
+                // Record the original name of the var
+                builder_.ir.SetName(override, o->name->symbol.Name());
             },
             [&](const ast::Const*) {
                 // Skip. This should be handled by const-eval already, so the const will be a
                 // `core::constant::` value at the usage sites. Can just ignore the `const` variable
                 // as it should never be used.
-                //
-                // TODO(dsinclair): Probably want to store the const variable somewhere and then
-                // in identifier expression log an error if we ever see a const identifier. Add
-                // this when identifiers and variables are supported.
-            },  //
+            },
             TINT_ICE_ON_NO_MATCH);
     }
 
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc
index 1b12473..57457f5 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc
@@ -32,6 +32,7 @@
 #include "src/tint/lang/core/ir/if.h"
 #include "src/tint/lang/core/ir/loop.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
+#include "src/tint/lang/core/ir/override.h"
 #include "src/tint/lang/core/ir/switch.h"
 #include "src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h"
 
@@ -1160,9 +1161,6 @@
 )");
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// Bugs
-////////////////////////////////////////////////////////////////////////////////
 TEST_F(IR_FromProgramTest, BugChromium324466107) {
     Func("f", Empty, ty.void_(),
          Vector{
@@ -1182,5 +1180,87 @@
 )");
 }
 
+TEST_F(IR_FromProgramTest, OverrideNoInitializer) {
+    Override("a", ty.i32());
+
+    auto res = Build();
+    ASSERT_EQ(res, Success);
+
+    auto m = res.Move();
+    auto* overide = FindSingleInstruction<core::ir::Override>(m);
+
+    ASSERT_NE(overide, nullptr);
+    ASSERT_EQ(overide->Initializer(), nullptr);
+
+    EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: {  # root
+  %a:i32 = override @id(0)
+}
+
+)");
+}
+
+TEST_F(IR_FromProgramTest, OverrideWithConstantInitializer) {
+    Override("a", Expr(1_f));
+
+    auto res = Build();
+    ASSERT_EQ(res, Success);
+
+    auto m = res.Move();
+    auto* override = FindSingleInstruction<core::ir::Override>(m);
+
+    ASSERT_NE(override, nullptr);
+    ASSERT_NE(override->Initializer(), nullptr);
+
+    auto* init = override->Initializer()->As<core::ir::Constant>();
+    ASSERT_NE(init, nullptr);
+    EXPECT_FLOAT_EQ(1.0f, init->Value()->ValueAs<float>());
+
+    EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: {  # root
+  %a:f32 = override, 1.0f @id(0)
+}
+
+)");
+}
+
+TEST_F(IR_FromProgramTest, OverrideWithAddInitializer) {
+    Override("a", Add(1_u, 2_u));
+
+    auto res = Build();
+    ASSERT_EQ(res, Success);
+
+    auto m = res.Move();
+    auto* override = FindSingleInstruction<core::ir::Override>(m);
+
+    ASSERT_NE(override, nullptr);
+    ASSERT_NE(override->Initializer(), nullptr);
+
+    auto* init = override->Initializer()->As<core::ir::Constant>();
+    ASSERT_NE(init, nullptr);
+    EXPECT_EQ(3u, init->Value()->ValueAs<uint32_t>());
+
+    EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: {  # root
+  %a:u32 = override, 3u @id(0)
+}
+
+)");
+}
+
+TEST_F(IR_FromProgramTest, OverrideWithOverrideAddInitializer) {
+    auto* z = Override("z", ty.u32());
+    Override("a", Add(z, 2_u));
+
+    auto res = Build();
+    ASSERT_EQ(res, Success);
+
+    auto m = res.Move();
+    EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: {  # root
+  %z:u32 = override @id(0)
+  %2:u32 = add %z, 2u
+  %a:u32 = override, %2 @id(1)
+}
+
+)");
+}
+
 }  // namespace
 }  // namespace tint::wgsl::reader
diff --git a/src/tint/lang/wgsl/reader/reader.cc b/src/tint/lang/wgsl/reader/reader.cc
index ac8d967..4cbaaf6 100644
--- a/src/tint/lang/wgsl/reader/reader.cc
+++ b/src/tint/lang/wgsl/reader/reader.cc
@@ -51,29 +51,20 @@
 
 Result<core::ir::Module> WgslToIR(const Source::File* file, const Options& options) {
     Program program = Parse(file, options);
-    auto module = ProgramToIR(program);
-    if (module != Success) {
-        return module.Failure();
-    }
-    // WGSL-dialect -> core-dialect
-    if (auto res = Lower(module.Get()); res != Success) {
-        return res.Failure();
-    }
-    return module;
+    return ProgramToLoweredIR(program);
 }
 
 tint::Result<core::ir::Module> ProgramToLoweredIR(const Program& program) {
-    auto ir = tint::wgsl::reader::ProgramToIR(program);
+    auto ir = ProgramToIR(program);
     if (ir != Success) {
         return ir.Failure();
     }
 
     // Lower from WGSL-dialect to core-dialect
-    auto res = tint::wgsl::reader::Lower(ir.Get());
+    auto res = Lower(ir.Get());
     if (res != Success) {
         return res.Failure();
     }
-
     return ir;
 }