Import Tint changes from Dawn

Changes:
  - 81d11b3cf1d5d10427b3ee5ac08f3e209d37a97f tint/spirv-reader: cast offset and count args to u32 for ... by Antonio Maiorano <amaiorano@google.com>
  - d3c9e8c0fe6e20ece7664cd502089407a7d3a8fe tint: Fix RemovePhonies with @must_use fns by Ben Clayton <bclayton@google.com>
  - 9313aab0fdba908ee85068eadac873656ba275e5 tint/reader/wgsl: Remove case_body() by Ben Clayton <bclayton@google.com>
  - 2fc56a1c637925f76c6f40604283d33f6cf5018c tint: Use std::string_view for diagnostics by Ben Clayton <bclayton@google.com>
  - 9a56b25a310cb0c40004669136c0b0c6f3edd82f tint/ast: Remove move-constructors for AST nodes by Ben Clayton <bclayton@google.com>
  - 39d4065653b86a705e1f270d7622566e26865221 Move BindingRemapper and MultiplanarExternalTexture to a ... by dan sinclair <dsinclair@chromium.org>
  - 0551c4f40fa372272ab93de25ef12e0c722a88b4 Move MSL configuration for `ArrayLengthFromUniform` trans... by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: 81d11b3cf1d5d10427b3ee5ac08f3e209d37a97f
Change-Id: Ia59e348b41c22c73056d8287520a6e3ef02a86cd
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/124381
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 0a0c027..0bde567 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -26,10 +26,8 @@
 #include "src/tint/inspector/inspector.h"
 #include "src/tint/reader/reader.h"
 #include "src/tint/text/unicode.h"
-#include "src/tint/transform/binding_remapper.h"
 #include "src/tint/transform/first_index_offset.h"
 #include "src/tint/transform/manager.h"
-#include "src/tint/transform/multiplanar_external_texture.h"
 #include "src/tint/transform/renamer.h"
 #include "src/tint/transform/single_entry_point.h"
 #include "src/tint/transform/substitute_override.h"
@@ -37,6 +35,8 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/writer/array_length_from_uniform_options.h"
 #include "src/tint/writer/binding_point.h"
+#include "src/tint/writer/binding_remapper_options.h"
+#include "src/tint/writer/external_texture_options.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 11d98bd..b065ed0 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -900,6 +900,8 @@
     "writer/array_length_from_uniform_options.cc",
     "writer/array_length_from_uniform_options.h",
     "writer/binding_point.h",
+    "writer/binding_remapper_options.cc",
+    "writer/binding_remapper_options.h",
     "writer/check_supported_extensions.cc",
     "writer/check_supported_extensions.h",
     "writer/external_texture_options.cc",
@@ -1749,7 +1751,6 @@
       "reader/wgsl/parser_impl_break_stmt_test.cc",
       "reader/wgsl/parser_impl_bug_cases_test.cc",
       "reader/wgsl/parser_impl_call_stmt_test.cc",
-      "reader/wgsl/parser_impl_case_body_test.cc",
       "reader/wgsl/parser_impl_compound_stmt_test.cc",
       "reader/wgsl/parser_impl_const_literal_test.cc",
       "reader/wgsl/parser_impl_continue_stmt_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 07f7aa8..d9b9469 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -544,6 +544,8 @@
   writer/array_length_from_uniform_options.cc
   writer/array_length_from_uniform_options.h
   writer/binding_point.h
+  writer/binding_remapper_options.cc
+  writer/binding_remapper_options.h
   writer/check_supported_extensions.cc
   writer/check_supported_extensions.h
   writer/external_texture_options.cc
@@ -1102,7 +1104,6 @@
       reader/wgsl/parser_impl_break_stmt_test.cc
       reader/wgsl/parser_impl_bug_cases_test.cc
       reader/wgsl/parser_impl_call_stmt_test.cc
-      reader/wgsl/parser_impl_case_body_test.cc
       reader/wgsl/parser_impl_compound_stmt_test.cc
       reader/wgsl/parser_impl_const_literal_test.cc
       reader/wgsl/parser_impl_continue_stmt_test.cc
diff --git a/src/tint/ast/accessor_expression.cc b/src/tint/ast/accessor_expression.cc
index 55cdabf..6d0a751 100644
--- a/src/tint/ast/accessor_expression.cc
+++ b/src/tint/ast/accessor_expression.cc
@@ -29,8 +29,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, object, program_id);
 }
 
-AccessorExpression::AccessorExpression(AccessorExpression&&) = default;
-
 AccessorExpression::~AccessorExpression() = default;
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/accessor_expression.h b/src/tint/ast/accessor_expression.h
index 653eccb..d668f1f 100644
--- a/src/tint/ast/accessor_expression.h
+++ b/src/tint/ast/accessor_expression.h
@@ -28,8 +28,8 @@
     /// @param source the member accessor expression source
     /// @param object the object
     AccessorExpression(ProgramID pid, NodeID nid, const Source& source, const Expression* object);
-    /// Move constructor
-    AccessorExpression(AccessorExpression&&);
+
+    /// Destructor
     ~AccessorExpression() override;
 
     /// The object being accessed
diff --git a/src/tint/ast/alias.cc b/src/tint/ast/alias.cc
index ec5cf43..c878793 100644
--- a/src/tint/ast/alias.cc
+++ b/src/tint/ast/alias.cc
@@ -25,8 +25,6 @@
     TINT_ASSERT(AST, type);
 }
 
-Alias::Alias(Alias&&) = default;
-
 Alias::~Alias() = default;
 
 const Alias* Alias::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/alias.h b/src/tint/ast/alias.h
index 267a313..c30d387 100644
--- a/src/tint/ast/alias.h
+++ b/src/tint/ast/alias.h
@@ -32,8 +32,7 @@
     /// @param name the symbol for the alias
     /// @param subtype the alias'd type
     Alias(ProgramID pid, NodeID nid, const Source& src, const Identifier* name, Type subtype);
-    /// Move constructor
-    Alias(Alias&&);
+
     /// Destructor
     ~Alias() override;
 
diff --git a/src/tint/ast/assignment_statement.cc b/src/tint/ast/assignment_statement.cc
index 6a835b8..0441665 100644
--- a/src/tint/ast/assignment_statement.cc
+++ b/src/tint/ast/assignment_statement.cc
@@ -32,8 +32,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id);
 }
 
-AssignmentStatement::AssignmentStatement(AssignmentStatement&&) = default;
-
 AssignmentStatement::~AssignmentStatement() = default;
 
 const AssignmentStatement* AssignmentStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/assignment_statement.h b/src/tint/ast/assignment_statement.h
index 6b8c412..01a1a75 100644
--- a/src/tint/ast/assignment_statement.h
+++ b/src/tint/ast/assignment_statement.h
@@ -34,8 +34,8 @@
                         const Source& source,
                         const Expression* lhs,
                         const Expression* rhs);
-    /// Move constructor
-    AssignmentStatement(AssignmentStatement&&);
+
+    /// Destructor
     ~AssignmentStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/binary_expression.cc b/src/tint/ast/binary_expression.cc
index ebf704e..6e50f57 100644
--- a/src/tint/ast/binary_expression.cc
+++ b/src/tint/ast/binary_expression.cc
@@ -34,8 +34,6 @@
     TINT_ASSERT(AST, op != BinaryOp::kNone);
 }
 
-BinaryExpression::BinaryExpression(BinaryExpression&&) = default;
-
 BinaryExpression::~BinaryExpression() = default;
 
 const BinaryExpression* BinaryExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/bitcast_expression.cc b/src/tint/ast/bitcast_expression.cc
index 4091d73..089c659 100644
--- a/src/tint/ast/bitcast_expression.cc
+++ b/src/tint/ast/bitcast_expression.cc
@@ -31,7 +31,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
 }
 
-BitcastExpression::BitcastExpression(BitcastExpression&&) = default;
 BitcastExpression::~BitcastExpression() = default;
 
 const BitcastExpression* BitcastExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/bitcast_expression.h b/src/tint/ast/bitcast_expression.h
index 82d3bd2..7737721 100644
--- a/src/tint/ast/bitcast_expression.h
+++ b/src/tint/ast/bitcast_expression.h
@@ -34,8 +34,8 @@
                       const Source& source,
                       Type type,
                       const Expression* expr);
-    /// Move constructor
-    BitcastExpression(BitcastExpression&&);
+
+    /// Destructor
     ~BitcastExpression() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/block_statement.cc b/src/tint/ast/block_statement.cc
index 5d75d27..7c76319 100644
--- a/src/tint/ast/block_statement.cc
+++ b/src/tint/ast/block_statement.cc
@@ -36,8 +36,6 @@
     }
 }
 
-BlockStatement::BlockStatement(BlockStatement&&) = default;
-
 BlockStatement::~BlockStatement() = default;
 
 const BlockStatement* BlockStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/block_statement.h b/src/tint/ast/block_statement.h
index 87989d8..2044b5b 100644
--- a/src/tint/ast/block_statement.h
+++ b/src/tint/ast/block_statement.h
@@ -40,8 +40,8 @@
                    const Source& source,
                    utils::VectorRef<const Statement*> statements,
                    utils::VectorRef<const Attribute*> attributes);
-    /// Move constructor
-    BlockStatement(BlockStatement&&);
+
+    /// Destructor
     ~BlockStatement() override;
 
     /// @returns true if the block has no statements
diff --git a/src/tint/ast/break_if_statement.cc b/src/tint/ast/break_if_statement.cc
index a931541..dad7beb 100644
--- a/src/tint/ast/break_if_statement.cc
+++ b/src/tint/ast/break_if_statement.cc
@@ -29,8 +29,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
 }
 
-BreakIfStatement::BreakIfStatement(BreakIfStatement&&) = default;
-
 BreakIfStatement::~BreakIfStatement() = default;
 
 const BreakIfStatement* BreakIfStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/break_if_statement.h b/src/tint/ast/break_if_statement.h
index 1437797..a366240 100644
--- a/src/tint/ast/break_if_statement.h
+++ b/src/tint/ast/break_if_statement.h
@@ -32,8 +32,7 @@
     /// @param condition the if condition
     BreakIfStatement(ProgramID pid, NodeID nid, const Source& src, const Expression* condition);
 
-    /// Move constructor
-    BreakIfStatement(BreakIfStatement&&);
+    /// Destructor
     ~BreakIfStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext` `ctx`.
diff --git a/src/tint/ast/break_statement.cc b/src/tint/ast/break_statement.cc
index ecd5f06..e898d8c 100644
--- a/src/tint/ast/break_statement.cc
+++ b/src/tint/ast/break_statement.cc
@@ -23,8 +23,6 @@
 BreakStatement::BreakStatement(ProgramID pid, NodeID nid, const Source& src)
     : Base(pid, nid, src) {}
 
-BreakStatement::BreakStatement(BreakStatement&&) = default;
-
 BreakStatement::~BreakStatement() = default;
 
 const BreakStatement* BreakStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/break_statement.h b/src/tint/ast/break_statement.h
index 92f67b7..d9676bb 100644
--- a/src/tint/ast/break_statement.h
+++ b/src/tint/ast/break_statement.h
@@ -27,8 +27,8 @@
     /// @param nid the unique node identifier
     /// @param src the source of this node
     BreakStatement(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    BreakStatement(BreakStatement&&);
+
+    /// Destructor
     ~BreakStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/builtin_texture_helper_test.h b/src/tint/ast/builtin_texture_helper_test.h
index 738db0c..2f941bb 100644
--- a/src/tint/ast/builtin_texture_helper_test.h
+++ b/src/tint/ast/builtin_texture_helper_test.h
@@ -219,6 +219,7 @@
                         bool /* returns_value */);
     /// Copy constructor
     TextureOverloadCase(const TextureOverloadCase&);
+
     /// Destructor
     ~TextureOverloadCase();
 
diff --git a/src/tint/ast/call_expression.cc b/src/tint/ast/call_expression.cc
index 9da7106..2e49c5f 100644
--- a/src/tint/ast/call_expression.cc
+++ b/src/tint/ast/call_expression.cc
@@ -36,8 +36,6 @@
     }
 }
 
-CallExpression::CallExpression(CallExpression&&) = default;
-
 CallExpression::~CallExpression() = default;
 
 const CallExpression* CallExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/call_expression.h b/src/tint/ast/call_expression.h
index 5085818..6660494 100644
--- a/src/tint/ast/call_expression.h
+++ b/src/tint/ast/call_expression.h
@@ -43,8 +43,7 @@
                    const IdentifierExpression* target,
                    utils::VectorRef<const Expression*> args);
 
-    /// Move constructor
-    CallExpression(CallExpression&&);
+    /// Destructor
     ~CallExpression() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/call_statement.cc b/src/tint/ast/call_statement.cc
index 597e30f..89772dc 100644
--- a/src/tint/ast/call_statement.cc
+++ b/src/tint/ast/call_statement.cc
@@ -29,8 +29,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
 }
 
-CallStatement::CallStatement(CallStatement&&) = default;
-
 CallStatement::~CallStatement() = default;
 
 const CallStatement* CallStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/call_statement.h b/src/tint/ast/call_statement.h
index daf0b3f..9af47a1 100644
--- a/src/tint/ast/call_statement.h
+++ b/src/tint/ast/call_statement.h
@@ -29,8 +29,8 @@
     /// @param src the source of this node for the statement
     /// @param call the function
     CallStatement(ProgramID pid, NodeID nid, const Source& src, const CallExpression* call);
-    /// Move constructor
-    CallStatement(CallStatement&&);
+
+    /// Destructor
     ~CallStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/case_selector.cc b/src/tint/ast/case_selector.cc
index 7419fe5..a7f1446 100644
--- a/src/tint/ast/case_selector.cc
+++ b/src/tint/ast/case_selector.cc
@@ -25,8 +25,6 @@
 CaseSelector::CaseSelector(ProgramID pid, NodeID nid, const Source& src, const Expression* e)
     : Base(pid, nid, src), expr(e) {}
 
-CaseSelector::CaseSelector(CaseSelector&&) = default;
-
 CaseSelector::~CaseSelector() = default;
 
 const CaseSelector* CaseSelector::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/case_selector.h b/src/tint/ast/case_selector.h
index b4c3ca7..f2bc1b3 100644
--- a/src/tint/ast/case_selector.h
+++ b/src/tint/ast/case_selector.h
@@ -31,8 +31,8 @@
     /// @param src the source of this node
     /// @param expr the selector expression, |nullptr| for a `default` selector
     CaseSelector(ProgramID pid, NodeID nid, const Source& src, const Expression* expr = nullptr);
-    /// Move constructor
-    CaseSelector(CaseSelector&&);
+
+    /// Destructor
     ~CaseSelector() override;
 
     /// @returns true if this is a default statement
diff --git a/src/tint/ast/case_statement.cc b/src/tint/ast/case_statement.cc
index 7b2e798..b7e6970 100644
--- a/src/tint/ast/case_statement.cc
+++ b/src/tint/ast/case_statement.cc
@@ -37,8 +37,6 @@
     }
 }
 
-CaseStatement::CaseStatement(CaseStatement&&) = default;
-
 CaseStatement::~CaseStatement() = default;
 
 bool CaseStatement::ContainsDefault() const {
diff --git a/src/tint/ast/case_statement.h b/src/tint/ast/case_statement.h
index acd502a..a06c720 100644
--- a/src/tint/ast/case_statement.h
+++ b/src/tint/ast/case_statement.h
@@ -36,8 +36,8 @@
                   const Source& src,
                   utils::VectorRef<const CaseSelector*> selectors,
                   const BlockStatement* body);
-    /// Move constructor
-    CaseStatement(CaseStatement&&);
+
+    /// Destructor
     ~CaseStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/compound_assignment_statement.cc b/src/tint/ast/compound_assignment_statement.cc
index f752862..fc9d300 100644
--- a/src/tint/ast/compound_assignment_statement.cc
+++ b/src/tint/ast/compound_assignment_statement.cc
@@ -33,8 +33,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id);
 }
 
-CompoundAssignmentStatement::CompoundAssignmentStatement(CompoundAssignmentStatement&&) = default;
-
 CompoundAssignmentStatement::~CompoundAssignmentStatement() = default;
 
 const CompoundAssignmentStatement* CompoundAssignmentStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/compound_assignment_statement.h b/src/tint/ast/compound_assignment_statement.h
index 9fbd22c..bb1f34f 100644
--- a/src/tint/ast/compound_assignment_statement.h
+++ b/src/tint/ast/compound_assignment_statement.h
@@ -37,8 +37,8 @@
                                 const Expression* lhs,
                                 const Expression* rhs,
                                 BinaryOp op);
-    /// Move constructor
-    CompoundAssignmentStatement(CompoundAssignmentStatement&&);
+
+    /// Destructor
     ~CompoundAssignmentStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/const.cc b/src/tint/ast/const.cc
index 60f9bef..967f5f1 100644
--- a/src/tint/ast/const.cc
+++ b/src/tint/ast/const.cc
@@ -33,8 +33,6 @@
     TINT_ASSERT(AST, init != nullptr);
 }
 
-Const::Const(Const&&) = default;
-
 Const::~Const() = default;
 
 const char* Const::Kind() const {
diff --git a/src/tint/ast/const.h b/src/tint/ast/const.h
index 75dc767..95727ce 100644
--- a/src/tint/ast/const.h
+++ b/src/tint/ast/const.h
@@ -48,9 +48,6 @@
           const Expression* initializer,
           utils::VectorRef<const Attribute*> attributes);
 
-    /// Move constructor
-    Const(Const&&);
-
     /// Destructor
     ~Const() override;
 
diff --git a/src/tint/ast/const_assert.cc b/src/tint/ast/const_assert.cc
index 5f9978d..0dcdc16 100644
--- a/src/tint/ast/const_assert.cc
+++ b/src/tint/ast/const_assert.cc
@@ -26,8 +26,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, cond, program_id);
 }
 
-ConstAssert::ConstAssert(ConstAssert&&) = default;
-
 ConstAssert::~ConstAssert() = default;
 
 const ConstAssert* ConstAssert::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/const_assert.h b/src/tint/ast/const_assert.h
index 8f0aae5..6774f5e 100644
--- a/src/tint/ast/const_assert.h
+++ b/src/tint/ast/const_assert.h
@@ -30,9 +30,6 @@
     /// @param condition the assertion condition
     ConstAssert(ProgramID pid, NodeID nid, const Source& source, const Expression* condition);
 
-    /// Move constructor
-    ConstAssert(ConstAssert&&);
-
     /// Destructor
     ~ConstAssert() override;
 
diff --git a/src/tint/ast/continue_statement.cc b/src/tint/ast/continue_statement.cc
index 53bd6a9..a7868e8 100644
--- a/src/tint/ast/continue_statement.cc
+++ b/src/tint/ast/continue_statement.cc
@@ -23,8 +23,6 @@
 ContinueStatement::ContinueStatement(ProgramID pid, NodeID nid, const Source& src)
     : Base(pid, nid, src) {}
 
-ContinueStatement::ContinueStatement(ContinueStatement&&) = default;
-
 ContinueStatement::~ContinueStatement() = default;
 
 const ContinueStatement* ContinueStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/continue_statement.h b/src/tint/ast/continue_statement.h
index 09b8254..10af08b 100644
--- a/src/tint/ast/continue_statement.h
+++ b/src/tint/ast/continue_statement.h
@@ -27,8 +27,8 @@
     /// @param nid the unique node identifier
     /// @param src the source of this node
     ContinueStatement(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    ContinueStatement(ContinueStatement&&);
+
+    /// Destructor
     ~ContinueStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/diagnostic_control.cc b/src/tint/ast/diagnostic_control.cc
index e269446..a2bdd46 100644
--- a/src/tint/ast/diagnostic_control.cc
+++ b/src/tint/ast/diagnostic_control.cc
@@ -22,6 +22,8 @@
 
 namespace tint::ast {
 
+DiagnosticControl::DiagnosticControl() = default;
+
 DiagnosticControl::DiagnosticControl(builtin::DiagnosticSeverity sev, const Identifier* rule)
     : severity(sev), rule_name(rule) {
     TINT_ASSERT(AST, rule != nullptr);
@@ -31,4 +33,6 @@
     }
 }
 
+DiagnosticControl::DiagnosticControl(DiagnosticControl&&) = default;
+
 }  // namespace tint::ast
diff --git a/src/tint/ast/diagnostic_control.h b/src/tint/ast/diagnostic_control.h
index a0f5a9d..f99d002 100644
--- a/src/tint/ast/diagnostic_control.h
+++ b/src/tint/ast/diagnostic_control.h
@@ -32,13 +32,16 @@
 struct DiagnosticControl {
   public:
     /// Default constructor.
-    DiagnosticControl() {}
+    DiagnosticControl();
 
     /// Constructor
     /// @param sev the diagnostic severity
     /// @param rule the diagnostic rule name
     DiagnosticControl(builtin::DiagnosticSeverity sev, const Identifier* rule);
 
+    /// Move constructor
+    DiagnosticControl(DiagnosticControl&&);
+
     /// The diagnostic severity control.
     builtin::DiagnosticSeverity severity;
 
diff --git a/src/tint/ast/diagnostic_directive.cc b/src/tint/ast/diagnostic_directive.cc
index 42e1169..f0ef041 100644
--- a/src/tint/ast/diagnostic_directive.cc
+++ b/src/tint/ast/diagnostic_directive.cc
@@ -26,8 +26,6 @@
                                          DiagnosticControl&& dc)
     : Base(pid, nid, src), control(std::move(dc)) {}
 
-DiagnosticDirective::DiagnosticDirective(DiagnosticDirective&&) = default;
-
 DiagnosticDirective::~DiagnosticDirective() = default;
 
 const DiagnosticDirective* DiagnosticDirective::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/diagnostic_directive.h b/src/tint/ast/diagnostic_directive.h
index ee84381..979f32c 100644
--- a/src/tint/ast/diagnostic_directive.h
+++ b/src/tint/ast/diagnostic_directive.h
@@ -38,9 +38,6 @@
     /// @param dc the diagnostic control
     DiagnosticDirective(ProgramID pid, NodeID nid, const Source& src, DiagnosticControl&& dc);
 
-    /// Move constructor
-    DiagnosticDirective(DiagnosticDirective&&);
-
     /// Destructor
     ~DiagnosticDirective() override;
 
diff --git a/src/tint/ast/discard_statement.cc b/src/tint/ast/discard_statement.cc
index fc9e75b..e76d01e 100644
--- a/src/tint/ast/discard_statement.cc
+++ b/src/tint/ast/discard_statement.cc
@@ -23,8 +23,6 @@
 DiscardStatement::DiscardStatement(ProgramID pid, NodeID nid, const Source& src)
     : Base(pid, nid, src) {}
 
-DiscardStatement::DiscardStatement(DiscardStatement&&) = default;
-
 DiscardStatement::~DiscardStatement() = default;
 
 const DiscardStatement* DiscardStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/discard_statement.h b/src/tint/ast/discard_statement.h
index 272cc2d..a764683 100644
--- a/src/tint/ast/discard_statement.h
+++ b/src/tint/ast/discard_statement.h
@@ -27,8 +27,8 @@
     /// @param nid the unique node identifier
     /// @param src the source of this node
     DiscardStatement(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    DiscardStatement(DiscardStatement&&);
+
+    /// Destructor
     ~DiscardStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/enable.cc b/src/tint/ast/enable.cc
index bb2b3b6..b449444 100644
--- a/src/tint/ast/enable.cc
+++ b/src/tint/ast/enable.cc
@@ -26,8 +26,6 @@
                utils::VectorRef<const Extension*> exts)
     : Base(pid, nid, src), extensions(std::move(exts)) {}
 
-Enable::Enable(Enable&&) = default;
-
 Enable::~Enable() = default;
 
 bool Enable::HasExtension(builtin::Extension ext) const {
diff --git a/src/tint/ast/enable.h b/src/tint/ast/enable.h
index d87d12f..53f0218 100644
--- a/src/tint/ast/enable.h
+++ b/src/tint/ast/enable.h
@@ -36,9 +36,8 @@
     /// @param src the source of this node
     /// @param exts the extensions being enabled by this directive
     Enable(ProgramID pid, NodeID nid, const Source& src, utils::VectorRef<const Extension*> exts);
-    /// Move constructor
-    Enable(Enable&&);
 
+    /// Destructor
     ~Enable() override;
 
     /// @param ext the extension to search for
diff --git a/src/tint/ast/expression.cc b/src/tint/ast/expression.cc
index b9482fa..21989d0 100644
--- a/src/tint/ast/expression.cc
+++ b/src/tint/ast/expression.cc
@@ -20,8 +20,6 @@
 
 Expression::Expression(ProgramID pid, NodeID nid, const Source& src) : Base(pid, nid, src) {}
 
-Expression::Expression(Expression&&) = default;
-
 Expression::~Expression() = default;
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/expression.h b/src/tint/ast/expression.h
index d851cb1..e4a94cb 100644
--- a/src/tint/ast/expression.h
+++ b/src/tint/ast/expression.h
@@ -33,8 +33,6 @@
     /// @param nid the unique node identifier
     /// @param src the source of this node
     Expression(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    Expression(Expression&&);
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/extension.cc b/src/tint/ast/extension.cc
index a45c704..bfb8ffd 100644
--- a/src/tint/ast/extension.cc
+++ b/src/tint/ast/extension.cc
@@ -26,8 +26,6 @@
 Extension::Extension(ProgramID pid, NodeID nid, const Source& src, builtin::Extension ext)
     : Base(pid, nid, src), name(ext) {}
 
-Extension::Extension(Extension&&) = default;
-
 Extension::~Extension() = default;
 
 const Extension* Extension::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/extension.h b/src/tint/ast/extension.h
index 93f3baa..4988edb 100644
--- a/src/tint/ast/extension.h
+++ b/src/tint/ast/extension.h
@@ -32,9 +32,8 @@
     /// @param src the source of this node
     /// @param ext the extension
     Extension(ProgramID pid, NodeID nid, const Source& src, builtin::Extension ext);
-    /// Move constructor
-    Extension(Extension&&);
 
+    /// Destructor
     ~Extension() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/for_loop_statement.cc b/src/tint/ast/for_loop_statement.cc
index b2a5470..faec878 100644
--- a/src/tint/ast/for_loop_statement.cc
+++ b/src/tint/ast/for_loop_statement.cc
@@ -48,8 +48,6 @@
     }
 }
 
-ForLoopStatement::ForLoopStatement(ForLoopStatement&&) = default;
-
 ForLoopStatement::~ForLoopStatement() = default;
 
 const ForLoopStatement* ForLoopStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/for_loop_statement.h b/src/tint/ast/for_loop_statement.h
index 6063dda..00fcf1d 100644
--- a/src/tint/ast/for_loop_statement.h
+++ b/src/tint/ast/for_loop_statement.h
@@ -41,8 +41,8 @@
                      const Statement* continuing,
                      const BlockStatement* body,
                      utils::VectorRef<const ast::Attribute*> attributes);
-    /// Move constructor
-    ForLoopStatement(ForLoopStatement&&);
+
+    /// Destructor
     ~ForLoopStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/function.cc b/src/tint/ast/function.cc
index 3c5446c..59a4571 100644
--- a/src/tint/ast/function.cc
+++ b/src/tint/ast/function.cc
@@ -57,8 +57,6 @@
     }
 }
 
-Function::Function(Function&&) = default;
-
 Function::~Function() = default;
 
 PipelineStage Function::PipelineStage() const {
diff --git a/src/tint/ast/function.h b/src/tint/ast/function.h
index dc8cc70..8fa009f 100644
--- a/src/tint/ast/function.h
+++ b/src/tint/ast/function.h
@@ -59,9 +59,8 @@
              const BlockStatement* body,
              utils::VectorRef<const Attribute*> attributes,
              utils::VectorRef<const Attribute*> return_type_attributes);
-    /// Move constructor
-    Function(Function&&);
 
+    /// Destructor
     ~Function() override;
 
     /// @returns the functions pipeline stage or None if not set
diff --git a/src/tint/ast/identifier.cc b/src/tint/ast/identifier.cc
index 8b8c3f5..49b9c86 100644
--- a/src/tint/ast/identifier.cc
+++ b/src/tint/ast/identifier.cc
@@ -26,8 +26,6 @@
     TINT_ASSERT(AST, symbol.IsValid());
 }
 
-Identifier::Identifier(Identifier&&) = default;
-
 Identifier::~Identifier() = default;
 
 const Identifier* Identifier::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/identifier.h b/src/tint/ast/identifier.h
index 7160173..df138e9 100644
--- a/src/tint/ast/identifier.h
+++ b/src/tint/ast/identifier.h
@@ -28,8 +28,8 @@
     /// @param src the source of this node
     /// @param sym the symbol for the identifier
     Identifier(ProgramID pid, NodeID nid, const Source& src, Symbol sym);
-    /// Move constructor
-    Identifier(Identifier&&);
+
+    /// Destructor
     ~Identifier() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/identifier_expression.cc b/src/tint/ast/identifier_expression.cc
index ca4e0cb..d3ee29b 100644
--- a/src/tint/ast/identifier_expression.cc
+++ b/src/tint/ast/identifier_expression.cc
@@ -29,8 +29,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL(AST, identifier, program_id);
 }
 
-IdentifierExpression::IdentifierExpression(IdentifierExpression&&) = default;
-
 IdentifierExpression::~IdentifierExpression() = default;
 
 const IdentifierExpression* IdentifierExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/identifier_expression.h b/src/tint/ast/identifier_expression.h
index 475249c..c2200bd 100644
--- a/src/tint/ast/identifier_expression.h
+++ b/src/tint/ast/identifier_expression.h
@@ -36,8 +36,8 @@
                          NodeID nid,
                          const Source& src,
                          const Identifier* identifier);
-    /// Move constructor
-    IdentifierExpression(IdentifierExpression&&);
+
+    /// Destructor
     ~IdentifierExpression() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/if_statement.cc b/src/tint/ast/if_statement.cc
index 6878c9f..40748c5 100644
--- a/src/tint/ast/if_statement.cc
+++ b/src/tint/ast/if_statement.cc
@@ -46,8 +46,6 @@
     }
 }
 
-IfStatement::IfStatement(IfStatement&&) = default;
-
 IfStatement::~IfStatement() = default;
 
 const IfStatement* IfStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/if_statement.h b/src/tint/ast/if_statement.h
index 8f291a8..255ce2f 100644
--- a/src/tint/ast/if_statement.h
+++ b/src/tint/ast/if_statement.h
@@ -40,8 +40,8 @@
                 const BlockStatement* body,
                 const Statement* else_stmt,
                 utils::VectorRef<const Attribute*> attributes);
-    /// Move constructor
-    IfStatement(IfStatement&&);
+
+    /// Destructor
     ~IfStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/increment_decrement_statement.cc b/src/tint/ast/increment_decrement_statement.cc
index 5b10e5f..f37242e 100644
--- a/src/tint/ast/increment_decrement_statement.cc
+++ b/src/tint/ast/increment_decrement_statement.cc
@@ -29,8 +29,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id);
 }
 
-IncrementDecrementStatement::IncrementDecrementStatement(IncrementDecrementStatement&&) = default;
-
 IncrementDecrementStatement::~IncrementDecrementStatement() = default;
 
 const IncrementDecrementStatement* IncrementDecrementStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/increment_decrement_statement.h b/src/tint/ast/increment_decrement_statement.h
index ec9923a..9046e46 100644
--- a/src/tint/ast/increment_decrement_statement.h
+++ b/src/tint/ast/increment_decrement_statement.h
@@ -34,8 +34,8 @@
                                 const Source& src,
                                 const Expression* lhs,
                                 bool inc);
-    /// Move constructor
-    IncrementDecrementStatement(IncrementDecrementStatement&&);
+
+    /// Destructor
     ~IncrementDecrementStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/index_accessor_expression.cc b/src/tint/ast/index_accessor_expression.cc
index e96c562..5462c6e 100644
--- a/src/tint/ast/index_accessor_expression.cc
+++ b/src/tint/ast/index_accessor_expression.cc
@@ -30,8 +30,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, idx, program_id);
 }
 
-IndexAccessorExpression::IndexAccessorExpression(IndexAccessorExpression&&) = default;
-
 IndexAccessorExpression::~IndexAccessorExpression() = default;
 
 const IndexAccessorExpression* IndexAccessorExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/index_accessor_expression.h b/src/tint/ast/index_accessor_expression.h
index 99db4db..09dc975 100644
--- a/src/tint/ast/index_accessor_expression.h
+++ b/src/tint/ast/index_accessor_expression.h
@@ -33,8 +33,8 @@
                             const Source& source,
                             const Expression* obj,
                             const Expression* idx);
-    /// Move constructor
-    IndexAccessorExpression(IndexAccessorExpression&&);
+
+    /// Destructor
     ~IndexAccessorExpression() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/let.cc b/src/tint/ast/let.cc
index e899f56..6f63ceb 100644
--- a/src/tint/ast/let.cc
+++ b/src/tint/ast/let.cc
@@ -33,8 +33,6 @@
     TINT_ASSERT(AST, init != nullptr);
 }
 
-Let::Let(Let&&) = default;
-
 Let::~Let() = default;
 
 const char* Let::Kind() const {
diff --git a/src/tint/ast/let.h b/src/tint/ast/let.h
index 7ff84b2..127c3d4 100644
--- a/src/tint/ast/let.h
+++ b/src/tint/ast/let.h
@@ -45,9 +45,6 @@
         const Expression* initializer,
         utils::VectorRef<const Attribute*> attributes);
 
-    /// Move constructor
-    Let(Let&&);
-
     /// Destructor
     ~Let() override;
 
diff --git a/src/tint/ast/loop_statement.cc b/src/tint/ast/loop_statement.cc
index b7e7a1b..731dee7 100644
--- a/src/tint/ast/loop_statement.cc
+++ b/src/tint/ast/loop_statement.cc
@@ -31,8 +31,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id);
 }
 
-LoopStatement::LoopStatement(LoopStatement&&) = default;
-
 LoopStatement::~LoopStatement() = default;
 
 const LoopStatement* LoopStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/loop_statement.h b/src/tint/ast/loop_statement.h
index d4b24cf..c1e5cc0 100644
--- a/src/tint/ast/loop_statement.h
+++ b/src/tint/ast/loop_statement.h
@@ -33,8 +33,8 @@
                   const Source& source,
                   const BlockStatement* body,
                   const BlockStatement* continuing);
-    /// Move constructor
-    LoopStatement(LoopStatement&&);
+
+    /// Destructor
     ~LoopStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/member_accessor_expression.cc b/src/tint/ast/member_accessor_expression.cc
index 3886502..4b062fb 100644
--- a/src/tint/ast/member_accessor_expression.cc
+++ b/src/tint/ast/member_accessor_expression.cc
@@ -35,8 +35,6 @@
     }
 }
 
-MemberAccessorExpression::MemberAccessorExpression(MemberAccessorExpression&&) = default;
-
 MemberAccessorExpression::~MemberAccessorExpression() = default;
 
 const MemberAccessorExpression* MemberAccessorExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/member_accessor_expression.h b/src/tint/ast/member_accessor_expression.h
index 6833f93..95fbbd2 100644
--- a/src/tint/ast/member_accessor_expression.h
+++ b/src/tint/ast/member_accessor_expression.h
@@ -35,8 +35,8 @@
                              const Source& source,
                              const Expression* object,
                              const Identifier* member);
-    /// Move constructor
-    MemberAccessorExpression(MemberAccessorExpression&&);
+
+    /// Destructor
     ~MemberAccessorExpression() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/node.cc b/src/tint/ast/node.cc
index ce3a71d..772c788 100644
--- a/src/tint/ast/node.cc
+++ b/src/tint/ast/node.cc
@@ -21,8 +21,6 @@
 Node::Node(ProgramID pid, NodeID nid, const Source& src)
     : program_id(pid), node_id(nid), source(src) {}
 
-Node::Node(Node&&) = default;
-
 Node::~Node() = default;
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/node.h b/src/tint/ast/node.h
index 6eaa1e9..afea2e8 100644
--- a/src/tint/ast/node.h
+++ b/src/tint/ast/node.h
@@ -42,11 +42,10 @@
     /// @param nid the unique node identifier
     /// @param src the input source for the node
     Node(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    Node(Node&&);
 
   private:
     Node(const Node&) = delete;
+    Node(Node&&) = delete;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/override.cc b/src/tint/ast/override.cc
index 79f0772..ff1836e 100644
--- a/src/tint/ast/override.cc
+++ b/src/tint/ast/override.cc
@@ -31,8 +31,6 @@
                    utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src, n, ty, init, std::move(attrs)) {}
 
-Override::Override(Override&&) = default;
-
 Override::~Override() = default;
 
 const char* Override::Kind() const {
diff --git a/src/tint/ast/override.h b/src/tint/ast/override.h
index 531d147..b824ed3 100644
--- a/src/tint/ast/override.h
+++ b/src/tint/ast/override.h
@@ -48,9 +48,6 @@
              const Expression* initializer,
              utils::VectorRef<const Attribute*> attributes);
 
-    /// Move constructor
-    Override(Override&&);
-
     /// Destructor
     ~Override() override;
 
diff --git a/src/tint/ast/parameter.cc b/src/tint/ast/parameter.cc
index a2c6e5b..4a76e55 100644
--- a/src/tint/ast/parameter.cc
+++ b/src/tint/ast/parameter.cc
@@ -30,8 +30,6 @@
                      utils::VectorRef<const Attribute*> attrs)
     : Base(pid, nid, src, n, ty, nullptr, std::move(attrs)) {}
 
-Parameter::Parameter(Parameter&&) = default;
-
 Parameter::~Parameter() = default;
 
 const char* Parameter::Kind() const {
diff --git a/src/tint/ast/parameter.h b/src/tint/ast/parameter.h
index 7ea49ae..b056afa 100644
--- a/src/tint/ast/parameter.h
+++ b/src/tint/ast/parameter.h
@@ -47,9 +47,6 @@
               Type type,
               utils::VectorRef<const Attribute*> attributes);
 
-    /// Move constructor
-    Parameter(Parameter&&);
-
     /// Destructor
     ~Parameter() override;
 
diff --git a/src/tint/ast/phony_expression.cc b/src/tint/ast/phony_expression.cc
index 6bce1bf..f2fca96 100644
--- a/src/tint/ast/phony_expression.cc
+++ b/src/tint/ast/phony_expression.cc
@@ -23,8 +23,6 @@
 PhonyExpression::PhonyExpression(ProgramID pid, NodeID nid, const Source& src)
     : Base(pid, nid, src) {}
 
-PhonyExpression::PhonyExpression(PhonyExpression&&) = default;
-
 PhonyExpression::~PhonyExpression() = default;
 
 const PhonyExpression* PhonyExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/phony_expression.h b/src/tint/ast/phony_expression.h
index d429a51..7f59c37 100644
--- a/src/tint/ast/phony_expression.h
+++ b/src/tint/ast/phony_expression.h
@@ -28,8 +28,8 @@
     /// @param nid the unique node identifier
     /// @param src the source of this node
     PhonyExpression(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    PhonyExpression(PhonyExpression&&);
+
+    /// Destructor
     ~PhonyExpression() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/return_statement.cc b/src/tint/ast/return_statement.cc
index 459bb72..4ac7313 100644
--- a/src/tint/ast/return_statement.cc
+++ b/src/tint/ast/return_statement.cc
@@ -31,8 +31,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, value, program_id);
 }
 
-ReturnStatement::ReturnStatement(ReturnStatement&&) = default;
-
 ReturnStatement::~ReturnStatement() = default;
 
 const ReturnStatement* ReturnStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/return_statement.h b/src/tint/ast/return_statement.h
index 571a738..7eae780 100644
--- a/src/tint/ast/return_statement.h
+++ b/src/tint/ast/return_statement.h
@@ -35,8 +35,8 @@
     /// @param src the source of this node
     /// @param value the return value
     ReturnStatement(ProgramID pid, NodeID nid, const Source& src, const Expression* value);
-    /// Move constructor
-    ReturnStatement(ReturnStatement&&);
+
+    /// Destructor
     ~ReturnStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/statement.cc b/src/tint/ast/statement.cc
index 7b9ca5d..42f01c4 100644
--- a/src/tint/ast/statement.cc
+++ b/src/tint/ast/statement.cc
@@ -31,8 +31,6 @@
 
 Statement::Statement(ProgramID pid, NodeID nid, const Source& src) : Base(pid, nid, src) {}
 
-Statement::Statement(Statement&&) = default;
-
 Statement::~Statement() = default;
 
 const char* Statement::Name() const {
diff --git a/src/tint/ast/statement.h b/src/tint/ast/statement.h
index 616e348..fa434cc 100644
--- a/src/tint/ast/statement.h
+++ b/src/tint/ast/statement.h
@@ -35,8 +35,6 @@
     /// @param nid the unique node identifier
     /// @param src the source of the expression
     Statement(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    Statement(Statement&&);
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/struct.cc b/src/tint/ast/struct.cc
index 96daeff..ebd33e2 100644
--- a/src/tint/ast/struct.cc
+++ b/src/tint/ast/struct.cc
@@ -39,8 +39,6 @@
     }
 }
 
-Struct::Struct(Struct&&) = default;
-
 Struct::~Struct() = default;
 
 const Struct* Struct::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/struct.h b/src/tint/ast/struct.h
index 36ad080..d7c2281 100644
--- a/src/tint/ast/struct.h
+++ b/src/tint/ast/struct.h
@@ -41,9 +41,8 @@
            const Identifier* name,
            utils::VectorRef<const StructMember*> members,
            utils::VectorRef<const Attribute*> attributes);
-    /// Move constructor
-    Struct(Struct&&);
 
+    /// Destructor
     ~Struct() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/struct_member.cc b/src/tint/ast/struct_member.cc
index 10f278d..acc20e5 100644
--- a/src/tint/ast/struct_member.cc
+++ b/src/tint/ast/struct_member.cc
@@ -39,8 +39,6 @@
     }
 }
 
-StructMember::StructMember(StructMember&&) = default;
-
 StructMember::~StructMember() = default;
 
 const StructMember* StructMember::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/struct_member.h b/src/tint/ast/struct_member.h
index fba504a..27e5b0c 100644
--- a/src/tint/ast/struct_member.h
+++ b/src/tint/ast/struct_member.h
@@ -43,9 +43,8 @@
                  const Identifier* name,
                  Type type,
                  utils::VectorRef<const Attribute*> attributes);
-    /// Move constructor
-    StructMember(StructMember&&);
 
+    /// Destructor
     ~StructMember() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/switch_statement.cc b/src/tint/ast/switch_statement.cc
index 0445e48..4e5d95d 100644
--- a/src/tint/ast/switch_statement.cc
+++ b/src/tint/ast/switch_statement.cc
@@ -41,8 +41,6 @@
     }
 }
 
-SwitchStatement::SwitchStatement(SwitchStatement&&) = default;
-
 SwitchStatement::~SwitchStatement() = default;
 
 const SwitchStatement* SwitchStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/switch_statement.h b/src/tint/ast/switch_statement.h
index e918535..354b6ad 100644
--- a/src/tint/ast/switch_statement.h
+++ b/src/tint/ast/switch_statement.h
@@ -36,8 +36,8 @@
                     const Expression* condition,
                     utils::VectorRef<const CaseStatement*> body,
                     utils::VectorRef<const Attribute*> attributes);
-    /// Move constructor
-    SwitchStatement(SwitchStatement&&);
+
+    /// Destructor
     ~SwitchStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/templated_identifier.cc b/src/tint/ast/templated_identifier.cc
index 3a8b545..98274b6 100644
--- a/src/tint/ast/templated_identifier.cc
+++ b/src/tint/ast/templated_identifier.cc
@@ -38,8 +38,6 @@
     }
 }
 
-TemplatedIdentifier::TemplatedIdentifier(TemplatedIdentifier&&) = default;
-
 TemplatedIdentifier::~TemplatedIdentifier() = default;
 
 const TemplatedIdentifier* TemplatedIdentifier::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/templated_identifier.h b/src/tint/ast/templated_identifier.h
index 9f4b17b..74ddb43 100644
--- a/src/tint/ast/templated_identifier.h
+++ b/src/tint/ast/templated_identifier.h
@@ -41,8 +41,8 @@
                         const Symbol& sym,
                         utils::VectorRef<const Expression*> args,
                         utils::VectorRef<const Attribute*> attrs);
-    /// Move constructor
-    TemplatedIdentifier(TemplatedIdentifier&&);
+
+    /// Destructor
     ~TemplatedIdentifier() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext` `ctx`.
diff --git a/src/tint/ast/type_decl.cc b/src/tint/ast/type_decl.cc
index 2319b19..206bf45 100644
--- a/src/tint/ast/type_decl.cc
+++ b/src/tint/ast/type_decl.cc
@@ -28,8 +28,6 @@
     }
 }
 
-TypeDecl::TypeDecl(TypeDecl&&) = default;
-
 TypeDecl::~TypeDecl() = default;
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/type_decl.h b/src/tint/ast/type_decl.h
index fb8958f..8ec0fe4 100644
--- a/src/tint/ast/type_decl.h
+++ b/src/tint/ast/type_decl.h
@@ -33,9 +33,8 @@
     /// @param src the source of this node for the import statement
     /// @param name The name of the type
     TypeDecl(ProgramID pid, NodeID nid, const Source& src, const Identifier* name);
-    /// Move constructor
-    TypeDecl(TypeDecl&&);
 
+    /// Destructor
     ~TypeDecl() override;
 
     /// The name of the type declaration
diff --git a/src/tint/ast/unary_op_expression.cc b/src/tint/ast/unary_op_expression.cc
index eec69a0..104d74a 100644
--- a/src/tint/ast/unary_op_expression.cc
+++ b/src/tint/ast/unary_op_expression.cc
@@ -30,8 +30,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id);
 }
 
-UnaryOpExpression::UnaryOpExpression(UnaryOpExpression&&) = default;
-
 UnaryOpExpression::~UnaryOpExpression() = default;
 
 const UnaryOpExpression* UnaryOpExpression::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/unary_op_expression.h b/src/tint/ast/unary_op_expression.h
index a5c2be9..3639447 100644
--- a/src/tint/ast/unary_op_expression.h
+++ b/src/tint/ast/unary_op_expression.h
@@ -34,8 +34,8 @@
                       const Source& source,
                       UnaryOp op,
                       const Expression* expr);
-    /// Move constructor
-    UnaryOpExpression(UnaryOpExpression&&);
+
+    /// Destructor
     ~UnaryOpExpression() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/var.cc b/src/tint/ast/var.cc
index bccdbcc..fdd700a 100644
--- a/src/tint/ast/var.cc
+++ b/src/tint/ast/var.cc
@@ -33,8 +33,6 @@
       declared_address_space(address_space),
       declared_access(access) {}
 
-Var::Var(Var&&) = default;
-
 Var::~Var() = default;
 
 const char* Var::Kind() const {
diff --git a/src/tint/ast/var.h b/src/tint/ast/var.h
index 006d8de..6f08a6f 100644
--- a/src/tint/ast/var.h
+++ b/src/tint/ast/var.h
@@ -61,9 +61,6 @@
         const Expression* initializer,
         utils::VectorRef<const Attribute*> attributes);
 
-    /// Move constructor
-    Var(Var&&);
-
     /// Destructor
     ~Var() override;
 
diff --git a/src/tint/ast/variable.cc b/src/tint/ast/variable.cc
index 2aa60bf..4a4f756 100644
--- a/src/tint/ast/variable.cc
+++ b/src/tint/ast/variable.cc
@@ -36,8 +36,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, initializer, program_id);
 }
 
-Variable::Variable(Variable&&) = default;
-
 Variable::~Variable() = default;
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/variable.h b/src/tint/ast/variable.h
index 70e5116..2230ac3 100644
--- a/src/tint/ast/variable.h
+++ b/src/tint/ast/variable.h
@@ -58,9 +58,6 @@
              const Expression* initializer,
              utils::VectorRef<const Attribute*> attributes);
 
-    /// Move constructor
-    Variable(Variable&&);
-
     /// Destructor
     ~Variable() override;
 
diff --git a/src/tint/ast/variable_decl_statement.cc b/src/tint/ast/variable_decl_statement.cc
index 79ee926..62c5797 100644
--- a/src/tint/ast/variable_decl_statement.cc
+++ b/src/tint/ast/variable_decl_statement.cc
@@ -29,8 +29,6 @@
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, variable, program_id);
 }
 
-VariableDeclStatement::VariableDeclStatement(VariableDeclStatement&&) = default;
-
 VariableDeclStatement::~VariableDeclStatement() = default;
 
 const VariableDeclStatement* VariableDeclStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/variable_decl_statement.h b/src/tint/ast/variable_decl_statement.h
index b71d0b2..99a9a5d 100644
--- a/src/tint/ast/variable_decl_statement.h
+++ b/src/tint/ast/variable_decl_statement.h
@@ -32,8 +32,8 @@
                           NodeID nid,
                           const Source& source,
                           const Variable* variable);
-    /// Move constructor
-    VariableDeclStatement(VariableDeclStatement&&);
+
+    /// Destructor
     ~VariableDeclStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/ast/while_statement.cc b/src/tint/ast/while_statement.cc
index 47cf2bb..713a0f6 100644
--- a/src/tint/ast/while_statement.cc
+++ b/src/tint/ast/while_statement.cc
@@ -40,8 +40,6 @@
     }
 }
 
-WhileStatement::WhileStatement(WhileStatement&&) = default;
-
 WhileStatement::~WhileStatement() = default;
 
 const WhileStatement* WhileStatement::Clone(CloneContext* ctx) const {
diff --git a/src/tint/ast/while_statement.h b/src/tint/ast/while_statement.h
index b2b91dc..360610a 100644
--- a/src/tint/ast/while_statement.h
+++ b/src/tint/ast/while_statement.h
@@ -37,8 +37,8 @@
                    const Expression* condition,
                    const BlockStatement* body,
                    utils::VectorRef<const ast::Attribute*> attributes);
-    /// Move constructor
-    WhileStatement(WhileStatement&&);
+
+    /// Destructor
     ~WhileStatement() override;
 
     /// Clones this node and all transitive child nodes using the `CloneContext`
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index 898f0ac..45982f2 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -660,6 +660,11 @@
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(input_program);
+    gen_options.array_length_from_uniform.ubo_binding = tint::writer::BindingPoint{0, 30};
+    gen_options.array_length_from_uniform.bindpoint_to_size_index.emplace(
+        tint::writer::BindingPoint{0, 0}, 0);
+    gen_options.array_length_from_uniform.bindpoint_to_size_index.emplace(
+        tint::writer::BindingPoint{0, 1}, 1);
     auto result = tint::writer::msl::Generate(input_program, gen_options);
     if (!result.success) {
         tint::cmd::PrintWGSL(std::cerr, *program);
diff --git a/src/tint/diagnostic/diagnostic.h b/src/tint/diagnostic/diagnostic.h
index 82b9eb9..bebdd7c 100644
--- a/src/tint/diagnostic/diagnostic.h
+++ b/src/tint/diagnostic/diagnostic.h
@@ -141,7 +141,7 @@
     /// @param system the system raising the note message
     /// @param note_msg the note message
     /// @param source the source of the note diagnostic
-    void add_note(System system, const std::string& note_msg, const Source& source) {
+    void add_note(System system, std::string_view note_msg, const Source& source) {
         diag::Diagnostic note{};
         note.severity = diag::Severity::Note;
         note.system = system;
@@ -154,7 +154,7 @@
     /// @param system the system raising the warning message
     /// @param warning_msg the warning message
     /// @param source the source of the warning diagnostic
-    void add_warning(System system, const std::string& warning_msg, const Source& source) {
+    void add_warning(System system, std::string_view warning_msg, const Source& source) {
         diag::Diagnostic warning{};
         warning.severity = diag::Severity::Warning;
         warning.system = system;
@@ -166,11 +166,11 @@
     /// adds the error message without a source to the end of this list.
     /// @param system the system raising the error message
     /// @param err_msg the error message
-    void add_error(System system, std::string err_msg) {
+    void add_error(System system, std::string_view err_msg) {
         diag::Diagnostic error{};
         error.severity = diag::Severity::Error;
         error.system = system;
-        error.message = std::move(err_msg);
+        error.message = err_msg;
         add(std::move(error));
     }
 
@@ -178,12 +178,12 @@
     /// @param system the system raising the error message
     /// @param err_msg the error message
     /// @param source the source of the error diagnostic
-    void add_error(System system, std::string err_msg, const Source& source) {
+    void add_error(System system, std::string_view err_msg, const Source& source) {
         diag::Diagnostic error{};
         error.severity = diag::Severity::Error;
         error.system = system;
         error.source = source;
-        error.message = std::move(err_msg);
+        error.message = err_msg;
         add(std::move(error));
     }
 
@@ -193,13 +193,16 @@
     /// @param code the error code
     /// @param err_msg the error message
     /// @param source the source of the error diagnostic
-    void add_error(System system, const char* code, std::string err_msg, const Source& source) {
+    void add_error(System system,
+                   const char* code,
+                   std::string_view err_msg,
+                   const Source& source) {
         diag::Diagnostic error{};
         error.code = code;
         error.severity = diag::Severity::Error;
         error.system = system;
         error.source = source;
-        error.message = std::move(err_msg);
+        error.message = err_msg;
         add(std::move(error));
     }
 
@@ -209,7 +212,7 @@
     /// @param source the source of the internal compiler error
     /// @param file the Source::File owned by this diagnostic
     void add_ice(System system,
-                 const std::string& err_msg,
+                 std::string_view err_msg,
                  const Source& source,
                  std::shared_ptr<Source::File> file) {
         diag::Diagnostic ice{};
diff --git a/src/tint/fuzzers/transform_builder.h b/src/tint/fuzzers/transform_builder.h
index 787abb9..4dcf604 100644
--- a/src/tint/fuzzers/transform_builder.h
+++ b/src/tint/fuzzers/transform_builder.h
@@ -22,6 +22,7 @@
 
 #include "src/tint/fuzzers/data_builder.h"
 #include "src/tint/fuzzers/shuffle_transform.h"
+#include "src/tint/transform/binding_remapper.h"
 #include "src/tint/transform/robustness.h"
 
 namespace tint::fuzzers {
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 7ec46e7..ff0d721 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -3819,7 +3819,14 @@
 
     const auto builtin = GetBuiltin(op);
     if (builtin != builtin::Function::kNone) {
-        return MakeBuiltinCall(inst);
+        switch (builtin) {
+            case builtin::Function::kExtractBits:
+                return MakeExtractBitsCall(inst);
+            case builtin::Function::kInsertBits:
+                return MakeInsertBitsCall(inst);
+            default:
+                return MakeBuiltinCall(inst);
+        }
     }
 
     if (op == spv::Op::OpFMod) {
@@ -5274,6 +5281,42 @@
     return parser_impl_.RectifyForcedResultType(call, inst, first_operand_type);
 }
 
+TypedExpression FunctionEmitter::MakeExtractBitsCall(const spvtools::opt::Instruction& inst) {
+    const auto builtin = GetBuiltin(opcode(inst));
+    auto* name = builtin::str(builtin);
+    auto* ident = create<ast::Identifier>(Source{}, builder_.Symbols().Register(name));
+    auto e = MakeOperand(inst, 0);
+    auto offset = ToU32(MakeOperand(inst, 1));
+    auto count = ToU32(MakeOperand(inst, 2));
+    auto* call_expr = builder_.Call(ident, ExpressionList{e.expr, offset.expr, count.expr});
+    auto* result_type = parser_impl_.ConvertType(inst.type_id());
+    if (!result_type) {
+        Fail() << "internal error: no mapped type result of call: " << inst.PrettyPrint();
+        return {};
+    }
+    TypedExpression call{result_type, call_expr};
+    return parser_impl_.RectifyForcedResultType(call, inst, e.type);
+}
+
+TypedExpression FunctionEmitter::MakeInsertBitsCall(const spvtools::opt::Instruction& inst) {
+    const auto builtin = GetBuiltin(opcode(inst));
+    auto* name = builtin::str(builtin);
+    auto* ident = create<ast::Identifier>(Source{}, builder_.Symbols().Register(name));
+    auto e = MakeOperand(inst, 0);
+    auto newbits = MakeOperand(inst, 1);
+    auto offset = ToU32(MakeOperand(inst, 2));
+    auto count = ToU32(MakeOperand(inst, 3));
+    auto* call_expr =
+        builder_.Call(ident, ExpressionList{e.expr, newbits.expr, offset.expr, count.expr});
+    auto* result_type = parser_impl_.ConvertType(inst.type_id());
+    if (!result_type) {
+        Fail() << "internal error: no mapped type result of call: " << inst.PrettyPrint();
+        return {};
+    }
+    TypedExpression call{result_type, call_expr};
+    return parser_impl_.RectifyForcedResultType(call, inst, e.type);
+}
+
 TypedExpression FunctionEmitter::MakeSimpleSelect(const spvtools::opt::Instruction& inst) {
     auto condition = MakeOperand(inst, 0);
     auto true_value = MakeOperand(inst, 1);
@@ -6053,6 +6096,13 @@
     return {ty_.I32(), builder_.Call(builder_.ty.i32(), utils::Vector{value.expr})};
 }
 
+TypedExpression FunctionEmitter::ToU32(TypedExpression value) {
+    if (!value || value.type->Is<U32>()) {
+        return value;
+    }
+    return {ty_.U32(), builder_.Call(builder_.ty.u32(), utils::Vector{value.expr})};
+}
+
 TypedExpression FunctionEmitter::ToSignedIfUnsigned(TypedExpression value) {
     if (!value || !value.type->IsUnsignedScalarOrVector()) {
         return value;
diff --git a/src/tint/reader/spirv/function.h b/src/tint/reader/spirv/function.h
index 718e8e3..11fc92c 100644
--- a/src/tint/reader/spirv/function.h
+++ b/src/tint/reader/spirv/function.h
@@ -945,6 +945,12 @@
     /// @returns the value as an i32 value.
     TypedExpression ToI32(TypedExpression value);
 
+    /// Returns the given value as an u32. If it's already an u32 then simply returns @p value.
+    /// Otherwise, wrap the value in a TypeInitializer expression.
+    /// @param value the value to pass through or convert
+    /// @returns the value as an u32 value.
+    TypedExpression ToU32(TypedExpression value);
+
     /// Returns the given value as a signed integer type of the same shape if the value is unsigned
     /// scalar or vector, by wrapping the value with a TypeInitializer expression.  Returns the
     /// value itself if the value was already signed.
@@ -1035,6 +1041,18 @@
     /// @returns an expression
     TypedExpression MakeBuiltinCall(const spvtools::opt::Instruction& inst);
 
+    /// Returns an expression for a SPIR-V instruction that maps to the extractBits WGSL
+    /// builtin function call, with special handling to cast offset and count to u32, if needed.
+    /// @param inst the SPIR-V instruction
+    /// @returns an expression
+    TypedExpression MakeExtractBitsCall(const spvtools::opt::Instruction& inst);
+
+    /// Returns an expression for a SPIR-V instruction that maps to the insertBits WGSL
+    /// builtin function call, with special handling to cast offset and count to u32, if needed.
+    /// @param inst the SPIR-V instruction
+    /// @returns an expression
+    TypedExpression MakeInsertBitsCall(const spvtools::opt::Instruction& inst);
+
     /// Returns an expression for a SPIR-V OpArrayLength instruction.
     /// @param inst the SPIR-V instruction
     /// @returns an expression
diff --git a/src/tint/reader/spirv/function_bit_test.cc b/src/tint/reader/spirv/function_bit_test.cc
index 40f9162..2a12f01 100644
--- a/src/tint/reader/spirv/function_bit_test.cc
+++ b/src/tint/reader/spirv/function_bit_test.cc
@@ -33,6 +33,8 @@
 
   %uint_10 = OpConstant %uint 10
   %uint_20 = OpConstant %uint 20
+  %int_10 = OpConstant %int 10
+  %int_20 = OpConstant %int 20
   %int_30 = OpConstant %int 30
   %int_40 = OpConstant %int 40
   %float_50 = OpConstant %float 50
@@ -832,7 +834,7 @@
 
 TEST_F(SpvUnaryBitTest, InsertBits_Int) {
     const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitFieldInsert %v2int %int_30 %int_40 %uint_10 %uint_20
+     %1 = OpBitFieldInsert %int %int_30 %int_40 %uint_10 %uint_20
      OpReturn
      OpFunctionEnd
   )";
@@ -842,7 +844,23 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = insertBits(30i, 40i, 10u, 20u);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : i32 = insertBits(30i, 40i, 10u, 20u);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, InsertBits_Int_SignedOffsetAndCount) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldInsert %int %int_30 %int_40 %int_10 %int_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(body, HasSubstr("let x_1 : i32 = insertBits(30i, 40i, u32(10i), u32(20i));"))
+        << body;
 }
 
 TEST_F(SpvUnaryBitTest, InsertBits_IntVector) {
@@ -864,9 +882,9 @@
         << body;
 }
 
-TEST_F(SpvUnaryBitTest, InsertBits_Uint) {
+TEST_F(SpvUnaryBitTest, InsertBits_IntVector_SignedOffsetAndCount) {
     const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitFieldInsert %v2uint %uint_20 %uint_10 %uint_10 %uint_20
+     %1 = OpBitFieldInsert %v2int %v2int_30_40 %v2int_40_30 %int_10 %int_20
      OpReturn
      OpFunctionEnd
   )";
@@ -876,7 +894,42 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = insertBits(20u, 10u, 10u, 20u);")) << body;
+    EXPECT_THAT(
+        body,
+        HasSubstr(
+            R"(let x_1 : vec2<i32> = insertBits(vec2<i32>(30i, 40i), vec2<i32>(40i, 30i), u32(10i), u32(20i));)"))
+        << body;
+}
+
+TEST_F(SpvUnaryBitTest, InsertBits_Uint) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldInsert %uint %uint_20 %uint_10 %uint_10 %uint_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(body, HasSubstr("let x_1 : u32 = insertBits(20u, 10u, 10u, 20u);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, InsertBits_Uint_SignedOffsetAndCount) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldInsert %uint %uint_20 %uint_10 %int_10 %int_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(body, HasSubstr("let x_1 : u32 = insertBits(20u, 10u, u32(10i), u32(20i));"))
+        << body;
 }
 
 TEST_F(SpvUnaryBitTest, InsertBits_UintVector) {
@@ -898,9 +951,9 @@
         << body;
 }
 
-TEST_F(SpvUnaryBitTest, ExtractBits_Int) {
+TEST_F(SpvUnaryBitTest, InsertBits_UintVector_SignedOffsetAndCount) {
     const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitFieldSExtract %v2int %int_30 %uint_10 %uint_20
+     %1 = OpBitFieldInsert %v2uint %v2uint_10_20 %v2uint_20_10 %int_10 %int_20
      OpReturn
      OpFunctionEnd
   )";
@@ -910,7 +963,41 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = extractBits(30i, 10u, 20u);")) << body;
+    EXPECT_THAT(
+        body,
+        HasSubstr(
+            R"(let x_1 : vec2<u32> = insertBits(vec2<u32>(10u, 20u), vec2<u32>(20u, 10u), u32(10i), u32(20i));)"))
+        << body;
+}
+
+TEST_F(SpvUnaryBitTest, ExtractBits_Int) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldSExtract %int %int_30 %uint_10 %uint_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(body, HasSubstr("let x_1 : i32 = extractBits(30i, 10u, 20u);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, ExtractBits_Int_SignedOffsetAndCount) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldSExtract %int %int_30 %int_10 %int_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(body, HasSubstr("let x_1 : i32 = extractBits(30i, u32(10i), u32(20i));")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, ExtractBits_IntVector) {
@@ -930,9 +1017,9 @@
         << body;
 }
 
-TEST_F(SpvUnaryBitTest, ExtractBits_Uint) {
+TEST_F(SpvUnaryBitTest, ExtractBits_IntVector_SignedOffsetAndCount) {
     const auto assembly = BitTestPreamble() + R"(
-     %1 = OpBitFieldUExtract %v2uint %uint_20 %uint_10 %uint_20
+     %1 = OpBitFieldSExtract %v2int %v2int_30_40 %int_10 %int_20
      OpReturn
      OpFunctionEnd
   )";
@@ -942,7 +1029,40 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = extractBits(20u, 10u, 20u);")) << body;
+    EXPECT_THAT(
+        body,
+        HasSubstr("let x_1 : vec2<i32> = extractBits(vec2<i32>(30i, 40i), u32(10i), u32(20i));"))
+        << body;
+}
+
+TEST_F(SpvUnaryBitTest, ExtractBits_Uint) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldUExtract %uint %uint_20 %uint_10 %uint_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(body, HasSubstr("let x_1 : u32 = extractBits(20u, 10u, 20u);")) << body;
+}
+
+TEST_F(SpvUnaryBitTest, ExtractBits_Uint_SignedOffsetAndCount) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldUExtract %uint %uint_20 %int_10 %int_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(body, HasSubstr("let x_1 : u32 = extractBits(20u, u32(10i), u32(20i));")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, ExtractBits_UintVector) {
@@ -962,5 +1082,23 @@
         << body;
 }
 
+TEST_F(SpvUnaryBitTest, ExtractBits_UintVector_SignedOffsetAndCount) {
+    const auto assembly = BitTestPreamble() + R"(
+     %1 = OpBitFieldUExtract %v2uint %v2uint_10_20 %int_10 %int_20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+    auto fe = p->function_emitter(100);
+    EXPECT_TRUE(fe.EmitBody()) << p->error();
+    auto ast_body = fe.ast_body();
+    auto body = test::ToString(p->program(), ast_body);
+    EXPECT_THAT(
+        body,
+        HasSubstr("let x_1 : vec2<u32> = extractBits(vec2<u32>(10u, 20u), u32(10i), u32(20i));"))
+        << body;
+}
+
 }  // namespace
 }  // namespace tint::reader::spirv
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 6864084..38d9aa2 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -213,30 +213,41 @@
 ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source,
                                                    std::string_view err,
                                                    std::string_view use) {
-    utils::StringStream msg;
-    msg << err;
-    if (!use.empty()) {
-        msg << " for " << use;
+    if (silence_diags_ == 0) {
+        utils::StringStream msg;
+        msg << err;
+        if (!use.empty()) {
+            msg << " for " << use;
+        }
+        add_error(source, msg.str());
     }
-    add_error(source, msg.str());
     return Failure::kErrored;
 }
 
-ParserImpl::Failure::Errored ParserImpl::add_error(const Token& t, const std::string& err) {
+ParserImpl::Failure::Errored ParserImpl::add_error(const Token& t, std::string_view err) {
     add_error(t.source(), err);
     return Failure::kErrored;
 }
 
-ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source, const std::string& err) {
-    if (silence_errors_ == 0) {
+ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source, std::string_view err) {
+    if (silence_diags_ == 0) {
         builder_.Diagnostics().add_error(diag::System::Reader, err, source);
     }
     return Failure::kErrored;
 }
 
-void ParserImpl::deprecated(const Source& source, const std::string& msg) {
-    builder_.Diagnostics().add_warning(diag::System::Reader,
-                                       "use of deprecated language feature: " + msg, source);
+void ParserImpl::add_note(const Source& source, std::string_view err) {
+    if (silence_diags_ == 0) {
+        builder_.Diagnostics().add_note(diag::System::Reader, err, source);
+    }
+}
+
+void ParserImpl::deprecated(const Source& source, std::string_view msg) {
+    if (silence_diags_ == 0) {
+        builder_.Diagnostics().add_warning(
+            diag::System::Reader, "use of deprecated language feature: " + std::string(msg),
+            source);
+    }
 }
 
 const Token& ParserImpl::next() {
@@ -605,7 +616,7 @@
 
     // We have a statement outside of a function?
     auto& t = peek();
-    auto stat = without_error([&] { return statement(); });
+    auto stat = without_diag([&] { return statement(); });
     if (stat.matched) {
         // Attempt to jump to the next '}' - the function might have just been
         // missing an opening line.
@@ -1719,36 +1730,6 @@
     return create<ast::CaseSelector>(p.source(), expr.value);
 }
 
-// case_body
-//   :
-//   | statement case_body
-Maybe<const ast::BlockStatement*> ParserImpl::case_body() {
-    StatementList stmts;
-    while (continue_parsing()) {
-        Source source;
-        if (match(Token::Type::kFallthrough, &source)) {
-            return add_error(
-                source,
-                "fallthrough is not premitted in WGSL. "
-                "Case can accept multiple selectors if the existing case bodies are empty. "
-                "(e.g. `case 1, 2, 3:`) "
-                "`default` is a valid case selector value. (e.g. `case 1, default:`)");
-        }
-
-        auto stmt = statement();
-        if (stmt.errored) {
-            return Failure::kErrored;
-        }
-        if (!stmt.matched) {
-            break;
-        }
-
-        stmts.Push(stmt.value);
-    }
-
-    return create<ast::BlockStatement>(Source{}, stmts, utils::Empty);
-}
-
 // loop_statement
 //   : LOOP BRACKET_LEFT statements continuing_statement? BRACKET_RIGHT
 Maybe<const ast::LoopStatement*> ParserImpl::loop_statement() {
@@ -3372,10 +3353,10 @@
 }
 
 template <typename F, typename T>
-T ParserImpl::without_error(F&& body) {
-    silence_errors_++;
+T ParserImpl::without_diag(F&& body) {
+    silence_diags_++;
     auto result = body();
-    silence_errors_--;
+    silence_diags_--;
     return result;
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 9693ea4..7ad2e6f 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -348,7 +348,7 @@
     /// @param msg the error message
     /// @return `Failure::Errored::kError` so that you can combine an add_error()
     /// call and return on the same line.
-    Failure::Errored add_error(const Token& t, const std::string& msg);
+    Failure::Errored add_error(const Token& t, std::string_view msg);
     /// Appends an error raised when parsing `use` at `t` with the message
     /// `msg`
     /// @param source the source to associate the error with
@@ -363,12 +363,16 @@
     /// @param msg the error message
     /// @return `Failure::Errored::kError` so that you can combine an add_error()
     /// call and return on the same line.
-    Failure::Errored add_error(const Source& source, const std::string& msg);
+    Failure::Errored add_error(const Source& source, std::string_view msg);
+    /// Appends a note at `source` with the message `msg`
+    /// @param source the source to associate the error with
+    /// @param msg the note message
+    void add_note(const Source& source, std::string_view msg);
     /// Appends a deprecated-language-feature warning at `source` with the message
     /// `msg`
     /// @param source the source to associate the error with
     /// @param msg the warning message
-    void deprecated(const Source& source, const std::string& msg);
+    void deprecated(const Source& source, std::string_view msg);
     /// Parses the `translation_unit` grammar element
     void translation_unit();
     /// Parses the `global_directive` grammar element, erroring on parse failure.
@@ -505,9 +509,6 @@
     /// Parses a `case_selector` grammar element
     /// @returns the selector
     Maybe<const ast::CaseSelector*> case_selector();
-    /// Parses a `case_body` grammar element
-    /// @returns the parsed statements
-    Maybe<const ast::BlockStatement*> case_body();
     /// Parses a `func_call_statement` grammar element
     /// @returns the parsed function call or nullptr
     Maybe<const ast::CallStatement*> func_call_statement();
@@ -805,14 +806,13 @@
         return synchronized_ && builder_.Diagnostics().error_count() < max_errors_;
     }
 
-    /// without_error() calls the function `func` muting any grammatical errors
-    /// found while executing the function. This can be used as a best-effort to
-    /// produce a meaningful error message when the parser is out of sync.
+    /// without_diag() calls the function `func` muting any diagnostics found while executing the
+    /// function. This can be used to silence spew when attempting to resynchronize the parser.
     /// @param func a function or lambda with the signature: `Expect<Result>()` or
     /// `Maybe<Result>()`.
     /// @return the value returned by `func`
     template <typename F, typename T = ReturnType<F>>
-    T without_error(F&& func);
+    T without_diag(F&& func);
 
     /// Reports an error if the attribute list `list` is not empty.
     /// Used to ensure that all attributes are consumed.
@@ -856,7 +856,7 @@
     bool synchronized_ = true;
     uint32_t parse_depth_ = 0;
     std::vector<Token::Type> sync_tokens_;
-    int silence_errors_ = 0;
+    int silence_diags_ = 0;
     ProgramBuilder builder_;
     size_t max_errors_ = 25;
 };
diff --git a/src/tint/reader/wgsl/parser_impl_case_body_test.cc b/src/tint/reader/wgsl/parser_impl_case_body_test.cc
deleted file mode 100644
index 4d60e23..0000000
--- a/src/tint/reader/wgsl/parser_impl_case_body_test.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2020 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/reader/wgsl/parser_impl_test_helper.h"
-
-namespace tint::reader::wgsl {
-namespace {
-
-TEST_F(ParserImplTest, CaseBody_Empty) {
-    auto p = parser("");
-    auto e = p->case_body();
-    ASSERT_FALSE(p->has_error()) << p->error();
-    EXPECT_FALSE(e.errored);
-    EXPECT_TRUE(e.matched);
-    EXPECT_EQ(e->statements.Length(), 0u);
-}
-
-TEST_F(ParserImplTest, CaseBody_Statements) {
-    auto p = parser(R"(
-  var a: i32;
-  a = 2;)");
-
-    auto e = p->case_body();
-    ASSERT_FALSE(p->has_error()) << p->error();
-    EXPECT_FALSE(e.errored);
-    EXPECT_TRUE(e.matched);
-    ASSERT_EQ(e->statements.Length(), 2u);
-    EXPECT_TRUE(e->statements[0]->Is<ast::VariableDeclStatement>());
-    EXPECT_TRUE(e->statements[1]->Is<ast::AssignmentStatement>());
-}
-
-TEST_F(ParserImplTest, CaseBody_InvalidStatement) {
-    auto p = parser("a =");
-    auto e = p->case_body();
-    EXPECT_TRUE(p->has_error());
-    EXPECT_TRUE(e.errored);
-    EXPECT_FALSE(e.matched);
-    EXPECT_EQ(e.value, nullptr);
-}
-
-}  // namespace
-}  // namespace tint::reader::wgsl
diff --git a/src/tint/transform/remove_phonies.cc b/src/tint/transform/remove_phonies.cc
index b6d877d..e5aecd2 100644
--- a/src/tint/transform/remove_phonies.cc
+++ b/src/tint/transform/remove_phonies.cc
@@ -88,10 +88,22 @@
                     }
 
                     if (side_effects.size() == 1) {
-                        if (auto* call = side_effects[0]->As<ast::CallExpression>()) {
+                        if (auto* call_expr = side_effects[0]->As<ast::CallExpression>()) {
                             // Phony assignment with single call side effect.
-                            // Replace phony assignment with call.
-                            ctx.Replace(stmt, [&, call] { return b.CallStmt(ctx.Clone(call)); });
+                            auto* call = sem.Get(call_expr)->Unwrap()->As<sem::Call>();
+                            if (call->Target()->MustUse()) {
+                                // Replace phony assignment assignment to uniquely named let.
+                                ctx.Replace<ast::Statement>(stmt, [&, call_expr] {  //
+                                    auto name = b.Symbols().New("tint_phony");
+                                    auto* rhs = ctx.Clone(call_expr);
+                                    return b.Decl(b.Let(name, rhs));
+                                });
+                            } else {
+                                // Replace phony assignment with call statement.
+                                ctx.Replace(stmt, [&, call_expr] {  //
+                                    return b.CallStmt(ctx.Clone(call_expr));
+                                });
+                            }
                             return;
                         }
                     }
diff --git a/src/tint/transform/remove_phonies_test.cc b/src/tint/transform/remove_phonies_test.cc
index dc91b5a..60252a1 100644
--- a/src/tint/transform/remove_phonies_test.cc
+++ b/src/tint/transform/remove_phonies_test.cc
@@ -131,6 +131,60 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(RemovePhoniesTest, SingleSideEffectsMustUse) {
+    auto* src = R"(
+@must_use
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+@must_use
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn f() {
+  let tint_phony = 42;
+  _ = neg(1);
+  _ = 100 + add(2, 3) + 200;
+  _ = add(neg(4), neg(5)) + 6;
+  _ = u32(neg(6));
+  _ = f32(add(7, 8));
+  _ = vec2<f32>(f32(neg(9)));
+  _ = vec3<i32>(1, neg(10), 3);
+  _ = mat2x2<f32>(1.0, f32(add(11, 12)), 3.0, 4.0);
+}
+)";
+
+    auto* expect = R"(
+@must_use
+fn neg(a : i32) -> i32 {
+  return -(a);
+}
+
+@must_use
+fn add(a : i32, b : i32) -> i32 {
+  return (a + b);
+}
+
+fn f() {
+  let tint_phony = 42;
+  let tint_phony_1 = neg(1);
+  let tint_phony_2 = add(2, 3);
+  let tint_phony_3 = add(neg(4), neg(5));
+  let tint_phony_4 = neg(6);
+  let tint_phony_5 = add(7, 8);
+  let tint_phony_6 = neg(9);
+  let tint_phony_7 = neg(10);
+  let tint_phony_8 = add(11, 12);
+}
+)";
+
+    auto got = Run<RemovePhonies>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(RemovePhoniesTest, SingleSideEffects_OutOfOrder) {
     auto* src = R"(
 fn f() {
@@ -185,6 +239,7 @@
   return -(a);
 }
 
+@must_use
 fn add(a : i32, b : i32) -> i32 {
   return (a + b);
 }
@@ -206,6 +261,7 @@
   return -(a);
 }
 
+@must_use
 fn add(a : i32, b : i32) -> i32 {
   return (a + b);
 }
diff --git a/src/tint/writer/binding_remapper_options.cc b/src/tint/writer/binding_remapper_options.cc
new file mode 100644
index 0000000..78f572c
--- /dev/null
+++ b/src/tint/writer/binding_remapper_options.cc
@@ -0,0 +1,29 @@
+// Copyright 2023 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/writer/binding_remapper_options.h"
+
+namespace tint::writer {
+
+BindingRemapperOptions::BindingRemapperOptions() = default;
+
+BindingRemapperOptions::~BindingRemapperOptions() = default;
+
+BindingRemapperOptions::BindingRemapperOptions(const BindingRemapperOptions&) = default;
+
+BindingRemapperOptions& BindingRemapperOptions::operator=(const BindingRemapperOptions&) = default;
+
+BindingRemapperOptions::BindingRemapperOptions(BindingRemapperOptions&&) = default;
+
+}  // namespace tint::writer
diff --git a/src/tint/writer/binding_remapper_options.h b/src/tint/writer/binding_remapper_options.h
new file mode 100644
index 0000000..865bcdb
--- /dev/null
+++ b/src/tint/writer/binding_remapper_options.h
@@ -0,0 +1,62 @@
+// Copyright 2023 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_WRITER_BINDING_REMAPPER_OPTIONS_H_
+#define SRC_TINT_WRITER_BINDING_REMAPPER_OPTIONS_H_
+
+#include <unordered_map>
+
+#include "src/tint/builtin/access.h"
+#include "src/tint/writer/binding_point.h"
+
+namespace tint::writer {
+
+/// Options used to specify mappings of binding points.
+class BindingRemapperOptions {
+  public:
+    /// BindingPoints is a map of old binding point to new binding point
+    using BindingPoints = std::unordered_map<BindingPoint, BindingPoint>;
+
+    /// AccessControls is a map of old binding point to new access control
+    using AccessControls = std::unordered_map<BindingPoint, builtin::Access>;
+
+    /// Constructor
+    BindingRemapperOptions();
+    /// Destructor
+    ~BindingRemapperOptions();
+    /// Copy constructor
+    BindingRemapperOptions(const BindingRemapperOptions&);
+    /// Copy assignment
+    /// @returns this BindingRemapperOptions
+    BindingRemapperOptions& operator=(const BindingRemapperOptions&);
+    /// Move constructor
+    BindingRemapperOptions(BindingRemapperOptions&&);
+
+    /// A map of old binding point to new binding point
+    BindingPoints binding_points;
+
+    /// A map of old binding point to new access controls
+    AccessControls access_controls;
+
+    /// If true, then validation will be disabled for binding point collisions
+    /// generated by this transform
+    bool allow_collisions = false;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(binding_points, access_controls, allow_collisions);
+};
+
+}  // namespace tint::writer
+
+#endif  // SRC_TINT_WRITER_BINDING_REMAPPER_OPTIONS_H_
diff --git a/src/tint/writer/hlsl/generator.h b/src/tint/writer/hlsl/generator.h
index 2cdfe6f..9b79ad1 100644
--- a/src/tint/writer/hlsl/generator.h
+++ b/src/tint/writer/hlsl/generator.h
@@ -28,6 +28,7 @@
 #include "src/tint/sem/binding_point.h"
 #include "src/tint/utils/bitset.h"
 #include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/binding_remapper_options.h"
 #include "src/tint/writer/external_texture_options.h"
 #include "src/tint/writer/text.h"
 
@@ -66,6 +67,9 @@
     /// from which to load buffer sizes.
     ArrayLengthFromUniformOptions array_length_from_uniform = {};
 
+    /// Options used in the bindings remapper
+    BindingRemapperOptions binding_remapper_options = {};
+
     /// Interstage locations actually used as inputs in the next stage of the pipeline.
     /// This is potentially used for truncating unused interstage outputs at current shader stage.
     std::bitset<16> interstage_locations;
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 1cfbf64..cc8ad64 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -43,6 +43,7 @@
 #include "src/tint/switch.h"
 #include "src/tint/transform/add_empty_entry_point.h"
 #include "src/tint/transform/array_length_from_uniform.h"
+#include "src/tint/transform/binding_remapper.h"
 #include "src/tint/transform/builtin_polyfill.h"
 #include "src/tint/transform/calculate_array_length.h"
 #include "src/tint/transform/canonicalize_entry_point_io.h"
@@ -195,6 +196,13 @@
         manager.Add<transform::MultiplanarExternalTexture>();
     }
 
+    // BindingRemapper must come after MultiplanarExternalTexture
+    manager.Add<transform::BindingRemapper>();
+    data.Add<transform::BindingRemapper::Remappings>(
+        options.binding_remapper_options.binding_points,
+        options.binding_remapper_options.access_controls,
+        options.binding_remapper_options.allow_collisions);
+
     {  // Builtin polyfills
         transform::BuiltinPolyfill::Builtins polyfills;
         polyfills.acosh = transform::BuiltinPolyfill::Level::kFull;
diff --git a/src/tint/writer/msl/generator.h b/src/tint/writer/msl/generator.h
index b4e11d9..53805f1 100644
--- a/src/tint/writer/msl/generator.h
+++ b/src/tint/writer/msl/generator.h
@@ -23,6 +23,7 @@
 
 #include "src/tint/reflection.h"
 #include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/binding_remapper_options.h"
 #include "src/tint/writer/external_texture_options.h"
 #include "src/tint/writer/text.h"
 
@@ -70,6 +71,9 @@
     /// from which to load buffer sizes.
     ArrayLengthFromUniformOptions array_length_from_uniform = {};
 
+    /// Options used in the bindings remapper
+    BindingRemapperOptions binding_remapper_options = {};
+
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(disable_robustness,
                  buffer_size_ubo_index,
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 268736f..f4bf13e 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -42,6 +42,7 @@
 #include "src/tint/sem/variable.h"
 #include "src/tint/switch.h"
 #include "src/tint/transform/array_length_from_uniform.h"
+#include "src/tint/transform/binding_remapper.h"
 #include "src/tint/transform/builtin_polyfill.h"
 #include "src/tint/transform/canonicalize_entry_point_io.h"
 #include "src/tint/transform/demote_to_helper.h"
@@ -173,29 +174,6 @@
     // ExpandCompoundAssignment must come before BuiltinPolyfill
     manager.Add<transform::ExpandCompoundAssignment>();
 
-    // Build the config for the internal ArrayLengthFromUniform transform.
-    auto& array_length_from_uniform = options.array_length_from_uniform;
-    transform::ArrayLengthFromUniform::Config array_length_from_uniform_cfg(
-        array_length_from_uniform.ubo_binding);
-    if (!array_length_from_uniform.bindpoint_to_size_index.empty()) {
-        // If |array_length_from_uniform| bindings are provided, use that config.
-        array_length_from_uniform_cfg.bindpoint_to_size_index =
-            array_length_from_uniform.bindpoint_to_size_index;
-    } else {
-        // If the binding map is empty, use the deprecated |buffer_size_ubo_index|
-        // and automatically choose indices using the binding numbers.
-        array_length_from_uniform_cfg = transform::ArrayLengthFromUniform::Config(
-            sem::BindingPoint{0, options.buffer_size_ubo_index});
-        // Use the SSBO binding numbers as the indices for the buffer size lookups.
-        for (auto* var : in->AST().GlobalVariables()) {
-            auto* global = in->Sem().Get<sem::GlobalVariable>(var);
-            if (global && global->AddressSpace() == builtin::AddressSpace::kStorage) {
-                array_length_from_uniform_cfg.bindpoint_to_size_index.emplace(
-                    global->BindingPoint(), global->BindingPoint().binding);
-            }
-        }
-    }
-
     // Build the configs for the internal CanonicalizeEntryPointIO transform.
     auto entry_point_io_cfg = transform::CanonicalizeEntryPointIO::Config(
         transform::CanonicalizeEntryPointIO::ShaderStyle::kMsl, options.fixed_sample_mask,
@@ -210,6 +188,7 @@
     if (!options.disable_robustness) {
         // Robustness must come after PromoteSideEffectsToDecl
         // Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
+        // Robustness must come before ArrayLengthFromUniform
         manager.Add<transform::Robustness>();
     }
 
@@ -239,6 +218,13 @@
         manager.Add<transform::MultiplanarExternalTexture>();
     }
 
+    // BindingRemapper must come after MultiplanarExternalTexture
+    manager.Add<transform::BindingRemapper>();
+    data.Add<transform::BindingRemapper::Remappings>(
+        options.binding_remapper_options.binding_points,
+        options.binding_remapper_options.access_controls,
+        options.binding_remapper_options.allow_collisions);
+
     if (!options.disable_workgroup_init) {
         // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
         // ZeroInitWorkgroupMemory may inject new builtin parameters.
@@ -247,6 +233,7 @@
 
     // CanonicalizeEntryPointIO must come after Robustness
     manager.Add<transform::CanonicalizeEntryPointIO>();
+    data.Add<transform::CanonicalizeEntryPointIO::Config>(std::move(entry_point_io_cfg));
 
     manager.Add<transform::PromoteInitializersToLet>();
 
@@ -257,14 +244,21 @@
     manager.Add<transform::VectorizeScalarMatrixInitializers>();
     manager.Add<transform::RemovePhonies>();
     manager.Add<transform::SimplifyPointers>();
+
     // ArrayLengthFromUniform must come after SimplifyPointers, as
     // it assumes that the form of the array length argument is &var.array.
     manager.Add<transform::ArrayLengthFromUniform>();
+
+    transform::ArrayLengthFromUniform::Config array_length_cfg(
+        std::move(options.array_length_from_uniform.ubo_binding));
+    array_length_cfg.bindpoint_to_size_index =
+        std::move(options.array_length_from_uniform.bindpoint_to_size_index);
+    data.Add<transform::ArrayLengthFromUniform::Config>(array_length_cfg);
+
     // PackedVec3 must come after ExpandCompoundAssignment.
     manager.Add<transform::PackedVec3>();
     manager.Add<transform::ModuleScopeVarToEntryPointParam>();
-    data.Add<transform::ArrayLengthFromUniform::Config>(std::move(array_length_from_uniform_cfg));
-    data.Add<transform::CanonicalizeEntryPointIO::Config>(std::move(entry_point_io_cfg));
+
     auto out = manager.Run(in, data);
 
     SanitizedResult result;
diff --git a/src/tint/writer/msl/generator_impl_sanitizer_test.cc b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
index f72e0e9..c75fe92 100644
--- a/src/tint/writer/msl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/msl/generator_impl_sanitizer_test.cc
@@ -39,7 +39,10 @@
              Stage(ast::PipelineStage::kFragment),
          });
 
-    GeneratorImpl& gen = SanitizeAndBuild();
+    Options opts = DefaultOptions();
+    opts.array_length_from_uniform.ubo_binding = sem::BindingPoint{0, 30};
+    opts.array_length_from_uniform.bindpoint_to_size_index.emplace(sem::BindingPoint{2, 1}, 1);
+    GeneratorImpl& gen = SanitizeAndBuild(opts);
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
 
@@ -93,7 +96,10 @@
              Stage(ast::PipelineStage::kFragment),
          });
 
-    GeneratorImpl& gen = SanitizeAndBuild();
+    Options opts = DefaultOptions();
+    opts.array_length_from_uniform.ubo_binding = sem::BindingPoint{0, 30};
+    opts.array_length_from_uniform.bindpoint_to_size_index.emplace(sem::BindingPoint{2, 1}, 1);
+    GeneratorImpl& gen = SanitizeAndBuild(opts);
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
 
@@ -151,7 +157,10 @@
              Stage(ast::PipelineStage::kFragment),
          });
 
-    GeneratorImpl& gen = SanitizeAndBuild();
+    Options opts = DefaultOptions();
+    opts.array_length_from_uniform.ubo_binding = sem::BindingPoint{0, 30};
+    opts.array_length_from_uniform.bindpoint_to_size_index.emplace(sem::BindingPoint{2, 1}, 1);
+    GeneratorImpl& gen = SanitizeAndBuild(opts);
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
 
diff --git a/src/tint/writer/spirv/generator.h b/src/tint/writer/spirv/generator.h
index f6d2268..d24d938 100644
--- a/src/tint/writer/spirv/generator.h
+++ b/src/tint/writer/spirv/generator.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "src/tint/reflection.h"
+#include "src/tint/writer/binding_remapper_options.h"
 #include "src/tint/writer/external_texture_options.h"
 #include "src/tint/writer/writer.h"
 
@@ -52,6 +53,9 @@
     /// Options used in the binding mappings for external textures
     ExternalTextureOptions external_texture_options = {};
 
+    /// Options used in the bindings remapper
+    BindingRemapperOptions binding_remapper_options = {};
+
     /// Set to `true` to initialize workgroup memory with OpConstantNull when
     /// VK_KHR_zero_initialize_workgroup_memory is enabled.
     bool use_zero_initialize_workgroup_memory_extension = false;
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index 1986f83..970a3e5 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -19,6 +19,7 @@
 
 #include "src/tint/transform/add_block_attribute.h"
 #include "src/tint/transform/add_empty_entry_point.h"
+#include "src/tint/transform/binding_remapper.h"
 #include "src/tint/transform/builtin_polyfill.h"
 #include "src/tint/transform/canonicalize_entry_point_io.h"
 #include "src/tint/transform/clamp_frag_depth.h"
@@ -59,13 +60,18 @@
     // ExpandCompoundAssignment must come before BuiltinPolyfill
     manager.Add<transform::ExpandCompoundAssignment>();
 
-    manager.Add<transform::PreservePadding>();  // Must come before DirectVariableAccess
+    // Must come before DirectVariableAccess
+    manager.Add<transform::PreservePadding>();
 
-    manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
+    // Must come before DirectVariableAccess
+    manager.Add<transform::Unshadow>();
 
     manager.Add<transform::RemoveUnreachableStatements>();
     manager.Add<transform::PromoteSideEffectsToDecl>();
-    manager.Add<transform::SimplifyPointers>();  // Required for arrayLength()
+
+    // Required for arrayLength()
+    manager.Add<transform::SimplifyPointers>();
+
     manager.Add<transform::RemovePhonies>();
     manager.Add<transform::VectorizeScalarMatrixInitializers>();
     manager.Add<transform::VectorizeMatrixConversions>();
@@ -78,6 +84,14 @@
         manager.Add<transform::Robustness>();
     }
 
+    // BindingRemapper must come before MultiplanarExternalTexture. Note, this is flipped to the
+    // other generators which run Multiplanar first and then binding remapper.
+    manager.Add<transform::BindingRemapper>();
+    data.Add<transform::BindingRemapper::Remappings>(
+        options.binding_remapper_options.binding_points,
+        options.binding_remapper_options.access_controls,
+        options.binding_remapper_options.allow_collisions);
+
     if (!options.external_texture_options.bindings_map.empty()) {
         // Note: it is more efficient for MultiplanarExternalTexture to come after Robustness
         data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(