Import Tint changes from Dawn

Changes:
  - 7d34de88f1752c1119f33a444b5383c8510e78e5 tint/test-runner: Split expectations for FXC and DXC by Ben Clayton <bclayton@google.com>
  - a089376daf17ed16c2b527d26aa41338c1ad60f8 Context aware address space parsing. by dan sinclair <dsinclair@chromium.org>
  - 87254ff58e954cf6daa9f8f851d4fa5604cadf60 Update error messages to say initializer by dan sinclair <dsinclair@chromium.org>
  - 05288f6cff05391449877104b338a15ae0bbe81f Rename paren_rhs_stmt to paren_expression. by dan sinclair <dsinclair@chromium.org>
  - 256f1116b836aa88d8704e7f4ee2f6b32ed44dff Add transform to substitute overrides with const expressi... by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: 7d34de88f1752c1119f33a444b5383c8510e78e5
Change-Id: I8b57174088889a4de2ceea5033b3b74e1a91fa1a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/96984
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/include/tint/tint.h b/include/tint/tint.h
index 42ceda4..a6ab6ca 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -31,6 +31,7 @@
 #include "src/tint/transform/renamer.h"
 #include "src/tint/transform/robustness.h"
 #include "src/tint/transform/single_entry_point.h"
+#include "src/tint/transform/substitute_override.h"
 #include "src/tint/transform/vertex_pulling.h"
 #include "src/tint/writer/flatten_bindings.h"
 #include "src/tint/writer/writer.h"
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 03f27e5..3290694 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -287,9 +287,9 @@
     "ast/module.h",
     "ast/multisampled_texture.cc",
     "ast/multisampled_texture.h",
-    "ast/node_id.h",
     "ast/node.cc",
     "ast/node.h",
+    "ast/node_id.h",
     "ast/override.cc",
     "ast/override.h",
     "ast/parameter.cc",
@@ -529,6 +529,8 @@
     "transform/single_entry_point.h",
     "transform/spirv_atomic.cc",
     "transform/spirv_atomic.h",
+    "transform/substitute_override.cc",
+    "transform/substitute_override.h",
     "transform/transform.cc",
     "transform/transform.h",
     "transform/unshadow.cc",
@@ -1034,6 +1036,7 @@
       "ast/module_clone_test.cc",
       "ast/module_test.cc",
       "ast/multisampled_texture_test.cc",
+      "ast/override_test.cc",
       "ast/phony_expression_test.cc",
       "ast/pointer_test.cc",
       "ast/return_statement_test.cc",
@@ -1197,6 +1200,7 @@
       "transform/simplify_pointers_test.cc",
       "transform/single_entry_point_test.cc",
       "transform/spirv_atomic_test.cc",
+      "transform/substitute_override_test.cc",
       "transform/test_helper.h",
       "transform/transform_test.cc",
       "transform/unshadow_test.cc",
@@ -1365,7 +1369,7 @@
       "reader/wgsl/parser_impl_loop_stmt_test.cc",
       "reader/wgsl/parser_impl_multiplicative_expression_test.cc",
       "reader/wgsl/parser_impl_param_list_test.cc",
-      "reader/wgsl/parser_impl_paren_rhs_stmt_test.cc",
+      "reader/wgsl/parser_impl_paren_expression_test.cc",
       "reader/wgsl/parser_impl_pipeline_stage_test.cc",
       "reader/wgsl/parser_impl_primary_expression_test.cc",
       "reader/wgsl/parser_impl_relational_expression_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 4eb97a2..8f8f19c 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -441,6 +441,8 @@
   transform/single_entry_point.h
   transform/spirv_atomic.cc
   transform/spirv_atomic.h
+  transform/substitute_override.cc
+  transform/substitute_override.h
   transform/transform.cc
   transform/transform.h
   transform/unshadow.cc
@@ -730,6 +732,7 @@
     ast/module_clone_test.cc
     ast/module_test.cc
     ast/multisampled_texture_test.cc
+    ast/override_test.cc
     ast/phony_expression_test.cc
     ast/pointer_test.cc
     ast/return_statement_test.cc
@@ -963,7 +966,7 @@
       reader/wgsl/parser_impl_loop_stmt_test.cc
       reader/wgsl/parser_impl_multiplicative_expression_test.cc
       reader/wgsl/parser_impl_param_list_test.cc
-      reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
+      reader/wgsl/parser_impl_paren_expression_test.cc
       reader/wgsl/parser_impl_pipeline_stage_test.cc
       reader/wgsl/parser_impl_primary_expression_test.cc
       reader/wgsl/parser_impl_relational_expression_test.cc
@@ -1109,6 +1112,7 @@
       transform/simplify_pointers_test.cc
       transform/single_entry_point_test.cc
       transform/spirv_atomic_test.cc
+      transform/substitute_override_test.cc
       transform/test_helper.h
       transform/unshadow_test.cc
       transform/unwind_discard_functions_test.cc
diff --git a/src/tint/ast/override.cc b/src/tint/ast/override.cc
index b7ee064..2876920 100644
--- a/src/tint/ast/override.cc
+++ b/src/tint/ast/override.cc
@@ -46,4 +46,11 @@
     return ctx->dst->create<Override>(src, sym, ty, ctor, attrs);
 }
 
+std::string Override::Identifier(const SymbolTable& symbols) const {
+    if (auto* id = ast::GetAttribute<ast::IdAttribute>(attributes)) {
+        return std::to_string(id->value);
+    }
+    return symbols.NameFor(symbol);
+}
+
 }  // namespace tint::ast
diff --git a/src/tint/ast/override.h b/src/tint/ast/override.h
index e97f14c..00c36fc 100644
--- a/src/tint/ast/override.h
+++ b/src/tint/ast/override.h
@@ -15,6 +15,8 @@
 #ifndef SRC_TINT_AST_OVERRIDE_H_
 #define SRC_TINT_AST_OVERRIDE_H_
 
+#include <string>
+
 #include "src/tint/ast/variable.h"
 
 namespace tint::ast {
@@ -60,6 +62,12 @@
     /// @param ctx the clone context
     /// @return the newly cloned node
     const Override* Clone(CloneContext* ctx) const override;
+
+    /// @param symbols the symbol table to retrieve the name from
+    /// @returns the identifier string for the override. If the override has
+    /// an ID attribute, the string is the id-stringified. Otherwise, the ID
+    /// is the symbol.
+    std::string Identifier(const SymbolTable& symbols) const;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/override_test.cc b/src/tint/ast/override_test.cc
new file mode 100644
index 0000000..b4179e5
--- /dev/null
+++ b/src/tint/ast/override_test.cc
@@ -0,0 +1,35 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ast/override.h"
+
+#include "src/tint/ast/test_helper.h"
+
+namespace tint::ast {
+namespace {
+
+using OverrideTest = TestHelper;
+
+TEST_F(OverrideTest, Identifier_NoId) {
+    auto* o = Override("o", nullptr, Expr(f32(1.0)));
+    EXPECT_EQ(std::string("o"), o->Identifier(Symbols()));
+}
+
+TEST_F(OverrideTest, Identifier_WithId) {
+    auto* o = Override("o", nullptr, Expr(f32(1.0)), {Id(4u)});
+    EXPECT_EQ(std::string("4"), o->Identifier(Symbols()));
+}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index f6029ac..b948e69 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -20,6 +20,7 @@
 #include <optional>
 #include <sstream>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #if TINT_BUILD_GLSL_WRITER
@@ -66,6 +67,7 @@
 
 struct Options {
     bool show_help = false;
+    bool verbose = false;
 
     std::string input_filename;
     std::string output_file = "-";  // Default to stdout
@@ -83,10 +85,10 @@
 
     std::vector<std::string> transforms;
 
-    bool use_fxc = false;
+    std::string fxc_path;
     std::string dxc_path;
     std::string xcrun_path;
-    std::vector<std::string> overrides;
+    std::unordered_map<std::string, double> overrides;
     std::optional<tint::sem::BindingPoint> hlsl_root_constant_binding_point;
 };
 
@@ -119,14 +121,15 @@
                                used for num_workgroups in HLSL. If not specified, then
                                default to binding 0 of the largest used group plus 1,
                                or group 0 if no resource bound.
-  --validate                -- Validates the generated shader
-  --fxc                     -- Ask to validate HLSL output using FXC instead of DXC.
-                               When specified, automatically enables --validate
+  --validate                -- Validates the generated shader with all available validators
+  --fxc                     -- Path to FXC dll, used to validate HLSL output.
+                               When specified, automatically enables HLSL validation with FXC
   --dxc                     -- Path to DXC executable, used to validate HLSL output.
-                               When specified, automatically enables --validate
+                               When specified, automatically enables HLSL validation with DXC
   --xcrun                   -- Path to xcrun executable, used to validate MSL output.
-                               When specified, automatically enables --validate
-  --overrides               -- Pipeline overrides as NAME=VALUE, comma-separated.)";
+                               When specified, automatically enables MSL validation
+  --overrides               -- Override values as IDENTIFIER=VALUE, comma-separated.
+)";
 
 Format parse_format(const std::string& fmt) {
     (void)fmt;
@@ -215,18 +218,26 @@
     return Format::kNone;
 }
 
-std::vector<std::string> split_on_comma(std::string list) {
+std::vector<std::string> split_on_char(std::string list, char c) {
     std::vector<std::string> res;
 
     std::stringstream str(list);
     while (str.good()) {
         std::string substr;
-        getline(str, substr, ',');
+        getline(str, substr, c);
         res.push_back(substr);
     }
     return res;
 }
 
+std::vector<std::string> split_on_comma(std::string list) {
+    return split_on_char(list, ',');
+}
+
+std::vector<std::string> split_on_equal(std::string list) {
+    return split_on_char(list, '=');
+}
+
 std::optional<uint64_t> parse_unsigned_number(std::string number) {
     for (char c : number) {
         if (!std::isdigit(c)) {
@@ -387,6 +398,8 @@
 
         } else if (arg == "-h" || arg == "--help") {
             opts->show_help = true;
+        } else if (arg == "-v" || arg == "--verbose") {
+            opts->verbose = true;
         } else if (arg == "--transform") {
             ++i;
             if (i >= args.size()) {
@@ -405,8 +418,12 @@
         } else if (arg == "--validate") {
             opts->validate = true;
         } else if (arg == "--fxc") {
-            opts->validate = true;
-            opts->use_fxc = true;
+            ++i;
+            if (i >= args.size()) {
+                std::cerr << "Missing value for " << arg << std::endl;
+                return false;
+            }
+            opts->fxc_path = args[i];
         } else if (arg == "--dxc") {
             ++i;
             if (i >= args.size()) {
@@ -414,7 +431,6 @@
                 return false;
             }
             opts->dxc_path = args[i];
-            opts->validate = true;
         } else if (arg == "--xcrun") {
             ++i;
             if (i >= args.size()) {
@@ -429,7 +445,10 @@
                 std::cerr << "Missing value for " << arg << std::endl;
                 return false;
             }
-            opts->overrides = split_on_comma(args[i]);
+            for (const auto& o : split_on_comma(args[i])) {
+                auto parts = split_on_equal(o);
+                opts->overrides.insert({parts[0], std::stod(parts[1])});
+            }
         } else if (arg == "--hlsl-root-constant-binding-point") {
             ++i;
             if (i >= args.size()) {
@@ -788,30 +807,71 @@
         return false;
     }
 
-    if (options.validate) {
-        tint::val::Result res;
-        if (options.use_fxc) {
-#ifdef _WIN32
-            res = tint::val::HlslUsingFXC(result.hlsl, result.entry_points, options.overrides);
-#else
-            res.failed = true;
-            res.output = "FXC can only be used on Windows. Sorry :X";
-#endif  // _WIN32
-        } else {
+    // If --fxc or --dxc was passed, then we must explicitly find and validate with that respective
+    // compiler.
+    const bool must_validate_dxc = !options.dxc_path.empty();
+    const bool must_validate_fxc = !options.fxc_path.empty();
+    if (options.validate || must_validate_dxc || must_validate_fxc) {
+        tint::val::Result dxc_res;
+        bool dxc_found = false;
+        if (options.validate || must_validate_dxc) {
             auto dxc =
                 tint::utils::Command::LookPath(options.dxc_path.empty() ? "dxc" : options.dxc_path);
             if (dxc.Found()) {
-                res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, result.entry_points,
-                                              options.overrides);
-            } else {
-                res.failed = true;
-                res.output = "DXC executable not found. Cannot validate";
+                dxc_found = true;
+                dxc_res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, result.entry_points);
+            } else if (must_validate_dxc) {
+                // DXC was explicitly requested. Error if it could not be found.
+                dxc_res.failed = true;
+                dxc_res.output =
+                    "DXC executable '" + options.dxc_path + "' not found. Cannot validate";
             }
         }
-        if (res.failed) {
-            std::cerr << res.output << std::endl;
+
+        tint::val::Result fxc_res;
+        bool fxc_found = false;
+        if (options.validate || must_validate_fxc) {
+            auto fxc = tint::utils::Command::LookPath(
+                options.fxc_path.empty() ? tint::val::kFxcDLLName : options.fxc_path);
+
+#ifdef _WIN32
+            if (fxc.Found()) {
+                fxc_found = true;
+                fxc_res = tint::val::HlslUsingFXC(fxc.Path(), result.hlsl, result.entry_points);
+            } else if (must_validate_fxc) {
+                // FXC was explicitly requested. Error if it could not be found.
+                fxc_res.failed = true;
+                fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate";
+            }
+#else
+            if (must_validate_dxc) {
+                fxc_res.failed = true;
+                fxc_res.output = "FXC can only be used on Windows.";
+            }
+#endif  // _WIN32
+        }
+
+        if (fxc_res.failed) {
+            std::cerr << "FXC validation failure:" << std::endl << fxc_res.output << std::endl;
+        }
+        if (dxc_res.failed) {
+            std::cerr << "DXC validation failure:" << std::endl << dxc_res.output << std::endl;
+        }
+        if (fxc_res.failed || dxc_res.failed) {
             return false;
         }
+        if (!fxc_found && !dxc_found) {
+            std::cerr << "Couldn't find FXC or DXC. Cannot validate" << std::endl;
+            return false;
+        }
+        if (options.verbose) {
+            if (fxc_found && !fxc_res.failed) {
+                std::cout << "Passed FXC validation" << std::endl;
+            }
+            if (dxc_found && !dxc_res.failed) {
+                std::cout << "Passed DXC validation" << std::endl;
+            }
+        }
     }
 
     return true;
@@ -947,6 +1007,14 @@
                        tint::transform::DataMap&) { m.Add<tint::transform::Renamer>(); }},
         {"robustness", [](tint::transform::Manager& m,
                           tint::transform::DataMap&) { m.Add<tint::transform::Robustness>(); }},
+        {"substitute_override",
+         [&](tint::transform::Manager& m, tint::transform::DataMap& i) {
+             tint::transform::SubstituteOverride::Config cfg;
+             cfg.map = options.overrides;
+
+             i.Add<tint::transform::SubstituteOverride::Config>(cfg);
+             m.Add<tint::transform::SubstituteOverride>();
+         }},
     };
     auto transform_names = [&] {
         std::stringstream names;
@@ -1078,6 +1146,17 @@
 
     tint::transform::Manager transform_manager;
     tint::transform::DataMap transform_inputs;
+
+    // If overrides are provided, add the SubstituteOverride transform.
+    if (!options.overrides.empty()) {
+        for (auto& t : transforms) {
+            if (t.name == std::string("substitute_override")) {
+                t.make(transform_manager, transform_inputs);
+                break;
+            }
+        }
+    }
+
     for (const auto& name : options.transforms) {
         // TODO(dsinclair): The vertex pulling transform requires setup code to
         // be run that needs user input. Should we find a way to support that here
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index ade723c..22eedda 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -1127,9 +1127,6 @@
     if (str == "for") {
         return {Token::Type::kFor, source, "for"};
     }
-    if (str == "function") {
-        return {Token::Type::kFunction, source, "function"};
-    }
     if (str == "i32") {
         return {Token::Type::kI32, source, "i32"};
     }
@@ -1175,9 +1172,6 @@
     if (str == "override") {
         return {Token::Type::kOverride, source, "override"};
     }
-    if (str == "private") {
-        return {Token::Type::kPrivate, source, "private"};
-    }
     if (str == "ptr") {
         return {Token::Type::kPtr, source, "ptr"};
     }
@@ -1190,9 +1184,6 @@
     if (str == "sampler_comparison") {
         return {Token::Type::kComparisonSampler, source, "sampler_comparison"};
     }
-    if (str == "storage_buffer" || str == "storage") {
-        return {Token::Type::kStorage, source, "storage"};
-    }
     if (str == "struct") {
         return {Token::Type::kStruct, source, "struct"};
     }
@@ -1259,9 +1250,6 @@
     if (str == "u32") {
         return {Token::Type::kU32, source, "u32"};
     }
-    if (str == "uniform") {
-        return {Token::Type::kUniform, source, "uniform"};
-    }
     if (str == "var") {
         return {Token::Type::kVar, source, "var"};
     }
@@ -1277,9 +1265,6 @@
     if (str == "while") {
         return {Token::Type::kWhile, source, "while"};
     }
-    if (str == "workgroup") {
-        return {Token::Type::kWorkgroup, source, "workgroup"};
-    }
     return {};
 }
 
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
index bf32a52..799f9c2 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -941,7 +941,6 @@
                     TokenData{"false", Token::Type::kFalse},
                     TokenData{"fn", Token::Type::kFn},
                     TokenData{"for", Token::Type::kFor},
-                    TokenData{"function", Token::Type::kFunction},
                     TokenData{"i32", Token::Type::kI32},
                     TokenData{"if", Token::Type::kIf},
                     TokenData{"import", Token::Type::kImport},
@@ -957,13 +956,10 @@
                     TokenData{"mat4x3", Token::Type::kMat4x3},
                     TokenData{"mat4x4", Token::Type::kMat4x4},
                     TokenData{"override", Token::Type::kOverride},
-                    TokenData{"private", Token::Type::kPrivate},
                     TokenData{"ptr", Token::Type::kPtr},
                     TokenData{"return", Token::Type::kReturn},
                     TokenData{"sampler", Token::Type::kSampler},
                     TokenData{"sampler_comparison", Token::Type::kComparisonSampler},
-                    TokenData{"storage", Token::Type::kStorage},
-                    TokenData{"storage_buffer", Token::Type::kStorage},
                     TokenData{"struct", Token::Type::kStruct},
                     TokenData{"switch", Token::Type::kSwitch},
                     TokenData{"texture_1d", Token::Type::kTextureSampled1d},
@@ -986,13 +982,11 @@
                     TokenData{"true", Token::Type::kTrue},
                     TokenData{"type", Token::Type::kType},
                     TokenData{"u32", Token::Type::kU32},
-                    TokenData{"uniform", Token::Type::kUniform},
                     TokenData{"var", Token::Type::kVar},
                     TokenData{"vec2", Token::Type::kVec2},
                     TokenData{"vec3", Token::Type::kVec3},
                     TokenData{"vec4", Token::Type::kVec4},
-                    TokenData{"while", Token::Type::kWhile},
-                    TokenData{"workgroup", Token::Type::kWorkgroup}));
+                    TokenData{"while", Token::Type::kWhile}));
 
 }  // namespace
 }  // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index a490a5c..6ca78d6 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -562,7 +562,7 @@
                             decl->type,                               // type
                             decl->storage_class,                      // storage class
                             decl->access,                             // access control
-                            initalizer,                               // constructor
+                            initalizer,                               // initializer
                             std::move(attrs));                        // attributes
 }
 
@@ -619,20 +619,20 @@
         return create<ast::Const>(decl->source,                             // source
                                   builder_.Symbols().Register(decl->name),  // symbol
                                   decl->type,                               // type
-                                  initializer,                              // constructor
+                                  initializer,                              // initializer
                                   std::move(attrs));                        // attributes
     }
     if (is_overridable) {
         return create<ast::Override>(decl->source,                             // source
                                      builder_.Symbols().Register(decl->name),  // symbol
                                      decl->type,                               // type
-                                     initializer,                              // constructor
+                                     initializer,                              // initializer
                                      std::move(attrs));                        // attributes
     }
     return create<ast::Const>(decl->source,                             // source
                               builder_.Symbols().Register(decl->name),  // symbol
                               decl->type,                               // type
-                              initializer,                              // constructor
+                              initializer,                              // initializer
                               std::move(attrs));                        // attributes
 }
 
@@ -1271,34 +1271,31 @@
     return builder_.ty.mat(make_source_range_from(t.source()), subtype, columns, rows);
 }
 
-// storage_class
-//  : INPUT
-//  | OUTPUT
-//  | UNIFORM
-//  | WORKGROUP
-//  | STORAGE
-//  | PRIVATE
-//  | FUNCTION
 Expect<ast::StorageClass> ParserImpl::expect_storage_class(std::string_view use) {
     auto source = peek().source();
+    auto ident = expect_ident("storage class");
+    if (ident.errored) {
+        return Failure::kErrored;
+    }
 
-    if (match(Token::Type::kUniform)) {
+    auto name = ident.value;
+    if (name == "uniform") {
         return {ast::StorageClass::kUniform, source};
     }
 
-    if (match(Token::Type::kWorkgroup)) {
+    if (name == "workgroup") {
         return {ast::StorageClass::kWorkgroup, source};
     }
 
-    if (match(Token::Type::kStorage)) {
+    if (name == "storage" || name == "storage_buffer") {
         return {ast::StorageClass::kStorage, source};
     }
 
-    if (match(Token::Type::kPrivate)) {
+    if (name == "private") {
         return {ast::StorageClass::kPrivate, source};
     }
 
-    if (match(Token::Type::kFunction)) {
+    if (name == "function") {
         return {ast::StorageClass::kFunction, source};
     }
 
@@ -1567,9 +1564,9 @@
     });
 }
 
-// paren_rhs_stmt
+// paren_expression
 //   : PAREN_LEFT logical_or_expression PAREN_RIGHT
-Expect<const ast::Expression*> ParserImpl::expect_paren_rhs_stmt() {
+Expect<const ast::Expression*> ParserImpl::expect_paren_expression() {
     return expect_paren_block("", [&]() -> Expect<const ast::Expression*> {
         auto expr = logical_or_expression();
         if (expr.errored) {
@@ -1803,18 +1800,18 @@
             return Failure::kErrored;
         }
 
-        auto constructor = logical_or_expression();
-        if (constructor.errored) {
+        auto initializer = logical_or_expression();
+        if (initializer.errored) {
             return Failure::kErrored;
         }
-        if (!constructor.matched) {
-            return add_error(peek(), "missing constructor for 'const' declaration");
+        if (!initializer.matched) {
+            return add_error(peek(), "missing initializer for 'const' declaration");
         }
 
         auto* const_ = create<ast::Const>(decl->source,                             // source
                                           builder_.Symbols().Register(decl->name),  // symbol
                                           decl->type,                               // type
-                                          constructor.value,                        // constructor
+                                          initializer.value,                        // initializer
                                           ast::AttributeList{});                    // attributes
 
         return create<ast::VariableDeclStatement>(decl->source, const_);
@@ -1831,18 +1828,18 @@
             return Failure::kErrored;
         }
 
-        auto constructor = logical_or_expression();
-        if (constructor.errored) {
+        auto initializer = logical_or_expression();
+        if (initializer.errored) {
             return Failure::kErrored;
         }
-        if (!constructor.matched) {
-            return add_error(peek(), "missing constructor for 'let' declaration");
+        if (!initializer.matched) {
+            return add_error(peek(), "missing initializer for 'let' declaration");
         }
 
         auto* let = create<ast::Let>(decl->source,                             // source
                                      builder_.Symbols().Register(decl->name),  // symbol
                                      decl->type,                               // type
-                                     constructor.value,                        // constructor
+                                     initializer.value,                        // initializer
                                      ast::AttributeList{});                    // attributes
 
         return create<ast::VariableDeclStatement>(decl->source, let);
@@ -1856,17 +1853,17 @@
         return Failure::kNoMatch;
     }
 
-    const ast::Expression* constructor = nullptr;
+    const ast::Expression* initializer = nullptr;
     if (match(Token::Type::kEqual)) {
-        auto constructor_expr = logical_or_expression();
-        if (constructor_expr.errored) {
+        auto initializer_expr = logical_or_expression();
+        if (initializer_expr.errored) {
             return Failure::kErrored;
         }
-        if (!constructor_expr.matched) {
-            return add_error(peek(), "missing constructor for 'var' declaration");
+        if (!initializer_expr.matched) {
+            return add_error(peek(), "missing initializer for 'var' declaration");
         }
 
-        constructor = constructor_expr.value;
+        initializer = initializer_expr.value;
     }
 
     auto* var = create<ast::Var>(decl->source,                             // source
@@ -1874,7 +1871,7 @@
                                  decl->type,                               // type
                                  decl->storage_class,                      // storage class
                                  decl->access,                             // access control
-                                 constructor,                              // constructor
+                                 initializer,                              // initializer
                                  ast::AttributeList{});                    // attributes
 
     return create<ast::VariableDeclStatement>(var->source, var);
@@ -1963,7 +1960,7 @@
 }
 
 // switch_stmt
-//   : SWITCH paren_rhs_stmt BRACKET_LEFT switch_body+ BRACKET_RIGHT
+//   : SWITCH paren_expression BRACKET_LEFT switch_body+ BRACKET_RIGHT
 Maybe<const ast::SwitchStatement*> ParserImpl::switch_stmt() {
     Source source;
     if (!match(Token::Type::kSwitch, &source)) {
@@ -2325,8 +2322,8 @@
 //   : IDENT argument_expression_list?
 //   | type_decl argument_expression_list
 //   | const_literal
-//   | paren_rhs_stmt
-//   | BITCAST LESS_THAN type_decl GREATER_THAN paren_rhs_stmt
+//   | paren_expression
+//   | BITCAST LESS_THAN type_decl GREATER_THAN paren_expression
 Maybe<const ast::Expression*> ParserImpl::primary_expression() {
     auto t = peek();
     auto source = t.source();
@@ -2340,7 +2337,7 @@
     }
 
     if (t.Is(Token::Type::kParenLeft)) {
-        auto paren = expect_paren_rhs_stmt();
+        auto paren = expect_paren_expression();
         if (paren.errored) {
             return Failure::kErrored;
         }
@@ -2356,7 +2353,7 @@
             return Failure::kErrored;
         }
 
-        auto params = expect_paren_rhs_stmt();
+        auto params = expect_paren_expression();
         if (params.errored) {
             return Failure::kErrored;
         }
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 5857454..a9c0423 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -476,9 +476,9 @@
     /// Parses a `body_stmt` grammar element, erroring on parse failure.
     /// @returns the parsed statements
     Expect<ast::BlockStatement*> expect_body_stmt();
-    /// Parses a `paren_rhs_stmt` grammar element, erroring on parse failure.
+    /// Parses a `paren_expression` grammar element, erroring on parse failure.
     /// @returns the parsed element or nullptr
-    Expect<const ast::Expression*> expect_paren_rhs_stmt();
+    Expect<const ast::Expression*> expect_paren_expression();
     /// Parses a `statements` grammar element
     /// @returns the statements parsed
     Expect<ast::StatementList> expect_statements();
diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
index 48969a5..453e549 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -220,7 +220,7 @@
 
 TEST_F(ParserImplErrorTest, ConstVarStmtMissingConstructor) {
     EXPECT("fn f() { let a : i32 = >; }",
-           R"(test.wgsl:1:24 error: missing constructor for 'let' declaration
+           R"(test.wgsl:1:24 error: missing initializer for 'let' declaration
 fn f() { let a : i32 = >; }
                        ^
 )");
@@ -1284,7 +1284,7 @@
 
 TEST_F(ParserImplErrorTest, VarStmtInvalidAssignment) {
     EXPECT("fn f() { var a : u32 = >; }",
-           R"(test.wgsl:1:24 error: missing constructor for 'var' declaration
+           R"(test.wgsl:1:24 error: missing initializer for 'var' declaration
 fn f() { var a : u32 = >; }
                        ^
 )");
diff --git a/src/tint/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_paren_expression_test.cc
similarity index 89%
rename from src/tint/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
rename to src/tint/reader/wgsl/parser_impl_paren_expression_test.cc
index dd5a978..e6bb223 100644
--- a/src/tint/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_paren_expression_test.cc
@@ -19,7 +19,7 @@
 
 TEST_F(ParserImplTest, ParenRhsStmt) {
     auto p = parser("(a + b)");
-    auto e = p->expect_paren_rhs_stmt();
+    auto e = p->expect_paren_expression();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
     ASSERT_NE(e.value, nullptr);
@@ -28,7 +28,7 @@
 
 TEST_F(ParserImplTest, ParenRhsStmt_MissingLeftParen) {
     auto p = parser("true)");
-    auto e = p->expect_paren_rhs_stmt();
+    auto e = p->expect_paren_expression();
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(e.errored);
     ASSERT_EQ(e.value, nullptr);
@@ -37,7 +37,7 @@
 
 TEST_F(ParserImplTest, ParenRhsStmt_MissingRightParen) {
     auto p = parser("(true");
-    auto e = p->expect_paren_rhs_stmt();
+    auto e = p->expect_paren_expression();
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(e.errored);
     ASSERT_EQ(e.value, nullptr);
@@ -46,7 +46,7 @@
 
 TEST_F(ParserImplTest, ParenRhsStmt_InvalidExpression) {
     auto p = parser("(if (a() {})");
-    auto e = p->expect_paren_rhs_stmt();
+    auto e = p->expect_paren_expression();
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(e.errored);
     ASSERT_EQ(e.value, nullptr);
@@ -55,7 +55,7 @@
 
 TEST_F(ParserImplTest, ParenRhsStmt_MissingExpression) {
     auto p = parser("()");
-    auto e = p->expect_paren_rhs_stmt();
+    auto e = p->expect_paren_expression();
     ASSERT_TRUE(p->has_error());
     ASSERT_TRUE(e.errored);
     ASSERT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_statement_test.cc b/src/tint/reader/wgsl/parser_impl_statement_test.cc
index 8190829..677daa0 100644
--- a/src/tint/reader/wgsl/parser_impl_statement_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_statement_test.cc
@@ -114,7 +114,7 @@
     EXPECT_TRUE(e.errored);
     EXPECT_FALSE(e.matched);
     EXPECT_EQ(e.value, nullptr);
-    EXPECT_EQ(p->error(), "1:14: missing constructor for 'var' declaration");
+    EXPECT_EQ(p->error(), "1:14: missing initializer for 'var' declaration");
 }
 
 TEST_F(ParserImplTest, Statement_Variable_MissingSemicolon) {
diff --git a/src/tint/reader/wgsl/parser_impl_storage_class_test.cc b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
index 4abbe76..deb87fc 100644
--- a/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
@@ -56,10 +56,6 @@
     EXPECT_EQ(sc.errored, true);
     EXPECT_TRUE(p->has_error());
     EXPECT_EQ(p->error(), "1:1: invalid storage class for test");
-
-    auto t = p->next();
-    EXPECT_TRUE(t.IsIdentifier());
-    EXPECT_EQ(t.to_str(), "not");
 }
 
 }  // namespace
diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
index bc2bfe5..5459af6 100644
--- a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
@@ -272,7 +272,7 @@
     EXPECT_FALSE(t.matched);
     ASSERT_EQ(t.value, nullptr);
     ASSERT_TRUE(p->has_error());
-    ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
+    ASSERT_EQ(p->error(), "1:5: expected identifier for storage class");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_MissingType) {
@@ -302,7 +302,7 @@
     EXPECT_FALSE(t.matched);
     ASSERT_EQ(t.value, nullptr);
     ASSERT_TRUE(p->has_error());
-    ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
+    ASSERT_EQ(p->error(), "1:5: expected identifier for storage class");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_BadStorageClass) {
diff --git a/src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc b/src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc
index de63f92..aefd2b5 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_qualifier_test.cc
@@ -73,7 +73,7 @@
     EXPECT_TRUE(p->has_error());
     EXPECT_TRUE(sc.errored);
     EXPECT_FALSE(sc.matched);
-    EXPECT_EQ(p->error(), "1:2: invalid storage class for variable declaration");
+    EXPECT_EQ(p->error(), "1:2: expected identifier for storage class");
 }
 
 TEST_F(ParserImplTest, VariableQualifier_MissingLessThan) {
@@ -84,7 +84,7 @@
     EXPECT_FALSE(sc.matched);
 
     auto t = p->next();
-    ASSERT_TRUE(t.Is(Token::Type::kPrivate));
+    ASSERT_TRUE(t.Is(Token::Type::kIdentifier));
 }
 
 TEST_F(ParserImplTest, VariableQualifier_MissingLessThan_AfterSC) {
@@ -95,7 +95,7 @@
     EXPECT_FALSE(sc.matched);
 
     auto t = p->next();
-    ASSERT_TRUE(t.Is(Token::Type::kPrivate));
+    ASSERT_TRUE(t.Is(Token::Type::kIdentifier));
 }
 
 TEST_F(ParserImplTest, VariableQualifier_MissingGreaterThan) {
diff --git a/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
index 9fdc503..21ab6a8 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_stmt_test.cc
@@ -63,7 +63,7 @@
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:15: missing constructor for 'var' declaration");
+    EXPECT_EQ(p->error(), "1:15: missing initializer for 'var' declaration");
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit) {
@@ -170,7 +170,7 @@
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:14: missing constructor for 'let' declaration");
+    EXPECT_EQ(p->error(), "1:14: missing initializer for 'let' declaration");
 }
 
 TEST_F(ParserImplTest, VariableStmt_Let_InvalidConstructor) {
@@ -180,7 +180,7 @@
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:15: missing constructor for 'let' declaration");
+    EXPECT_EQ(p->error(), "1:15: missing initializer for 'let' declaration");
 }
 
 }  // namespace
diff --git a/src/tint/reader/wgsl/token.cc b/src/tint/reader/wgsl/token.cc
index c6a06c9..44255d3 100644
--- a/src/tint/reader/wgsl/token.cc
+++ b/src/tint/reader/wgsl/token.cc
@@ -167,8 +167,6 @@
             return "fn";
         case Token::Type::kFor:
             return "for";
-        case Token::Type::kFunction:
-            return "function";
         case Token::Type::kI32:
             return "i32";
         case Token::Type::kIf:
@@ -199,8 +197,6 @@
             return "mat4x4";
         case Token::Type::kOverride:
             return "override";
-        case Token::Type::kPrivate:
-            return "private";
         case Token::Type::kPtr:
             return "ptr";
         case Token::Type::kReturn:
@@ -209,8 +205,6 @@
             return "sampler";
         case Token::Type::kComparisonSampler:
             return "sampler_comparison";
-        case Token::Type::kStorage:
-            return "storage";
         case Token::Type::kStruct:
             return "struct";
         case Token::Type::kSwitch:
@@ -255,8 +249,6 @@
             return "type";
         case Token::Type::kU32:
             return "u32";
-        case Token::Type::kUniform:
-            return "uniform";
         case Token::Type::kVar:
             return "var";
         case Token::Type::kVec2:
@@ -267,8 +259,6 @@
             return "vec4";
         case Token::Type::kWhile:
             return "while";
-        case Token::Type::kWorkgroup:
-            return "workgroup";
     }
 
     return "<unknown>";
diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h
index df78a5b..473588d 100644
--- a/src/tint/reader/wgsl/token.h
+++ b/src/tint/reader/wgsl/token.h
@@ -177,8 +177,6 @@
         kFn,
         // A 'for'
         kFor,
-        /// A 'function'
-        kFunction,
         /// A 'i32'
         kI32,
         /// A 'if'
@@ -209,8 +207,6 @@
         kMat4x4,
         /// A 'override'
         kOverride,
-        /// A 'private'
-        kPrivate,
         /// A 'ptr'
         kPtr,
         /// A 'return'
@@ -219,8 +215,6 @@
         kSampler,
         /// A 'sampler_comparison'
         kComparisonSampler,
-        /// A 'storage'
-        kStorage,
         /// A 'struct'
         kStruct,
         /// A 'switch'
@@ -265,8 +259,6 @@
         kType,
         /// A 'u32'
         kU32,
-        /// A 'uniform'
-        kUniform,
         /// A 'var'
         kVar,
         /// A 'vec2'
@@ -277,8 +269,6 @@
         kVec4,
         /// A 'while'
         kWhile,
-        /// A 'workgroup'
-        kWorkgroup,
     };
 
     /// Converts a token type to a name
diff --git a/src/tint/transform/substitute_override.cc b/src/tint/transform/substitute_override.cc
new file mode 100644
index 0000000..aa7abff
--- /dev/null
+++ b/src/tint/transform/substitute_override.cc
@@ -0,0 +1,96 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/transform/substitute_override.h"
+
+#include <functional>
+
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/variable.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::SubstituteOverride);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::SubstituteOverride::Config);
+
+namespace tint::transform {
+
+SubstituteOverride::SubstituteOverride() = default;
+
+SubstituteOverride::~SubstituteOverride() = default;
+
+bool SubstituteOverride::ShouldRun(const Program* program, const DataMap&) const {
+    for (auto* node : program->AST().GlobalVariables()) {
+        if (node->Is<ast::Override>()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void SubstituteOverride::Run(CloneContext& ctx, const DataMap& config, DataMap&) const {
+    const auto* data = config.Get<Config>();
+    if (!data) {
+        ctx.dst->Diagnostics().add_error(diag::System::Transform,
+                                         "Missing override substitution data");
+        return;
+    }
+
+    ctx.ReplaceAll([&](const ast::Override* w) -> const ast::Const* {
+        auto ident = w->Identifier(ctx.src->Symbols());
+
+        auto src = ctx.Clone(w->source);
+        auto sym = ctx.Clone(w->symbol);
+        auto* ty = ctx.Clone(w->type);
+
+        // No replacement provided, just clone the override node as a const.
+        auto iter = data->map.find(ident);
+        if (iter == data->map.end()) {
+            if (!w->constructor) {
+                ctx.dst->Diagnostics().add_error(
+                    diag::System::Transform,
+                    "Initializer not provided for override, and override not overridden.");
+                return nullptr;
+            }
+            return ctx.dst->Const(src, sym, ty, ctx.Clone(w->constructor));
+        }
+
+        auto value = iter->second;
+        auto* ctor = Switch(
+            ctx.src->Sem().Get(w)->Type(),
+            [&](const sem::Bool*) { return ctx.dst->Expr(!std::equal_to<double>()(value, 0.0)); },
+            [&](const sem::I32*) { return ctx.dst->Expr(i32(value)); },
+            [&](const sem::U32*) { return ctx.dst->Expr(u32(value)); },
+            [&](const sem::F32*) { return ctx.dst->Expr(f32(value)); },
+            [&](const sem::F16*) { return ctx.dst->Expr(f16(value)); });
+
+        if (!ctor) {
+            ctx.dst->Diagnostics().add_error(diag::System::Transform,
+                                             "Failed to create override expression");
+            return nullptr;
+        }
+
+        return ctx.dst->Const(src, sym, ty, ctor);
+    });
+
+    ctx.Clone();
+}
+
+SubstituteOverride::Config::Config() = default;
+
+SubstituteOverride::Config::Config(const Config&) = default;
+
+SubstituteOverride::Config::~Config() = default;
+
+SubstituteOverride::Config& SubstituteOverride::Config::operator=(const Config&) = default;
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/substitute_override.h b/src/tint/transform/substitute_override.h
new file mode 100644
index 0000000..ab4b38a
--- /dev/null
+++ b/src/tint/transform/substitute_override.h
@@ -0,0 +1,89 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_SUBSTITUTE_OVERRIDE_H_
+#define SRC_TINT_TRANSFORM_SUBSTITUTE_OVERRIDE_H_
+
+#include <string>
+#include <unordered_map>
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// A transform that replaces overrides with the constant values provided.
+///
+/// # Example
+/// ```
+/// override width: f32;
+/// @id(1) override height: i32 = 4;
+/// override depth = 1i;
+/// ```
+///
+/// When transformed with `width` -> 1, `1` -> 22, `depth` -> 42
+///
+/// ```
+/// const width: f32 = 1f;
+/// const height: i32 = 22i;
+/// const depth = 42i;
+/// ```
+///
+/// @see crbug.com/tint/1582
+class SubstituteOverride final : public Castable<SubstituteOverride, Transform> {
+  public:
+    /// Configuration options for the transform
+    struct Config final : public Castable<Config, Data> {
+        /// Constructor
+        Config();
+
+        /// Copy constructor
+        Config(const Config&);
+
+        /// Destructor
+        ~Config() override;
+
+        /// Assignment operator
+        /// @returns this Config
+        Config& operator=(const Config&);
+
+        /// The map of override name (either the identifier or id) to value.
+        /// The value is always a double coming into the transform and will be
+        /// converted to the correct type through and initializer.
+        std::unordered_map<std::string, double> map;
+    };
+
+    /// Constructor
+    SubstituteOverride();
+
+    /// Destructor
+    ~SubstituteOverride() override;
+
+    /// @param program the program to inspect
+    /// @param data optional extra transform-specific input data
+    /// @returns true if this transform should be run for the given program
+    bool ShouldRun(const Program* program, const DataMap& data = {}) const override;
+
+  protected:
+    /// Runs the transform using the CloneContext built for transforming a
+    /// program. Run() is responsible for calling Clone() on the CloneContext.
+    /// @param ctx the CloneContext primed with the input program and
+    /// ProgramBuilder
+    /// @param inputs optional extra transform-specific input data
+    /// @param outputs optional extra transform-specific output data
+    void Run(CloneContext& ctx, const DataMap& inputs, DataMap& outputs) const override;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_SUBSTITUTE_OVERRIDE_H_
diff --git a/src/tint/transform/substitute_override_test.cc b/src/tint/transform/substitute_override_test.cc
new file mode 100644
index 0000000..70ac675
--- /dev/null
+++ b/src/tint/transform/substitute_override_test.cc
@@ -0,0 +1,243 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/transform/substitute_override.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint::transform {
+namespace {
+
+using SubstituteOverrideTest = TransformTest;
+
+TEST_F(SubstituteOverrideTest, Error_NoData) {
+    auto* src = R"(
+override width: i32;
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    auto* expect = "error: Missing override substitution data";
+
+    DataMap data;
+    auto got = Run<SubstituteOverride>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SubstituteOverrideTest, Error_NoOverrideValue) {
+    auto* src = R"(
+override width: i32;
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    auto* expect = "error: Initializer not provided for override, and override not overridden.";
+
+    SubstituteOverride::Config cfg;
+    DataMap data;
+    data.Add<SubstituteOverride::Config>(cfg);
+
+    auto got = Run<SubstituteOverride>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SubstituteOverrideTest, Module_NoOverrides) {
+    auto* src = R"(
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    auto* expect = R"(
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    SubstituteOverride::Config cfg;
+
+    DataMap data;
+    data.Add<SubstituteOverride::Config>(cfg);
+    auto got = Run<SubstituteOverride>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SubstituteOverrideTest, Identifier) {
+    auto* src = R"(
+override i_width: i32;
+override i_height = 1i;
+
+override f_width: f32;
+override f_height = 1.f;
+
+// TODO(crbug.com/tint/1473)
+// override h_width: f16;
+// override h_height = 1.h;
+
+override b_width: bool;
+override b_height = true;
+
+override o_width = 2i;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    auto* expect = R"(
+const i_width : i32 = 42i;
+
+const i_height = 11i;
+
+const f_width : f32 = 22.299999237f;
+
+const f_height = 12.399999619f;
+
+const b_width : bool = true;
+
+const b_height = false;
+
+const o_width = 2i;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    SubstituteOverride::Config cfg;
+    cfg.map.insert({"i_width", 42.0});
+    cfg.map.insert({"i_height", 11.0});
+    cfg.map.insert({"f_width", 22.3});
+    cfg.map.insert({"f_height", 12.4});
+    cfg.map.insert({"h_width", 9.4});
+    cfg.map.insert({"h_height", 3.4});
+    cfg.map.insert({"b_width", 1.0});
+    cfg.map.insert({"b_height", 0.0});
+
+    DataMap data;
+    data.Add<SubstituteOverride::Config>(cfg);
+    auto got = Run<SubstituteOverride>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SubstituteOverrideTest, Id) {
+    auto* src = R"(
+enable f16;
+
+@id(0) override i_width: i32;
+@id(10) override i_height = 1i;
+
+@id(1) override f_width: f32;
+@id(9) override f_height = 1.f;
+
+// TODO(crbug.com/tint/1473)
+// @id(2) override h_width: f16;
+// @id(8) override h_height = 1.h;
+
+@id(3) override b_width: bool;
+@id(7) override b_height = true;
+
+@id(5) override o_width = 2i;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    auto* expect = R"(
+enable f16;
+
+const i_width : i32 = 42i;
+
+const i_height = 11i;
+
+const f_width : f32 = 22.299999237f;
+
+const f_height = 12.399999619f;
+
+const b_width : bool = true;
+
+const b_height = false;
+
+const o_width = 2i;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    SubstituteOverride::Config cfg;
+    cfg.map.insert({"0", 42.0});
+    cfg.map.insert({"10", 11.0});
+    cfg.map.insert({"1", 22.3});
+    cfg.map.insert({"9", 12.4});
+    cfg.map.insert({"2", 9.4});
+    cfg.map.insert({"8", 3.4});
+    cfg.map.insert({"3", 1.0});
+    cfg.map.insert({"7", 0.0});
+    // No effect because an @id is set for o_width
+    cfg.map.insert({"o_width", 13});
+
+    DataMap data;
+    data.Add<SubstituteOverride::Config>(cfg);
+    auto got = Run<SubstituteOverride>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SubstituteOverrideTest, Identifier_Expression) {
+    auto* src = R"(
+override i_height = ~2i;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    auto* expect = R"(
+const i_height = 11i;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    SubstituteOverride::Config cfg;
+    cfg.map.insert({"i_height", 11.0});
+
+    DataMap data;
+    data.Add<SubstituteOverride::Config>(cfg);
+    auto got = Run<SubstituteOverride>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace tint::transform
diff --git a/src/tint/utils/io/command_windows.cc b/src/tint/utils/io/command_windows.cc
index f953a85..36c39c6 100644
--- a/src/tint/utils/io/command_windows.cc
+++ b/src/tint/utils/io/command_windows.cc
@@ -16,9 +16,12 @@
 
 #define WIN32_LEAN_AND_MEAN 1
 #include <Windows.h>
+#include <dbghelp.h>
 #include <sstream>
 #include <string>
 
+#include "src/tint/utils/defer.h"
+
 namespace tint::utils {
 
 namespace {
@@ -97,9 +100,34 @@
     Handle write;
 };
 
+/// Queries whether the file at the given path is an executable or DLL.
 bool ExecutableExists(const std::string& path) {
-    DWORD type = 0;
-    return GetBinaryTypeA(path.c_str(), &type);
+    auto file = Handle(CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                                   FILE_ATTRIBUTE_READONLY, NULL));
+    if (!file) {
+        return false;
+    }
+
+    auto map = Handle(CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL));
+    if (map == INVALID_HANDLE_VALUE) {
+        return false;
+    }
+
+    void* addr_header = MapViewOfFileEx(map, FILE_MAP_READ, 0, 0, 0, NULL);
+
+    // Dynamically obtain the address of, and call ImageNtHeader. This is done to avoid tint.exe
+    // needing to statically link Dbghelp.lib.
+    static auto* dbg_help = LoadLibraryA("Dbghelp.dll");  // Leaks, but who cares?
+    if (dbg_help) {
+        if (FARPROC proc = GetProcAddress(dbg_help, "ImageNtHeader")) {
+            using ImageNtHeaderPtr = decltype(&ImageNtHeader);
+            auto* image_nt_header = reinterpret_cast<ImageNtHeaderPtr>(proc)(addr_header);
+            return image_nt_header != nullptr;
+        }
+    }
+
+    // Couldn't call ImageNtHeader, assume it is executable
+    return false;
 }
 
 std::string FindExecutable(const std::string& name) {
@@ -187,7 +215,8 @@
                         &si,                                    // Pointer to STARTUPINFO structure
                         &pi)) {  // Pointer to PROCESS_INFORMATION structure
         Output out;
-        out.err = "Command::Exec() CreateProcess() failed";
+        out.err = "Command::Exec() CreateProcess('" + args.str() + "') failed";
+        out.error_code = 1;
         return out;
     }
 
diff --git a/src/tint/val/hlsl.cc b/src/tint/val/hlsl.cc
index 49c3850..a2998d8 100644
--- a/src/tint/val/hlsl.cc
+++ b/src/tint/val/hlsl.cc
@@ -30,8 +30,7 @@
 
 Result HlslUsingDXC(const std::string& dxc_path,
                     const std::string& source,
-                    const EntryPointList& entry_points,
-                    const std::vector<std::string>& overrides) {
+                    const EntryPointList& entry_points) {
     Result result;
 
     auto dxc = utils::Command(dxc_path);
@@ -70,13 +69,7 @@
             "/Zpr "  // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
             "/Gis";  // D3DCOMPILE_IEEE_STRICTNESS
 
-        std::string defs;
-        defs.reserve(overrides.size() * 20);
-        for (auto& o : overrides) {
-            defs += "/D" + o + " ";
-        }
-
-        auto res = dxc(profile, "-E " + ep.first, compileFlags, file.Path(), defs);
+        auto res = dxc(profile, "-E " + ep.first, compileFlags, file.Path());
         if (!res.out.empty()) {
             if (!result.output.empty()) {
                 result.output += "\n";
@@ -102,15 +95,15 @@
 }
 
 #ifdef _WIN32
-Result HlslUsingFXC(const std::string& source,
-                    const EntryPointList& entry_points,
-                    const std::vector<std::string>& overrides) {
+Result HlslUsingFXC(const std::string& fxc_path,
+                    const std::string& source,
+                    const EntryPointList& entry_points) {
     Result result;
 
     // This library leaks if an error happens in this function, but it is ok
     // because it is loaded at most once, and the executables using HlslUsingFXC
     // are short-lived.
-    HMODULE fxcLib = LoadLibraryA("d3dcompiler_47.dll");
+    HMODULE fxcLib = LoadLibraryA(fxc_path.c_str());
     if (fxcLib == nullptr) {
         result.output = "Couldn't load FXC";
         result.failed = true;
@@ -148,26 +141,12 @@
         UINT compileFlags = D3DCOMPILE_OPTIMIZATION_LEVEL0 | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR |
                             D3DCOMPILE_IEEE_STRICTNESS;
 
-        auto overrides_copy = overrides;  // Copy so that we can replace '=' with '\0'
-        std::vector<D3D_SHADER_MACRO> macros;
-        macros.reserve(overrides_copy.size() * 2);
-        for (auto& o : overrides_copy) {
-            if (auto sep = o.find_first_of('='); sep != std::string::npos) {
-                // Replace '=' with '\0' so we can point directly into the allocated string buffer
-                o[sep] = '\0';
-                macros.push_back(D3D_SHADER_MACRO{&o[0], &o[sep + 1]});
-            } else {
-                macros.emplace_back(D3D_SHADER_MACRO{o.c_str(), NULL});
-            }
-        }
-        macros.emplace_back(D3D_SHADER_MACRO{NULL, NULL});
-
         ComPtr<ID3DBlob> compiledShader;
         ComPtr<ID3DBlob> errors;
         HRESULT cr = d3dCompile(source.c_str(),    // pSrcData
                                 source.length(),   // SrcDataSize
                                 nullptr,           // pSourceName
-                                macros.data(),     // pDefines
+                                nullptr,           // pDefines
                                 nullptr,           // pInclude
                                 ep.first.c_str(),  // pEntrypoint
                                 profile,           // pTarget
diff --git a/src/tint/val/val.h b/src/tint/val/val.h
index c869efb..dae6fca 100644
--- a/src/tint/val/val.h
+++ b/src/tint/val/val.h
@@ -30,6 +30,9 @@
 
 using EntryPointList = std::vector<std::pair<std::string, ast::PipelineStage>>;
 
+/// Name of the FXC compiler DLL
+static constexpr const char kFxcDLLName[] = "d3dcompiler_47.dll";
+
 /// The return structure of Validate()
 struct Result {
     /// True if validation passed
@@ -43,23 +46,21 @@
 /// @param dxc_path path to DXC
 /// @param source the generated HLSL source
 /// @param entry_points the list of entry points to validate
-/// @param overrides optional list of pipeline overrides
 /// @return the result of the compile
 Result HlslUsingDXC(const std::string& dxc_path,
                     const std::string& source,
-                    const EntryPointList& entry_points,
-                    const std::vector<std::string>& overrides);
+                    const EntryPointList& entry_points);
 
 #ifdef _WIN32
 /// Hlsl attempts to compile the shader with FXC, verifying that the shader
 /// compiles successfully.
+/// @param fxc_path path to the FXC DLL
 /// @param source the generated HLSL source
 /// @param entry_points the list of entry points to validate
-/// @param overrides optional list of pipeline overrides
 /// @return the result of the compile
-Result HlslUsingFXC(const std::string& source,
-                    const EntryPointList& entry_points,
-                    const std::vector<std::string>& overrides);
+Result HlslUsingFXC(const std::string& fxc_path,
+                    const std::string& source,
+                    const EntryPointList& entry_points);
 #endif  // _WIN32
 
 /// Msl attempts to compile the shader with the Metal Shader Compiler,