Import Tint changes from Dawn

Changes:
  - 142bddf7dabe2cbbbb2adfaaf0a9708aad0ba1a8 Fix #ifdefs for spvtools by Austin Eng <enga@chromium.org>
  - f244bffdd51d8c4aeddd4181025559a41de4154f Fix build of tint_cmd_helper by Austin Eng <enga@chromium.org>
  - 8525ff29da6b005381abe94023a303edc0f39c44 tint/transform/robustness: Implement predicated mode by Ben Clayton <bclayton@google.com>
  - 5e5655155110e121763e3dca13d20c3eda09167e Revert "Add writer to emit the AST." by Dan Sinclair <dsinclair@chromium.org>
  - 5ab2928f6ab2c2c9d899bf1220d4be7740f09664 Convert `strtoll` to use `std::from_chars`. by dan sinclair <dsinclair@chromium.org>
  - a753ad47a48fc0fba6e617b7ffcddc0e1fdc4b2e Add writer to emit the AST. by dan sinclair <dsinclair@chromium.org>
  - 63d0fabeb1fc11e4829256804bf776a23e8f2849 tint: Fix HLSL emission for out-of-order storage / unifor... by Ben Clayton <bclayton@google.com>
  - fd387a37c3b936a547288347bf854405ca2ec647 tint: Fix WGSL emission of const_assert by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 142bddf7dabe2cbbbb2adfaaf0a9708aad0ba1a8
Change-Id: Ic0f52f73c9a9fa70f04d272f616db8e5abd82136
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/122880
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ast/disable_validation_attribute.cc b/src/tint/ast/disable_validation_attribute.cc
index eff1c1f..27eaef0 100644
--- a/src/tint/ast/disable_validation_attribute.cc
+++ b/src/tint/ast/disable_validation_attribute.cc
@@ -23,7 +23,7 @@
 DisableValidationAttribute::DisableValidationAttribute(ProgramID pid,
                                                        NodeID nid,
                                                        DisabledValidation val)
-    : Base(pid, nid), validation(val) {}
+    : Base(pid, nid, utils::Empty), validation(val) {}
 
 DisableValidationAttribute::~DisableValidationAttribute() = default;
 
diff --git a/src/tint/ast/internal_attribute.cc b/src/tint/ast/internal_attribute.cc
index 1b4ca9e..c5c5f2e 100644
--- a/src/tint/ast/internal_attribute.cc
+++ b/src/tint/ast/internal_attribute.cc
@@ -14,11 +14,16 @@
 
 #include "src/tint/ast/internal_attribute.h"
 
+#include <utility>
+
 TINT_INSTANTIATE_TYPEINFO(tint::ast::InternalAttribute);
 
 namespace tint::ast {
 
-InternalAttribute::InternalAttribute(ProgramID pid, NodeID nid) : Base(pid, nid, Source{}) {}
+InternalAttribute::InternalAttribute(ProgramID pid,
+                                     NodeID nid,
+                                     utils::VectorRef<const IdentifierExpression*> deps)
+    : Base(pid, nid, Source{}), dependencies(std::move(deps)) {}
 
 InternalAttribute::~InternalAttribute() = default;
 
diff --git a/src/tint/ast/internal_attribute.h b/src/tint/ast/internal_attribute.h
index 9904af8..36f2a98 100644
--- a/src/tint/ast/internal_attribute.h
+++ b/src/tint/ast/internal_attribute.h
@@ -18,6 +18,12 @@
 #include <string>
 
 #include "src/tint/ast/attribute.h"
+#include "src/tint/utils/vector.h"
+
+// Forward declarations
+namespace tint::ast {
+class IdentifierExpression;
+}  // namespace tint::ast
 
 namespace tint::ast {
 
@@ -29,7 +35,10 @@
     /// Constructor
     /// @param program_id the identifier of the program that owns this node
     /// @param nid the unique node identifier
-    explicit InternalAttribute(ProgramID program_id, NodeID nid);
+    /// @param deps a list of identifiers that this attribute is dependent on
+    InternalAttribute(ProgramID program_id,
+                      NodeID nid,
+                      utils::VectorRef<const IdentifierExpression*> deps);
 
     /// Destructor
     ~InternalAttribute() override;
@@ -40,6 +49,9 @@
 
     /// @returns the WGSL name for the attribute
     std::string Name() const override;
+
+    /// A list of identifiers that this attribute is dependent on
+    const utils::Vector<const IdentifierExpression*, 1> dependencies;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/cmd/BUILD.gn b/src/tint/cmd/BUILD.gn
index ab0c964..b4ff6d5 100644
--- a/src/tint/cmd/BUILD.gn
+++ b/src/tint/cmd/BUILD.gn
@@ -21,10 +21,10 @@
     "helper.h",
   ]
 
-  deps = [
-    "${tint_root_dir}/src/tint:libtint",
-    "${tint_spirv_tools_dir}/:spvtools_headers",
-  ]
+  deps = [ "${tint_root_dir}/src/tint:libtint" ]
+  if (tint_build_spv_reader || tint_build_spv_writer) {
+    deps += [ "${tint_spirv_tools_dir}/:spvtools_headers" ]
+  }
 }
 
 executable("tint") {
diff --git a/src/tint/fuzzers/tint_common_fuzzer.cc b/src/tint/fuzzers/tint_common_fuzzer.cc
index c4f1fd1..7c390af 100644
--- a/src/tint/fuzzers/tint_common_fuzzer.cc
+++ b/src/tint/fuzzers/tint_common_fuzzer.cc
@@ -24,9 +24,9 @@
 #include <utility>
 #include <vector>
 
-#if TINT_BUILD_SPV_READER
+#if TINT_BUILD_SPV_READER || TINT_BUILD_SPV_WRITER
 #include "spirv-tools/libspirv.hpp"
-#endif  // TINT_BUILD_SPV_READER
+#endif  // TINT_BUILD_SPV_READER || TINT_BUILD_SPV_WRITER
 
 #include "src/tint/ast/module.h"
 #include "src/tint/diagnostic/formatter.h"
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index 5171e4c..28fb135 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -15,6 +15,7 @@
 #include "src/tint/reader/wgsl/lexer.h"
 
 #include <cctype>
+#include <charconv>
 #include <cmath>
 #include <cstring>
 #include <functional>
@@ -804,41 +805,48 @@
     return {Token::Type::kFloatLiteral, source, result_f64};
 }
 
-Token Lexer::build_token_from_int_if_possible(Source source, size_t start, int32_t base) {
+Token Lexer::build_token_from_int_if_possible(Source source,
+                                              size_t start,
+                                              size_t prefix_count,
+                                              int32_t base) {
     const char* start_ptr = &at(start);
-    char* end_ptr = nullptr;
+    // The call to `from_chars` will return the pointer to just after the last parsed character.
+    // We also need to tell it the maximum end character to parse. So, instead of walking all the
+    // characters to find the last possible and using that, we just provide the end of the string.
+    // We then calculate the count based off the provided end pointer and the start pointer. The
+    // extra `prefix_count` is to handle a `0x` which is not included in the `start` value.
+    const char* end_ptr = &at(length() - 1) + 1;
 
-    errno = 0;
-    int64_t res = strtoll(start_ptr, &end_ptr, base);
-    const bool overflow = errno == ERANGE;
-
-    if (end_ptr) {
-        advance(static_cast<size_t>(end_ptr - start_ptr));
-    }
+    int64_t value = 0;
+    auto res = std::from_chars(start_ptr, end_ptr, value, base);
+    const bool overflow = res.ec != std::errc();
+    advance(static_cast<size_t>(res.ptr - start_ptr) + prefix_count);
 
     if (matches(pos(), 'u')) {
-        if (!overflow && CheckedConvert<u32>(AInt(res))) {
+        if (!overflow && CheckedConvert<u32>(AInt(value))) {
             advance(1);
             end_source(source);
-            return {Token::Type::kIntLiteral_U, source, res};
+            return {Token::Type::kIntLiteral_U, source, value};
         }
         return {Token::Type::kError, source, "value cannot be represented as 'u32'"};
     }
 
     if (matches(pos(), 'i')) {
-        if (!overflow && CheckedConvert<i32>(AInt(res))) {
+        if (!overflow && CheckedConvert<i32>(AInt(value))) {
             advance(1);
             end_source(source);
-            return {Token::Type::kIntLiteral_I, source, res};
+            return {Token::Type::kIntLiteral_I, source, value};
         }
         return {Token::Type::kError, source, "value cannot be represented as 'i32'"};
     }
 
-    end_source(source);
+    // Check this last in order to get the more specific sized error messages
     if (overflow) {
         return {Token::Type::kError, source, "value cannot be represented as 'abstract-int'"};
     }
-    return {Token::Type::kIntLiteral, source, res};
+
+    end_source(source);
+    return {Token::Type::kIntLiteral, source, value};
 }
 
 Token Lexer::try_hex_integer() {
@@ -858,7 +866,7 @@
                 "integer or float hex literal has no significant digits"};
     }
 
-    return build_token_from_int_if_possible(source, start, 16);
+    return build_token_from_int_if_possible(source, curr, curr - start, 16);
 }
 
 Token Lexer::try_integer() {
@@ -879,7 +887,7 @@
         }
     }
 
-    return build_token_from_int_if_possible(source, start, 10);
+    return build_token_from_int_if_possible(source, start, 0, 10);
 }
 
 Token Lexer::try_ident() {
diff --git a/src/tint/reader/wgsl/lexer.h b/src/tint/reader/wgsl/lexer.h
index 8e0306b..ac41dae 100644
--- a/src/tint/reader/wgsl/lexer.h
+++ b/src/tint/reader/wgsl/lexer.h
@@ -47,7 +47,10 @@
     /// @returns uninitialized token on success, or error
     Token skip_comment();
 
-    Token build_token_from_int_if_possible(Source source, size_t start, int32_t base);
+    Token build_token_from_int_if_possible(Source source,
+                                           size_t start,
+                                           size_t prefix_count,
+                                           int32_t base);
 
     Token check_keyword(const Source&, std::string_view);
 
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index b0a863d..5c09c2a 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -1977,4 +1977,40 @@
 }  // namespace
 }  // namespace MustUseTests
 
+namespace InternalAttributeDeps {
+namespace {
+
+class TestAttribute : public Castable<TestAttribute, ast::InternalAttribute> {
+  public:
+    TestAttribute(ProgramID pid, ast::NodeID nid, const ast::IdentifierExpression* dep)
+        : Base(pid, nid, utils::Vector{dep}) {}
+    std::string InternalName() const override { return "test_attribute"; }
+    const Cloneable* Clone(CloneContext*) const override { return nullptr; }
+};
+
+using InternalAttributeDepsTest = ResolverTest;
+TEST_F(InternalAttributeDepsTest, Dependency) {
+    auto* ident = Expr("v");
+    auto* attr = ASTNodes().Create<TestAttribute>(ID(), AllocateNodeID(), ident);
+    auto* f = Func("f", utils::Empty, ty.void_(), utils::Empty, utils::Vector{attr});
+    auto* v = GlobalVar("v", ty.i32(), builtin::AddressSpace::kPrivate);
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* user = As<sem::VariableUser>(Sem().Get(ident));
+    ASSERT_NE(user, nullptr);
+
+    auto* var = Sem().Get(v);
+    EXPECT_EQ(user->Variable(), var);
+
+    auto* fn = Sem().Get(f);
+    EXPECT_THAT(fn->DirectlyReferencedGlobals(), testing::ElementsAre(var));
+    EXPECT_THAT(fn->TransitivelyReferencedGlobals(), testing::ElementsAre(var));
+}
+
+}  // namespace
+}  // namespace InternalAttributeDeps
+
 }  // namespace tint::resolver
+
+TINT_INSTANTIATE_TYPEINFO(tint::resolver::InternalAttributeDeps::TestAttribute);
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 8292adb..f718a46 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -414,12 +414,18 @@
                 TraverseExpression(wg->y);
                 TraverseExpression(wg->z);
                 return true;
+            },
+            [&](const ast::InternalAttribute* i) {
+                for (auto* dep : i->dependencies) {
+                    TraverseExpression(dep);
+                }
+                return true;
             });
         if (handled) {
             return;
         }
 
-        if (attr->IsAnyOf<ast::BuiltinAttribute, ast::DiagnosticAttribute, ast::InternalAttribute,
+        if (attr->IsAnyOf<ast::BuiltinAttribute, ast::DiagnosticAttribute,
                           ast::InterpolateAttribute, ast::InvariantAttribute, ast::MustUseAttribute,
                           ast::StageAttribute, ast::StrideAttribute,
                           ast::StructMemberOffsetAttribute>()) {
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index 0d2cc10..e8fe271 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -1495,7 +1495,7 @@
 
     // Was this overload a constructor or conversion?
     if (match.overload->flags.Contains(OverloadFlag::kIsConstructor)) {
-        utils::Vector<const sem::Parameter*, 8> params;
+        utils::Vector<sem::Parameter*, 8> params;
         params.Reserve(match.parameters.Length());
         for (auto& p : match.parameters) {
             params.Push(builder.create<sem::Parameter>(
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index cd426f7..f51e5cc 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -856,9 +856,9 @@
 sem::Function* Resolver::Function(const ast::Function* decl) {
     Mark(decl->name);
 
-    uint32_t parameter_index = 0;
-    utils::Hashmap<Symbol, Source, 8> parameter_names;
-    utils::Vector<sem::Parameter*, 8> parameters;
+    auto* func = builder_->create<sem::Function>(decl);
+    builder_->Sem().Add(decl, func);
+    TINT_SCOPED_ASSIGNMENT(current_function_, func);
 
     validator_.DiagnosticFilters().Push();
     TINT_DEFER(validator_.DiagnosticFilters().Pop());
@@ -872,6 +872,8 @@
     }
 
     // Resolve all the parameters
+    uint32_t parameter_index = 0;
+    utils::Hashmap<Symbol, Source, 8> parameter_names;
     for (auto* param : decl->params) {
         Mark(param);
 
@@ -893,7 +895,7 @@
             return nullptr;
         }
 
-        parameters.Push(p);
+        func->AddParameter(p);
 
         auto* p_ty = const_cast<type::Type*>(p->Type());
         if (auto* str = p_ty->As<sem::Struct>()) {
@@ -923,9 +925,9 @@
     } else {
         return_type = builder_->create<type::Void>();
     }
+    func->SetReturnType(return_type);
 
     // Determine if the return type has a location
-    std::optional<uint32_t> return_location;
     for (auto* attr : decl->return_type_attributes) {
         if (!Attribute(attr)) {
             return nullptr;
@@ -936,7 +938,7 @@
             if (!value) {
                 return nullptr;
             }
-            return_location = value.Get();
+            func->SetReturnLocation(value.Get());
         }
     }
 
@@ -963,12 +965,7 @@
         }
     }
 
-    auto* func =
-        builder_->create<sem::Function>(decl, return_type, return_location, std::move(parameters));
     ApplyDiagnosticSeverities(func);
-    builder_->Sem().Add(decl, func);
-
-    TINT_SCOPED_ASSIGNMENT(current_function_, func);
 
     if (!WorkgroupSize(decl)) {
         return nullptr;
@@ -2089,7 +2086,7 @@
                 auto* call_target = struct_ctors_.GetOrCreate(
                     StructConstructorSig{{str, args.Length(), args_stage}},
                     [&]() -> sem::ValueConstructor* {
-                        utils::Vector<const sem::Parameter*, 8> params;
+                        utils::Vector<sem::Parameter*, 8> params;
                         params.Resize(std::min(args.Length(), str->Members().Length()));
                         for (size_t i = 0, n = params.Length(); i < n; i++) {
                             params[i] = builder_->create<sem::Parameter>(
@@ -3436,6 +3433,7 @@
         [&](const ast::BuiltinAttribute* b) { return BuiltinAttribute(b); },
         [&](const ast::DiagnosticAttribute* d) { return DiagnosticControl(d->control); },
         [&](const ast::InterpolateAttribute* i) { return InterpolateAttribute(i); },
+        [&](const ast::InternalAttribute* i) { return InternalAttribute(i); },
         [&](Default) { return true; });
 }
 
@@ -3460,6 +3458,15 @@
     return true;
 }
 
+bool Resolver::InternalAttribute(const ast::InternalAttribute* attr) {
+    for (auto* dep : attr->dependencies) {
+        if (!Expression(dep)) {
+            return false;
+        }
+    }
+    return true;
+}
+
 bool Resolver::DiagnosticControl(const ast::DiagnosticControl& control) {
     Mark(control.rule_name);
 
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index fff7506..8acf602 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -321,6 +321,10 @@
     /// @returns true on success, false on failure
     bool InterpolateAttribute(const ast::InterpolateAttribute* attr);
 
+    /// Resolves the internal attribute @p attr
+    /// @returns true on success, false on failure
+    bool InternalAttribute(const ast::InternalAttribute* attr);
+
     /// @param control the diagnostic control
     /// @returns true on success, false on failure
     bool DiagnosticControl(const ast::DiagnosticControl& control);
diff --git a/src/tint/sem/builtin.cc b/src/tint/sem/builtin.cc
index 8bcd8af..4fd2c34 100644
--- a/src/tint/sem/builtin.cc
+++ b/src/tint/sem/builtin.cc
@@ -25,17 +25,6 @@
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Builtin);
 
 namespace tint::sem {
-namespace {
-
-utils::VectorRef<const Parameter*> SetOwner(utils::VectorRef<Parameter*> parameters,
-                                            const tint::sem::CallTarget* owner) {
-    for (auto* parameter : parameters) {
-        parameter->SetOwner(owner);
-    }
-    return parameters;
-}
-
-}  // namespace
 
 const char* Builtin::str() const {
     return sem::str(type_);
@@ -112,7 +101,7 @@
                  PipelineStageSet supported_stages,
                  bool is_deprecated,
                  bool must_use)
-    : Base(return_type, SetOwner(std::move(parameters), this), eval_stage, must_use),
+    : Base(return_type, std::move(parameters), eval_stage, must_use),
       type_(type),
       supported_stages_(supported_stages),
       is_deprecated_(is_deprecated) {}
diff --git a/src/tint/sem/call_target.cc b/src/tint/sem/call_target.cc
index 76c1ab1..9ef0059 100644
--- a/src/tint/sem/call_target.cc
+++ b/src/tint/sem/call_target.cc
@@ -23,17 +23,25 @@
 
 namespace tint::sem {
 
+CallTarget::CallTarget(EvaluationStage stage, bool must_use) : stage_(stage), must_use_(must_use) {}
+
 CallTarget::CallTarget(const type::Type* return_type,
-                       utils::VectorRef<const Parameter*> parameters,
+                       utils::VectorRef<Parameter*> parameters,
                        EvaluationStage stage,
                        bool must_use)
-    : signature_{return_type, std::move(parameters)}, stage_(stage), must_use_(must_use) {
+    : stage_(stage), must_use_(must_use) {
+    SetReturnType(return_type);
+    for (auto* param : parameters) {
+        AddParameter(param);
+    }
     TINT_ASSERT(Semantic, return_type);
 }
 
 CallTarget::CallTarget(const CallTarget&) = default;
 CallTarget::~CallTarget() = default;
 
+CallTargetSignature::CallTargetSignature() = default;
+
 CallTargetSignature::CallTargetSignature(const type::Type* ret_ty,
                                          utils::VectorRef<const sem::Parameter*> params)
     : return_type(ret_ty), parameters(std::move(params)) {}
diff --git a/src/tint/sem/call_target.h b/src/tint/sem/call_target.h
index 4f0ab33..096aa8f 100644
--- a/src/tint/sem/call_target.h
+++ b/src/tint/sem/call_target.h
@@ -28,6 +28,9 @@
 /// CallTargetSignature holds the return type and parameters for a call target
 struct CallTargetSignature {
     /// Constructor
+    CallTargetSignature();
+
+    /// Constructor
     /// @param ret_ty the call target return type
     /// @param params the call target parameters
     CallTargetSignature(const type::Type* ret_ty, utils::VectorRef<const Parameter*> params);
@@ -39,9 +42,9 @@
     ~CallTargetSignature();
 
     /// The type of the call target return value
-    const type::Type* const return_type = nullptr;
+    const type::Type* return_type = nullptr;
     /// The parameters of the call target
-    const utils::Vector<const sem::Parameter*, 8> parameters;
+    utils::Vector<const sem::Parameter*, 8> parameters;
 
     /// Equality operator
     /// @param other the signature to compare this to
@@ -67,13 +70,19 @@
 class CallTarget : public Castable<CallTarget, Node> {
   public:
     /// Constructor
+    /// @param stage the earliest evaluation stage for a call to this target
+    /// @param must_use the result of the call target must be used, i.e. it cannot be used as a call
+    /// statement.
+    CallTarget(EvaluationStage stage, bool must_use);
+
+    /// Constructor
     /// @param return_type the return type of the call target
     /// @param parameters the parameters for the call target
     /// @param stage the earliest evaluation stage for a call to this target
     /// @param must_use the result of the call target must be used, i.e. it cannot be used as a call
     /// statement.
     CallTarget(const type::Type* return_type,
-               utils::VectorRef<const Parameter*> parameters,
+               utils::VectorRef<Parameter*> parameters,
                EvaluationStage stage,
                bool must_use);
 
@@ -83,9 +92,20 @@
     /// Destructor
     ~CallTarget() override;
 
+    /// Sets the call target's return type
+    /// @param ty the parameter
+    void SetReturnType(const type::Type* ty) { signature_.return_type = ty; }
+
     /// @return the return type of the call target
     const type::Type* ReturnType() const { return signature_.return_type; }
 
+    /// Adds a parameter to the call target
+    /// @param parameter the parameter
+    void AddParameter(Parameter* parameter) {
+        parameter->SetOwner(this);
+        signature_.parameters.Push(parameter);
+    }
+
     /// @return the parameters of the call target
     auto& Parameters() const { return signature_.parameters; }
 
diff --git a/src/tint/sem/function.cc b/src/tint/sem/function.cc
index 0859dd8..ec51b7b 100644
--- a/src/tint/sem/function.cc
+++ b/src/tint/sem/function.cc
@@ -28,29 +28,12 @@
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Function);
 
 namespace tint::sem {
-namespace {
 
-utils::VectorRef<const Parameter*> SetOwner(utils::VectorRef<Parameter*> parameters,
-                                            const tint::sem::CallTarget* owner) {
-    for (auto* parameter : parameters) {
-        parameter->SetOwner(owner);
-    }
-    return parameters;
-}
-
-}  // namespace
-
-Function::Function(const ast::Function* declaration,
-                   type::Type* return_type,
-                   std::optional<uint32_t> return_location,
-                   utils::VectorRef<Parameter*> parameters)
-    : Base(return_type,
-           SetOwner(std::move(parameters), this),
-           EvaluationStage::kRuntime,
+Function::Function(const ast::Function* declaration)
+    : Base(EvaluationStage::kRuntime,
            ast::HasAttribute<ast::MustUseAttribute>(declaration->attributes)),
       declaration_(declaration),
-      workgroup_size_{1, 1, 1},
-      return_location_(return_location) {}
+      workgroup_size_{1, 1, 1} {}
 
 Function::~Function() = default;
 
diff --git a/src/tint/sem/function.h b/src/tint/sem/function.h
index 528ebef..b157e7d 100644
--- a/src/tint/sem/function.h
+++ b/src/tint/sem/function.h
@@ -54,17 +54,15 @@
 
     /// Constructor
     /// @param declaration the ast::Function
-    /// @param return_type the return type of the function
-    /// @param return_location the location value for the return, if provided
-    /// @param parameters the parameters to the function
-    Function(const ast::Function* declaration,
-             type::Type* return_type,
-             std::optional<uint32_t> return_location,
-             utils::VectorRef<Parameter*> parameters);
+    explicit Function(const ast::Function* declaration);
 
     /// Destructor
     ~Function() override;
 
+    /// Sets the function's return location
+    /// @param return_location the location value
+    void SetReturnLocation(uint32_t return_location) { return_location_ = return_location; }
+
     /// @returns the ast::Function declaration
     const ast::Function* Declaration() const { return declaration_; }
 
diff --git a/src/tint/sem/value_constructor.cc b/src/tint/sem/value_constructor.cc
index 62be478..b270414 100644
--- a/src/tint/sem/value_constructor.cc
+++ b/src/tint/sem/value_constructor.cc
@@ -21,7 +21,7 @@
 namespace tint::sem {
 
 ValueConstructor::ValueConstructor(const type::Type* type,
-                                   utils::VectorRef<const Parameter*> parameters,
+                                   utils::VectorRef<Parameter*> parameters,
                                    EvaluationStage stage)
     : Base(type, std::move(parameters), stage, /* must_use */ true) {}
 
diff --git a/src/tint/sem/value_constructor.h b/src/tint/sem/value_constructor.h
index aef827b..34c3b43 100644
--- a/src/tint/sem/value_constructor.h
+++ b/src/tint/sem/value_constructor.h
@@ -28,7 +28,7 @@
     /// @param parameters the constructor parameters
     /// @param stage the earliest evaluation stage for the expression
     ValueConstructor(const type::Type* type,
-                     utils::VectorRef<const Parameter*> parameters,
+                     utils::VectorRef<Parameter*> parameters,
                      EvaluationStage stage);
 
     /// Destructor
diff --git a/src/tint/sem/value_conversion.cc b/src/tint/sem/value_conversion.cc
index 37331af..95587fe 100644
--- a/src/tint/sem/value_conversion.cc
+++ b/src/tint/sem/value_conversion.cc
@@ -19,9 +19,9 @@
 namespace tint::sem {
 
 ValueConversion::ValueConversion(const type::Type* type,
-                                 const sem::Parameter* parameter,
+                                 sem::Parameter* parameter,
                                  EvaluationStage stage)
-    : Base(type, utils::Vector<const sem::Parameter*, 1>{parameter}, stage, /* must_use */ true) {}
+    : Base(type, utils::Vector<sem::Parameter*, 1>{parameter}, stage, /* must_use */ true) {}
 
 ValueConversion::~ValueConversion() = default;
 
diff --git a/src/tint/sem/value_conversion.h b/src/tint/sem/value_conversion.h
index b79caa3..2d2ab38 100644
--- a/src/tint/sem/value_conversion.h
+++ b/src/tint/sem/value_conversion.h
@@ -26,7 +26,7 @@
     /// @param type the target type of the cast
     /// @param parameter the type cast parameter
     /// @param stage the earliest evaluation stage for the expression
-    ValueConversion(const type::Type* type, const sem::Parameter* parameter, EvaluationStage stage);
+    ValueConversion(const type::Type* type, sem::Parameter* parameter, EvaluationStage stage);
 
     /// Destructor
     ~ValueConversion() override;
diff --git a/src/tint/transform/add_block_attribute.cc b/src/tint/transform/add_block_attribute.cc
index c63bd04..c486936 100644
--- a/src/tint/transform/add_block_attribute.cc
+++ b/src/tint/transform/add_block_attribute.cc
@@ -102,7 +102,7 @@
 }
 
 AddBlockAttribute::BlockAttribute::BlockAttribute(ProgramID pid, ast::NodeID nid)
-    : Base(pid, nid) {}
+    : Base(pid, nid, utils::Empty) {}
 AddBlockAttribute::BlockAttribute::~BlockAttribute() = default;
 std::string AddBlockAttribute::BlockAttribute::InternalName() const {
     return "block";
diff --git a/src/tint/transform/calculate_array_length.cc b/src/tint/transform/calculate_array_length.cc
index a25c89e..9f5b659 100644
--- a/src/tint/transform/calculate_array_length.cc
+++ b/src/tint/transform/calculate_array_length.cc
@@ -71,7 +71,7 @@
 }  // namespace
 
 CalculateArrayLength::BufferSizeIntrinsic::BufferSizeIntrinsic(ProgramID pid, ast::NodeID nid)
-    : Base(pid, nid) {}
+    : Base(pid, nid, utils::Empty) {}
 CalculateArrayLength::BufferSizeIntrinsic::~BufferSizeIntrinsic() = default;
 std::string CalculateArrayLength::BufferSizeIntrinsic::InternalName() const {
     return "intrinsic_buffer_size";
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 0030ca1..f3d92c5 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -228,7 +228,7 @@
     }
     return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
         builder->ID(), builder->AllocateNodeID(), DecomposeMemoryAccess::Intrinsic::Op::kLoad, type,
-        address_space, buffer);
+        address_space, builder->Expr(buffer));
 }
 
 /// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied to a stub function to
@@ -242,7 +242,7 @@
     }
     return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
         builder->ID(), builder->AllocateNodeID(), DecomposeMemoryAccess::Intrinsic::Op::kStore,
-        type, builtin::AddressSpace::kStorage, buffer);
+        type, builtin::AddressSpace::kStorage, builder->Expr(buffer));
 }
 
 /// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied to a stub function for
@@ -299,7 +299,7 @@
     }
     return builder->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
         builder->ID(), builder->AllocateNodeID(), op, type, builtin::AddressSpace::kStorage,
-        buffer);
+        builder->Expr(buffer));
 }
 
 /// BufferAccess describes a single storage or uniform buffer access
@@ -692,8 +692,8 @@
                                             Op o,
                                             DataType ty,
                                             builtin::AddressSpace as,
-                                            const Symbol& buf)
-    : Base(pid, nid), op(o), type(ty), address_space(as), buffer(buf) {}
+                                            const ast::IdentifierExpression* buf)
+    : Base(pid, nid, utils::Vector{buf}), op(o), type(ty), address_space(as) {}
 DecomposeMemoryAccess::Intrinsic::~Intrinsic() = default;
 std::string DecomposeMemoryAccess::Intrinsic::InternalName() const {
     utils::StringStream ss;
@@ -794,7 +794,7 @@
 
 const DecomposeMemoryAccess::Intrinsic* DecomposeMemoryAccess::Intrinsic::Clone(
     CloneContext* ctx) const {
-    auto buf = ctx->Clone(buffer);
+    auto buf = ctx->Clone(Buffer());
     return ctx->dst->ASTNodes().Create<DecomposeMemoryAccess::Intrinsic>(
         ctx->dst->ID(), ctx->dst->AllocateNodeID(), op, type, address_space, buf);
 }
@@ -803,6 +803,10 @@
     return op != Op::kLoad && op != Op::kStore;
 }
 
+const ast::IdentifierExpression* DecomposeMemoryAccess::Intrinsic::Buffer() const {
+    return dependencies[0];
+}
+
 DecomposeMemoryAccess::DecomposeMemoryAccess() = default;
 DecomposeMemoryAccess::~DecomposeMemoryAccess() = default;
 
diff --git a/src/tint/transform/decompose_memory_access.h b/src/tint/transform/decompose_memory_access.h
index e34cd63..f85ad6d 100644
--- a/src/tint/transform/decompose_memory_access.h
+++ b/src/tint/transform/decompose_memory_access.h
@@ -80,13 +80,13 @@
         /// @param o the op of the intrinsic
         /// @param type the data type of the intrinsic
         /// @param address_space the address space of the buffer
-        /// @param buffer the storage or uniform buffer name
+        /// @param buffer the storage or uniform buffer identifier
         Intrinsic(ProgramID pid,
                   ast::NodeID nid,
                   Op o,
                   DataType type,
                   builtin::AddressSpace address_space,
-                  const Symbol& buffer);
+                  const ast::IdentifierExpression* buffer);
         /// Destructor
         ~Intrinsic() override;
 
@@ -102,6 +102,9 @@
         /// @return true if op is atomic
         bool IsAtomic() const;
 
+        /// @return the buffer that this intrinsic operates on
+        const ast::IdentifierExpression* Buffer() const;
+
         /// The op of the intrinsic
         const Op op;
 
@@ -110,9 +113,6 @@
 
         /// The address space of the buffer this intrinsic operates on
         const builtin::AddressSpace address_space;
-
-        /// The buffer name
-        const Symbol buffer;
     };
 
     /// Constructor
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index c6bf218..b79a029 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -20,10 +20,15 @@
 
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/builtin.h"
 #include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
 #include "src/tint/sem/index_accessor_expression.h"
+#include "src/tint/sem/load.h"
+#include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/value_expression.h"
+#include "src/tint/transform/utils/hoist_to_decl_before.h"
 #include "src/tint/type/reference.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness);
@@ -36,16 +41,147 @@
 /// PIMPL state for the transform
 struct Robustness::State {
     /// Constructor
-    /// @param program the source program
-    /// @param omitted the omitted address spaces
-    State(const Program* program, std::unordered_set<builtin::AddressSpace>&& omitted)
-        : src(program), omitted_address_spaces(std::move(omitted)) {}
+    /// @param p the source program
+    /// @param c the transform config
+    State(const Program* p, Config&& c) : src(p), cfg(std::move(c)) {}
 
     /// Runs the transform
     /// @returns the new program or SkipTransform if the transform is not required
     ApplyResult Run() {
-        ctx.ReplaceAll([&](const ast::IndexAccessorExpression* expr) { return Transform(expr); });
-        ctx.ReplaceAll([&](const ast::CallExpression* expr) { return Transform(expr); });
+        if (HasAction(Action::kPredicate)) {
+            AddPredicateParameters();
+        }
+
+        // Walk all the AST nodes in the module, starting with the leaf nodes.
+        // The most deeply nested expressions will come first.
+        for (auto* node : ctx.src->ASTNodes().Objects()) {
+            Switch(
+                node,  //
+                [&](const ast::IndexAccessorExpression* e) {
+                    // obj[idx]
+                    // Array, matrix and vector indexing may require robustness transformation.
+                    auto* expr = sem.Get(e)->Unwrap()->As<sem::IndexAccessorExpression>();
+                    switch (ActionFor(expr)) {
+                        case Action::kPredicate:
+                            PredicateIndexAccessor(expr);
+                            break;
+                        case Action::kClamp:
+                            ClampIndexAccessor(expr);
+                            break;
+                        case Action::kIgnore:
+                            break;
+                    }
+                },
+                [&](const ast::IdentifierExpression* e) {
+                    // Identifiers may resolve to pointer lets, which may be predicated.
+                    // Inspect.
+                    if (auto* user = sem.Get<sem::VariableUser>(e)) {
+                        auto* v = user->Variable();
+                        if (v->Type()->Is<type::Pointer>()) {
+                            // Propagate predicate from pointer
+                            if (auto pred = predicates.Get(v->Declaration()->initializer)) {
+                                predicates.Add(e, *pred);
+                            }
+                        }
+                    }
+                },
+                [&](const ast::AccessorExpression* e) {
+                    // obj.member
+                    // Propagate the predication from the object to this expression.
+                    if (auto pred = predicates.Get(e->object)) {
+                        predicates.Add(e, *pred);
+                    }
+                },
+                [&](const ast::UnaryOpExpression* e) {
+                    // Includes address-of, or indirection
+                    // Propagate the predication from the inner expression to this expression.
+                    if (auto pred = predicates.Get(e->expr)) {
+                        predicates.Add(e, *pred);
+                    }
+                },
+                [&](const ast::AssignmentStatement* s) {
+                    if (auto pred = predicates.Get(s->lhs)) {
+                        // Assignment target is predicated
+                        // Replace statement with condition on the predicate
+                        ctx.Replace(s, b.If(*pred, b.Block(ctx.Clone(s))));
+                    }
+                },
+                [&](const ast::CompoundAssignmentStatement* s) {
+                    if (auto pred = predicates.Get(s->lhs)) {
+                        // Assignment expression is predicated
+                        // Replace statement with condition on the predicate
+                        ctx.Replace(s, b.If(*pred, b.Block(ctx.Clone(s))));
+                    }
+                },
+                [&](const ast::IncrementDecrementStatement* s) {
+                    if (auto pred = predicates.Get(s->lhs)) {
+                        // Assignment expression is predicated
+                        // Replace statement with condition on the predicate
+                        ctx.Replace(s, b.If(*pred, b.Block(ctx.Clone(s))));
+                    }
+                },
+                [&](const ast::CallExpression* e) {
+                    if (auto* call = sem.Get<sem::Call>(e)) {
+                        Switch(
+                            call->Target(),  //
+                            [&](const sem::Builtin* builtin) {
+                                // Calls to builtins may require robustness transformation.
+                                // Inspect.
+                                if (builtin->IsTexture()) {
+                                    switch (cfg.texture_action) {
+                                        case Action::kPredicate:
+                                            PredicateTextureBuiltin(call, builtin);
+                                            break;
+                                        case Action::kClamp:
+                                            ClampTextureBuiltin(call, builtin);
+                                            break;
+                                        case Action::kIgnore:
+                                            break;
+                                    }
+                                } else {
+                                    MaybePredicateNonTextureBuiltin(call, builtin);
+                                }
+                            },
+                            [&](const sem::Function* fn) {
+                                // Calls to user function may require passing additional predicate
+                                // arguments.
+                                InsertPredicateArguments(call, fn);
+                            });
+                    }
+                });
+
+            // Check whether the node is an expression that:
+            // * Has a predicate
+            // * Is of a non-pointer or non-reference type
+            // If the above is true, then we need to predicate evaluation of this expression by
+            // replacing `expr` with `predicated_expr` and injecting the following above the
+            // expression's statement:
+            //
+            //   var predicated_expr : expr_ty;
+            //   if (predicate) {
+            //     predicated_expr = expr;
+            //   }
+            //
+            if (auto* expr = node->As<ast::Expression>()) {
+                if (auto pred = predicates.Get(expr)) {
+                    // Expression is predicated
+                    auto* sem_expr = sem.GetVal(expr);
+                    if (!sem_expr->Type()->IsAnyOf<type::Reference, type::Pointer>()) {
+                        auto pred_load = b.Symbols().New("predicated_expr");
+                        auto ty = CreateASTTypeFor(ctx, sem_expr->Type());
+                        hoist.InsertBefore(sem_expr->Stmt(), b.Decl(b.Var(pred_load, ty)));
+                        hoist.InsertBefore(
+                            sem_expr->Stmt(),
+                            b.If(*pred, b.Block(b.Assign(pred_load, ctx.Clone(expr)))));
+                        ctx.Replace(expr, b.Expr(pred_load));
+
+                        // The predication has been consumed for this expression.
+                        // Don't predicate expressions that use this expression.
+                        predicates.Remove(expr);
+                    }
+                }
+            }
+        }
 
         ctx.Clone();
         return Program(std::move(b));
@@ -54,227 +190,486 @@
   private:
     /// The source program
     const Program* const src;
+    /// The transform's config
+    Config cfg;
     /// The target program builder
-    ProgramBuilder b;
+    ProgramBuilder b{};
     /// The clone context
     CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    /// Helper for hoisting declarations
+    HoistToDeclBefore hoist{ctx};
+    /// Alias to the source program's semantic info
+    const sem::Info& sem = ctx.src->Sem();
+    /// Map of expression to predicate condition
+    utils::Hashmap<const ast::Expression*, Symbol, 32> predicates{};
 
-    /// Set of address spaces to not apply the transform to
-    std::unordered_set<builtin::AddressSpace> omitted_address_spaces;
-
-    /// Apply bounds clamping to array, vector and matrix indexing
-    /// @param expr the array, vector or matrix index expression
-    /// @return the clamped replacement expression, or nullptr if `expr` should be cloned without
-    /// changes.
-    const ast::IndexAccessorExpression* Transform(const ast::IndexAccessorExpression* expr) {
-        auto* sem = src->Sem().Get(expr)->Unwrap()->As<sem::IndexAccessorExpression>();
-        auto* ret_type = sem->Type();
-
-        auto* ref = ret_type->As<type::Reference>();
-        if (ref && omitted_address_spaces.count(ref->AddressSpace()) != 0) {
-            return nullptr;
-        }
-
-        // idx return the cloned index expression, as a u32.
-        auto idx = [&]() -> const ast::Expression* {
-            auto* i = ctx.Clone(expr->index);
-            if (sem->Index()->Type()->is_signed_integer_scalar()) {
-                return b.Call<u32>(i);  // u32(idx)
-            }
-            return i;
-        };
-
-        auto* clamped_idx = Switch(
-            sem->Object()->Type()->UnwrapRef(),  //
+    /// @return the `u32` typed expression that represents the maximum indexable value for the index
+    /// accessor @p expr, or nullptr if there is no robustness limit for this expression.
+    const ast::Expression* DynamicLimitFor(const sem::IndexAccessorExpression* expr) {
+        auto* obj_type = expr->Object()->Type();
+        return Switch(
+            obj_type->UnwrapRef(),  //
             [&](const type::Vector* vec) -> const ast::Expression* {
-                if (sem->Index()->ConstantValue()) {
+                if (expr->Index()->ConstantValue() || expr->Index()->Is<sem::Swizzle>()) {
                     // Index and size is constant.
                     // Validation will have rejected any OOB accesses.
                     return nullptr;
                 }
-
-                return b.Call("min", idx(), u32(vec->Width() - 1u));
+                return b.Expr(u32(vec->Width() - 1u));
             },
             [&](const type::Matrix* mat) -> const ast::Expression* {
-                if (sem->Index()->ConstantValue()) {
+                if (expr->Index()->ConstantValue()) {
                     // Index and size is constant.
                     // Validation will have rejected any OOB accesses.
                     return nullptr;
                 }
-
-                return b.Call("min", idx(), u32(mat->columns() - 1u));
+                return b.Expr(u32(mat->columns() - 1u));
             },
             [&](const type::Array* arr) -> const ast::Expression* {
-                const ast::Expression* max = nullptr;
                 if (arr->Count()->Is<type::RuntimeArrayCount>()) {
                     // Size is unknown until runtime.
                     // Must clamp, even if the index is constant.
-                    auto* arr_ptr = b.AddressOf(ctx.Clone(expr->object));
-                    max = b.Sub(b.Call("arrayLength", arr_ptr), 1_u);
-                } else if (auto count = arr->ConstantCount()) {
-                    if (sem->Index()->ConstantValue()) {
+
+                    auto* arr_ptr = b.AddressOf(ctx.Clone(expr->Object()->Declaration()));
+                    return b.Sub(b.Call(sem::BuiltinType::kArrayLength, arr_ptr), 1_u);
+                }
+                if (auto count = arr->ConstantCount()) {
+                    if (expr->Index()->ConstantValue()) {
                         // Index and size is constant.
                         // Validation will have rejected any OOB accesses.
                         return nullptr;
                     }
-                    max = b.Expr(u32(count.value() - 1u));
-                } else {
-                    // Note: Don't be tempted to use the array override variable as an expression
-                    // here, the name might be shadowed!
-                    b.Diagnostics().add_error(diag::System::Transform,
-                                              type::Array::kErrExpectedConstantCount);
-                    return nullptr;
+                    return b.Expr(u32(count.value() - 1u));
                 }
-
-                return b.Call("min", idx(), max);
+                // Note: Don't be tempted to use the array override variable as an expression here,
+                // the name might be shadowed!
+                b.Diagnostics().add_error(diag::System::Transform,
+                                          type::Array::kErrExpectedConstantCount);
+                return nullptr;
             },
-            [&](Default) {
+            [&](Default) -> const ast::Expression* {
                 TINT_ICE(Transform, b.Diagnostics())
                     << "unhandled object type in robustness of array index: "
-                    << src->FriendlyName(ret_type->UnwrapRef());
+                    << src->FriendlyName(obj_type->UnwrapRef());
                 return nullptr;
             });
-
-        if (!clamped_idx) {
-            return nullptr;  // Clamping not needed
-        }
-
-        auto idx_src = ctx.Clone(expr->source);
-        auto* idx_obj = ctx.Clone(expr->object);
-        return b.IndexAccessor(idx_src, idx_obj, clamped_idx);
     }
 
-    /// @param type builtin type
-    /// @returns true if the given builtin is a texture function that requires
-    /// argument clamping,
-    bool TextureBuiltinNeedsClamping(sem::BuiltinType type) {
-        return type == sem::BuiltinType::kTextureLoad || type == sem::BuiltinType::kTextureStore;
+    /// Transform the program to insert additional predicate parameters to all user functions that
+    /// have a pointer parameter type in an address space that has predicate action.
+    void AddPredicateParameters() {
+        for (auto* fn : src->AST().Functions()) {
+            for (auto* param : fn->params) {
+                auto* sem_param = sem.Get(param);
+                if (auto* ptr = sem_param->Type()->As<type::Pointer>()) {
+                    if (ActionFor(ptr->AddressSpace()) == Action::kPredicate) {
+                        auto name = b.Symbols().New(src->Symbols().NameFor(param->name->symbol) +
+                                                    "_predicate");
+                        ctx.InsertAfter(fn->params, param, b.Param(name, b.ty.bool_()));
+
+                        // Associate the pointer parameter expressions with the predicate.
+                        for (auto* user : sem_param->Users()) {
+                            predicates.Add(user->Declaration(), name);
+                        }
+                    }
+                }
+            }
+        }
     }
 
-    /// Apply bounds clamping to the coordinates, array index and level arguments
-    /// of the `textureLoad()` and `textureStore()` builtins.
-    /// @param expr the builtin call expression
-    /// @return the clamped replacement call expression, or nullptr if `expr`
-    /// should be cloned without changes.
-    const ast::CallExpression* Transform(const ast::CallExpression* expr) {
-        auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
-        auto* call_target = call->Target();
-        auto* builtin = call_target->As<sem::Builtin>();
-        if (!builtin || !TextureBuiltinNeedsClamping(builtin->Type())) {
-            return nullptr;  // No transform, just clone.
+    /// Transforms call expressions to user functions, inserting additional predicate arguments
+    /// after all pointer parameters with a type in an address space that has predicate action.
+    void InsertPredicateArguments(const sem::Call* call, const sem::Function* fn) {
+        auto* expr = call->Declaration();
+        for (size_t i = 0; i < fn->Parameters().Length(); i++) {
+            auto* param = fn->Parameters()[i];
+            if (auto* ptr = param->Type()->As<type::Pointer>()) {
+                if (ActionFor(ptr->AddressSpace()) == Action::kPredicate) {
+                    auto* arg = expr->args[i];
+                    if (auto predicate = predicates.Get(arg)) {
+                        ctx.InsertAfter(expr->args, arg, b.Expr(*predicate));
+                    } else {
+                        ctx.InsertAfter(expr->args, arg, b.Expr(true));
+                    }
+                }
+            }
         }
+    }
+
+    /// Applies predication to the index on an array, vector or matrix.
+    /// @param expr the index accessor expression.
+    void PredicateIndexAccessor(const sem::IndexAccessorExpression* expr) {
+        auto* obj = expr->Object()->Declaration();
+        auto* idx = expr->Index()->Declaration();
+        auto* max = DynamicLimitFor(expr);
+        if (!max) {
+            // robustness is not required
+            // Just propagate predicate from object
+            if (auto pred = predicates.Get(obj)) {
+                predicates.Add(expr->Declaration(), *pred);
+            }
+            return;
+        }
+
+        auto* stmt = expr->Stmt();
+        auto obj_pred = *predicates.GetOrZero(obj);
+
+        auto idx_let = b.Symbols().New("index");
+        auto pred = b.Symbols().New("predicate");
+
+        hoist.InsertBefore(stmt, b.Decl(b.Let(idx_let, ctx.Clone(idx))));
+        ctx.Replace(idx, b.Expr(idx_let));
+
+        auto* cond = b.LessThanEqual(b.Call<u32>(b.Expr(idx_let)), max);
+        if (obj_pred.IsValid()) {
+            cond = b.And(b.Expr(obj_pred), cond);
+        }
+        hoist.InsertBefore(stmt, b.Decl(b.Let(pred, cond)));
+
+        predicates.Add(expr->Declaration(), pred);
+    }
+
+    /// Applies bounds clamping to the index on an array, vector or matrix.
+    /// @param expr the index accessor expression.
+    void ClampIndexAccessor(const sem::IndexAccessorExpression* expr) {
+        auto* max = DynamicLimitFor(expr);
+        if (!max) {
+            return;  // robustness is not required
+        }
+
+        auto* expr_sem = expr->Unwrap()->As<sem::IndexAccessorExpression>();
+
+        auto idx = ctx.Clone(expr->Declaration()->index);
+        if (expr_sem->Index()->Type()->is_signed_integer_scalar()) {
+            idx = b.Call<u32>(idx);  // u32(idx)
+        }
+        auto* clamped_idx = b.Call(sem::BuiltinType::kMin, idx, max);
+        ctx.Replace(expr->Declaration()->index, clamped_idx);
+    }
+
+    /// Applies predication to the non-texture builtin call, if required.
+    void MaybePredicateNonTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
+        // Gather the predications for the builtin arguments
+        const ast::Expression* predicate = nullptr;
+        for (auto* arg : call->Declaration()->args) {
+            if (auto pred = predicates.Get(arg)) {
+                predicate = And(predicate, b.Expr(*pred));
+            }
+        }
+
+        if (predicate) {
+            if (builtin->Type() == sem::BuiltinType::kWorkgroupUniformLoad) {
+                // https://www.w3.org/TR/WGSL/#workgroupUniformLoad-builtin:
+                //  "Executes a control barrier synchronization function that affects memory and
+                //   atomic operations in the workgroup address space."
+                // Because the call acts like a control barrier, we need to make sure that we still
+                // trigger a workgroup barrier if the predicate fails.
+                PredicateCall(call, predicate,
+                              b.Block(b.CallStmt(b.Call(sem::BuiltinType::kWorkgroupBarrier))));
+            } else {
+                PredicateCall(call, predicate);
+            }
+        }
+    }
+
+    /// Applies predication to texture builtins, based on whether the coordinates, array index and
+    /// level arguments are all in bounds.
+    void PredicateTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
+        if (!TextureBuiltinNeedsRobustness(builtin->Type())) {
+            return;
+        }
+
+        auto* expr = call->Declaration();
+        auto* stmt = call->Stmt();
 
         // Indices of the mandatory texture and coords parameters, and the optional
         // array and level parameters.
         auto& signature = builtin->Signature();
-        auto texture_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
-        auto coords_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
-        auto array_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
-        auto level_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
+        auto texture_arg_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
+        auto coords_arg_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
+        auto array_arg_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
+        auto level_arg_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
 
-        auto* texture_arg = expr->args[static_cast<size_t>(texture_idx)];
-        auto* coords_arg = expr->args[static_cast<size_t>(coords_idx)];
-        auto* coords_ty = builtin->Parameters()[static_cast<size_t>(coords_idx)]->Type();
+        auto* texture_arg = expr->args[static_cast<size_t>(texture_arg_idx)];
 
-        auto width_of = [&](const type::Type* ty) {
-            if (auto* vec = ty->As<type::Vector>()) {
-                return vec->Width();
+        // Build the builtin predicate from the arguments
+        const ast::Expression* predicate = nullptr;
+
+        Symbol level_idx, num_levels;
+        if (level_arg_idx >= 0) {
+            auto* param = builtin->Parameters()[static_cast<size_t>(level_arg_idx)];
+            if (param->Type()->is_integer_scalar()) {
+                // let level_idx = u32(level-arg);
+                level_idx = b.Symbols().New("level_idx");
+                auto* arg = expr->args[static_cast<size_t>(level_arg_idx)];
+                hoist.InsertBefore(stmt,
+                                   b.Decl(b.Let(level_idx, CastToUnsigned(ctx.Clone(arg), 1u))));
+
+                // let num_levels = textureNumLevels(texture-arg);
+                num_levels = b.Symbols().New("num_levels");
+                hoist.InsertBefore(
+                    stmt, b.Decl(b.Let(num_levels, b.Call(sem::BuiltinType::kTextureNumLevels,
+                                                          ctx.Clone(texture_arg)))));
+
+                // predicate: level_idx < num_levels
+                predicate = And(predicate, b.LessThan(level_idx, num_levels));
+
+                // Replace the level argument with `level_idx`
+                ctx.Replace(arg, b.Expr(level_idx));
             }
-            return 1u;
-        };
-        auto scalar_or_vec_ty = [&](ast::Type scalar, uint32_t width) {
-            if (width > 1) {
-                return b.ty.vec(scalar, width);
+        }
+
+        Symbol coords;
+        if (coords_arg_idx >= 0) {
+            auto* param = builtin->Parameters()[static_cast<size_t>(coords_arg_idx)];
+            if (param->Type()->is_integer_scalar_or_vector()) {
+                // let coords = u32(coords-arg)
+                coords = b.Symbols().New("coords");
+                auto* arg = expr->args[static_cast<size_t>(coords_arg_idx)];
+                hoist.InsertBefore(stmt,
+                                   b.Decl(b.Let(coords, CastToUnsigned(b.Expr(ctx.Clone(arg)),
+                                                                       WidthOf(param->Type())))));
+
+                // predicate: all(coords < textureDimensions(texture))
+                auto* dimensions =
+                    level_idx.IsValid()
+                        ? b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg),
+                                 b.Call(sem::BuiltinType::kMin, b.Expr(level_idx),
+                                        b.Sub(num_levels, 1_a)))
+                        : b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg));
+                predicate =
+                    And(predicate, b.Call(sem::BuiltinType::kAll, b.LessThan(coords, dimensions)));
+
+                // Replace the level argument with `coord`
+                ctx.Replace(arg, b.Expr(coords));
             }
-            return scalar;
-        };
-        auto scalar_or_vec = [&](const ast::Expression* scalar,
-                                 uint32_t width) -> const ast::Expression* {
-            if (width > 1) {
-                return b.Call(b.ty.vec<Infer>(width), scalar);
+        }
+
+        if (array_arg_idx >= 0) {
+            // let array_idx = u32(array-arg)
+            auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
+            auto* num_layers = b.Call(sem::BuiltinType::kTextureNumLayers, ctx.Clone(texture_arg));
+            auto array_idx = b.Symbols().New("array_idx");
+            hoist.InsertBefore(stmt, b.Decl(b.Let(array_idx, CastToUnsigned(ctx.Clone(arg), 1u))));
+
+            // predicate: array_idx < textureNumLayers(texture)
+            predicate = And(predicate, b.LessThan(array_idx, num_layers));
+
+            // Replace the array index argument with `array_idx`
+            ctx.Replace(arg, b.Expr(array_idx));
+        }
+
+        if (predicate) {
+            PredicateCall(call, predicate);
+        }
+    }
+
+    /// Applies bounds clamping to the coordinates, array index and level arguments of the texture
+    /// builtin.
+    void ClampTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
+        if (!TextureBuiltinNeedsRobustness(builtin->Type())) {
+            return;
+        }
+
+        auto* expr = call->Declaration();
+        auto* stmt = call->Stmt();
+
+        // Indices of the mandatory texture and coords parameters, and the optional
+        // array and level parameters.
+        auto& signature = builtin->Signature();
+        auto texture_arg_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
+        auto coords_arg_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
+        auto array_arg_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
+        auto level_arg_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
+
+        auto* texture_arg = expr->args[static_cast<size_t>(texture_arg_idx)];
+
+        // If the level is provided, then we need to clamp this. As the level is used by
+        // textureDimensions() and the texture[Load|Store]() calls, we need to clamp both usages.
+        Symbol level_idx;
+        if (level_arg_idx >= 0) {
+            const auto* param = builtin->Parameters()[static_cast<size_t>(level_arg_idx)];
+            if (param->Type()->is_integer_scalar()) {
+                const auto* arg = expr->args[static_cast<size_t>(level_arg_idx)];
+                level_idx = b.Symbols().New("level_idx");
+                const auto* num_levels =
+                    b.Call(sem::BuiltinType::kTextureNumLevels, ctx.Clone(texture_arg));
+                const auto* max = b.Sub(num_levels, 1_a);
+                hoist.InsertBefore(
+                    stmt, b.Decl(b.Let(level_idx, b.Call(sem::BuiltinType::kMin,
+                                                         b.Call<u32>(ctx.Clone(arg)), max))));
+                ctx.Replace(arg, b.Expr(level_idx));
             }
-            return scalar;
-        };
-        auto cast_to_signed = [&](const ast::Expression* val, uint32_t width) {
-            return b.Call(scalar_or_vec_ty(b.ty.i32(), width), val);
-        };
-        auto cast_to_unsigned = [&](const ast::Expression* val, uint32_t width) {
-            return b.Call(scalar_or_vec_ty(b.ty.u32(), width), val);
-        };
-
-        // If the level is provided, then we need to clamp this. As the level is
-        // used by textureDimensions() and the texture[Load|Store]() calls, we need
-        // to clamp both usages.
-        // TODO(bclayton): We probably want to place this into a let so that the
-        // calculation can be reused. This is fiddly to get right.
-        std::function<const ast::Expression*()> level_arg;
-        if (level_idx >= 0) {
-            level_arg = [&] {
-                const auto* arg = expr->args[static_cast<size_t>(level_idx)];
-                const auto* target_ty =
-                    builtin->Parameters()[static_cast<size_t>(level_idx)]->Type();
-                const auto* num_levels = b.Call("textureNumLevels", ctx.Clone(texture_arg));
-
-                // TODO(crbug.com/tint/1526) remove when num_levels returns u32
-                num_levels = cast_to_unsigned(num_levels, 1u);
-
-                const auto* unsigned_max = b.Sub(num_levels, 1_a);
-                if (target_ty->is_signed_integer_scalar()) {
-                    const auto* signed_max = cast_to_signed(unsigned_max, 1u);
-                    return b.Call("clamp", ctx.Clone(arg), 0_a, signed_max);
-                } else {
-                    return b.Call("min", ctx.Clone(arg), unsigned_max);
-                }
-            };
         }
 
         // Clamp the coordinates argument
-        {
-            const auto* target_ty = coords_ty;
-            const auto width = width_of(target_ty);
-            const auto* texture_dims =
-                level_arg ? b.Call("textureDimensions", ctx.Clone(texture_arg), level_arg())
-                          : b.Call("textureDimensions", ctx.Clone(texture_arg));
+        if (coords_arg_idx >= 0) {
+            const auto* param = builtin->Parameters()[static_cast<size_t>(coords_arg_idx)];
+            if (param->Type()->is_integer_scalar_or_vector()) {
+                auto* arg = expr->args[static_cast<size_t>(coords_arg_idx)];
+                const auto width = WidthOf(param->Type());
+                const auto* dimensions =
+                    level_idx.IsValid()
+                        ? b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg),
+                                 level_idx)
+                        : b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg));
 
-            // TODO(crbug.com/tint/1526) remove when texture_dims returns u32 or vecN<u32>
-            texture_dims = cast_to_unsigned(texture_dims, width);
-
-            // texture_dims is u32 or vecN<u32>
-            const auto* unsigned_max = b.Sub(texture_dims, scalar_or_vec(b.Expr(1_a), width));
-            if (target_ty->is_signed_integer_scalar_or_vector()) {
-                const auto* zero = scalar_or_vec(b.Expr(0_a), width);
-                const auto* signed_max = cast_to_signed(unsigned_max, width);
-                ctx.Replace(coords_arg, b.Call("clamp", ctx.Clone(coords_arg), zero, signed_max));
-            } else {
-                ctx.Replace(coords_arg, b.Call("min", ctx.Clone(coords_arg), unsigned_max));
+                // dimensions is u32 or vecN<u32>
+                const auto* unsigned_max = b.Sub(dimensions, ScalarOrVec(b.Expr(1_a), width));
+                if (param->Type()->is_signed_integer_scalar_or_vector()) {
+                    const auto* zero = ScalarOrVec(b.Expr(0_a), width);
+                    const auto* signed_max = CastToSigned(unsigned_max, width);
+                    ctx.Replace(arg,
+                                b.Call(sem::BuiltinType::kClamp, ctx.Clone(arg), zero, signed_max));
+                } else {
+                    ctx.Replace(arg, b.Call(sem::BuiltinType::kMin, ctx.Clone(arg), unsigned_max));
+                }
             }
         }
 
         // Clamp the array_index argument, if provided
-        if (array_idx >= 0) {
-            auto* target_ty = builtin->Parameters()[static_cast<size_t>(array_idx)]->Type();
-            auto* arg = expr->args[static_cast<size_t>(array_idx)];
-            auto* num_layers = b.Call("textureNumLayers", ctx.Clone(texture_arg));
-
-            // TODO(crbug.com/tint/1526) remove when num_layers returns u32
-            num_layers = cast_to_unsigned(num_layers, 1u);
+        if (array_arg_idx >= 0) {
+            auto* param = builtin->Parameters()[static_cast<size_t>(array_arg_idx)];
+            auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
+            auto* num_layers = b.Call(sem::BuiltinType::kTextureNumLayers, ctx.Clone(texture_arg));
 
             const auto* unsigned_max = b.Sub(num_layers, 1_a);
-            if (target_ty->is_signed_integer_scalar()) {
-                const auto* signed_max = cast_to_signed(unsigned_max, 1u);
-                ctx.Replace(arg, b.Call("clamp", ctx.Clone(arg), 0_a, signed_max));
+            if (param->Type()->is_signed_integer_scalar()) {
+                const auto* signed_max = CastToSigned(unsigned_max, 1u);
+                ctx.Replace(arg, b.Call(sem::BuiltinType::kClamp, ctx.Clone(arg), 0_a, signed_max));
             } else {
-                ctx.Replace(arg, b.Call("min", ctx.Clone(arg), unsigned_max));
+                ctx.Replace(arg, b.Call(sem::BuiltinType::kMin, ctx.Clone(arg), unsigned_max));
             }
         }
+    }
 
-        // Clamp the level argument, if provided
-        if (level_idx >= 0) {
-            auto* arg = expr->args[static_cast<size_t>(level_idx)];
-            ctx.Replace(arg, level_arg ? level_arg() : b.Expr(0_a));
+    /// @param type builtin type
+    /// @returns true if the given builtin is a texture function that requires predication or
+    /// clamping of arguments.
+    bool TextureBuiltinNeedsRobustness(sem::BuiltinType type) {
+        return type == sem::BuiltinType::kTextureLoad || type == sem::BuiltinType::kTextureStore ||
+               type == sem::BuiltinType::kTextureDimensions;
+    }
+
+    /// @returns a bitwise and of the two expressions, or the other expression if one is null.
+    const ast::Expression* And(const ast::Expression* lhs, const ast::Expression* rhs) {
+        if (lhs && rhs) {
+            return b.And(lhs, rhs);
         }
+        if (lhs) {
+            return lhs;
+        }
+        return rhs;
+    }
 
-        return nullptr;  // Clone, which will use the argument replacements above.
+    /// Transforms a call statement or expression so that the expression is predicated by @p
+    /// predicate.
+    /// @param else_stmt - the statement to execute for the predication failure
+    void PredicateCall(const sem::Call* call,
+                       const ast::Expression* predicate,
+                       const ast::BlockStatement* else_stmt = nullptr) {
+        auto* expr = call->Declaration();
+        auto* stmt = call->Stmt();
+        auto* call_stmt = stmt->Declaration()->As<ast::CallStatement>();
+        if (call_stmt && call_stmt->expr == expr) {
+            // Wrap the statement in an if-statement with the predicate condition.
+            hoist.Replace(stmt, b.If(predicate, b.Block(ctx.Clone(stmt->Declaration())),
+                                     ProgramBuilder::ElseStmt(else_stmt)));
+        } else {
+            // Emit the following before the expression's statement:
+            //   var predicated_value : return-type;
+            //   if (predicate) {
+            //     predicated_value = call(...);
+            //   }
+            auto value = b.Symbols().New("predicated_value");
+            hoist.InsertBefore(stmt, b.Decl(b.Var(value, CreateASTTypeFor(ctx, call->Type()))));
+            hoist.InsertBefore(stmt, b.If(predicate, b.Block(b.Assign(value, ctx.Clone(expr))),
+                                          ProgramBuilder::ElseStmt(else_stmt)));
+
+            // Replace the call expression with `predicated_value`
+            ctx.Replace(expr, b.Expr(value));
+        }
+    }
+
+    /// @returns true if @p action is enabled for any address space
+    bool HasAction(Action action) const {
+        return action == cfg.function_action ||       //
+               action == cfg.texture_action ||        //
+               action == cfg.private_action ||        //
+               action == cfg.push_constant_action ||  //
+               action == cfg.storage_action ||        //
+               action == cfg.uniform_action ||        //
+               action == cfg.workgroup_action;
+    }
+
+    /// @returns the robustness action to perform for an OOB access with the expression @p expr
+    Action ActionFor(const sem::ValueExpression* expr) {
+        return Switch(
+            expr->Type(),  //
+            [&](const type::Reference* t) { return ActionFor(t->AddressSpace()); },
+            [&](Default) { return cfg.value_action; });
+    }
+
+    /// @returns the robustness action to perform for an OOB access in the address space @p
+    /// address_space
+    Action ActionFor(builtin::AddressSpace address_space) {
+        switch (address_space) {
+            case builtin::AddressSpace::kFunction:
+                return cfg.function_action;
+            case builtin::AddressSpace::kHandle:
+                return cfg.texture_action;
+            case builtin::AddressSpace::kPrivate:
+                return cfg.private_action;
+            case builtin::AddressSpace::kPushConstant:
+                return cfg.push_constant_action;
+            case builtin::AddressSpace::kStorage:
+                return cfg.storage_action;
+            case builtin::AddressSpace::kUniform:
+                return cfg.uniform_action;
+            case builtin::AddressSpace::kWorkgroup:
+                return cfg.workgroup_action;
+            default:
+                break;
+        }
+        TINT_UNREACHABLE(Transform, b.Diagnostics()) << "unhandled address space" << address_space;
+        return Action::kDefault;
+    }
+
+    /// @returns the vector width of @p ty, or 1 if @p ty is not a vector
+    static uint32_t WidthOf(const type::Type* ty) {
+        if (auto* vec = ty->As<type::Vector>()) {
+            return vec->Width();
+        }
+        return 1u;
+    }
+
+    /// @returns a scalar or vector type with the element type @p scalar and width @p width
+    ast::Type ScalarOrVecTy(ast::Type scalar, uint32_t width) const {
+        if (width > 1) {
+            return b.ty.vec(scalar, width);
+        }
+        return scalar;
+    }
+
+    /// @returns a vector constructed with the scalar expression @p scalar if @p width > 1,
+    /// otherwise returns @p scalar.
+    const ast::Expression* ScalarOrVec(const ast::Expression* scalar, uint32_t width) {
+        if (width > 1) {
+            return b.Call(b.ty.vec<Infer>(width), scalar);
+        }
+        return scalar;
+    }
+
+    /// @returns @p val cast to a `vecN<i32>`, where `N` is @p width, or cast to i32 if @p width
+    /// is 1.
+    const ast::CallExpression* CastToSigned(const ast::Expression* val, uint32_t width) {
+        return b.Call(ScalarOrVecTy(b.ty.i32(), width), val);
+    }
+
+    /// @returns @p val cast to a `vecN<u32>`, where `N` is @p width, or cast to u32 if @p width
+    /// is 1.
+    const ast::CallExpression* CastToUnsigned(const ast::Expression* val, uint32_t width) {
+        return b.Call(ScalarOrVecTy(b.ty.u32(), width), val);
     }
 };
 
@@ -294,19 +689,7 @@
         cfg = *cfg_data;
     }
 
-    std::unordered_set<builtin::AddressSpace> omitted_address_spaces;
-    for (auto sc : cfg.omitted_address_spaces) {
-        switch (sc) {
-            case AddressSpace::kUniform:
-                omitted_address_spaces.insert(builtin::AddressSpace::kUniform);
-                break;
-            case AddressSpace::kStorage:
-                omitted_address_spaces.insert(builtin::AddressSpace::kStorage);
-                break;
-        }
-    }
-
-    return State{src, std::move(omitted_address_spaces)}.Run();
+    return State{src, std::move(cfg)}.Run();
 }
 
 }  // namespace tint::transform
diff --git a/src/tint/transform/robustness.h b/src/tint/transform/robustness.h
index 780eab9..596db4d 100644
--- a/src/tint/transform/robustness.h
+++ b/src/tint/transform/robustness.h
@@ -15,29 +15,33 @@
 #ifndef SRC_TINT_TRANSFORM_ROBUSTNESS_H_
 #define SRC_TINT_TRANSFORM_ROBUSTNESS_H_
 
-#include <unordered_set>
-
 #include "src/tint/transform/transform.h"
 
-// Forward declarations
-namespace tint::ast {
-class IndexAccessorExpression;
-class CallExpression;
-}  // namespace tint::ast
-
 namespace tint::transform {
 
-/// This transform is responsible for clamping all array accesses to be within
-/// the bounds of the array. Any access before the start of the array will clamp
-/// to zero and any access past the end of the array will clamp to
-/// (array length - 1).
-/// @note This transform must come before the BuiltinPolyfill transform
+/// This transform is responsible for ensuring that all out of bounds accesses are prevented,
+/// either by conditioning the access (predication) or through clamping of the index to keep the
+/// access in bounds.
+/// @note Robustness must come after:
+///       * PromoteSideEffectsToDecl as Robustness requires side-effecting expressions to be hoisted
+///         to their own statements.
+///       Robustness must come before:
+///       * BuiltinPolyfill as 'clamp' and binary operators may need to be polyfilled.
+///       * CanonicalizeEntryPointIO as the transform does not support the 'in' and 'out' address
+///         spaces.
 class Robustness final : public Castable<Robustness, Transform> {
   public:
-    /// Address space to be skipped in the transform
-    enum class AddressSpace {
-        kUniform,
-        kStorage,
+    /// Robustness action for out-of-bounds indexing.
+    enum class Action {
+        /// Do nothing to prevent the out-of-bounds action.
+        kIgnore,
+        /// Clamp the index to be within bounds.
+        kClamp,
+        /// Do not execute the read or write if the index is out-of-bounds.
+        kPredicate,
+
+        /// The default action
+        kDefault = kClamp,
     };
 
     /// Configuration options for the transform
@@ -55,9 +59,24 @@
         /// @returns this Config
         Config& operator=(const Config&);
 
-        /// Address spaces to omit from apply the transform to.
-        /// This allows for optimizing on hardware that provide safe accesses.
-        std::unordered_set<AddressSpace> omitted_address_spaces;
+        /// Robustness action for values
+        Action value_action = Action::kDefault;
+
+        /// Robustness action for non-sampling texture operations
+        Action texture_action = Action::kDefault;
+
+        /// Robustness action for variables in the 'function' address space
+        Action function_action = Action::kDefault;
+        /// Robustness action for variables in the 'private' address space
+        Action private_action = Action::kDefault;
+        /// Robustness action for variables in the 'push_constant' address space
+        Action push_constant_action = Action::kDefault;
+        /// Robustness action for variables in the 'storage' address space
+        Action storage_action = Action::kDefault;
+        /// Robustness action for variables in the 'uniform' address space
+        Action uniform_action = Action::kDefault;
+        /// Robustness action for variables in the 'workgroup' address space
+        Action workgroup_action = Action::kDefault;
     };
 
     /// Constructor
diff --git a/src/tint/transform/robustness_test.cc b/src/tint/transform/robustness_test.cc
index 9bb9527..ee583cf 100644
--- a/src/tint/transform/robustness_test.cc
+++ b/src/tint/transform/robustness_test.cc
@@ -17,11 +17,345 @@
 #include "src/tint/transform/test_helper.h"
 
 namespace tint::transform {
+
+static std::ostream& operator<<(std::ostream& out, Robustness::Action action) {
+    switch (action) {
+        case Robustness::Action::kIgnore:
+            return out << "ignore";
+        case Robustness::Action::kClamp:
+            return out << "clamp";
+        case Robustness::Action::kPredicate:
+            return out << "predicate";
+    }
+    return out << "unknown";
+}
+
 namespace {
 
-using RobustnessTest = TransformTest;
+DataMap Config(Robustness::Action action) {
+    Robustness::Config cfg;
+    cfg.value_action = action;
+    cfg.texture_action = action;
+    cfg.function_action = action;
+    cfg.private_action = action;
+    cfg.push_constant_action = action;
+    cfg.storage_action = action;
+    cfg.uniform_action = action;
+    cfg.workgroup_action = action;
+    DataMap data;
+    data.Add<Robustness::Config>(cfg);
+    return data;
+}
 
-TEST_F(RobustnessTest, Array_Let_Idx_Clamp) {
+const char* Expect(Robustness::Action action,
+                   const char* expect_ignore,
+                   const char* expect_clamp,
+                   const char* expect_predicate) {
+    switch (action) {
+        case Robustness::Action::kIgnore:
+            return expect_ignore;
+        case Robustness::Action::kClamp:
+            return expect_clamp;
+        case Robustness::Action::kPredicate:
+            return expect_predicate;
+    }
+    return "<invalid action>";
+}
+
+using RobustnessTest = TransformTestWithParam<Robustness::Action>;
+
+////////////////////////////////////////////////////////////////////////////////
+// Constant sized array
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayVal_IndexWithLiteral) {
+    auto* src = R"(
+fn f() {
+  var b : f32 = array<f32, 3>()[1i];
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayVal_IndexWithConst) {
+    auto* src = R"(
+const c : u32 = 1u;
+
+fn f() {
+  let b : f32 = array<f32, 3>()[c];
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayVal_IndexWithLet) {
+    auto* src = R"(
+fn f() {
+  let l : u32 = 1u;
+  let b : f32 = array<f32, 3>()[l];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+fn f() {
+  let l : u32 = 1u;
+  let b : f32 = array<f32, 3>()[min(l, 2u)];
+}
+)",
+                          /* predicate */ R"(
+fn f() {
+  let l : u32 = 1u;
+  let index = l;
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = array<f32, 3>()[index];
+  }
+  let b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayVal_IndexWithRuntimeArrayIndex) {
+    auto* src = R"(
+var<private> i : u32;
+
+fn f() {
+  let a = array<f32, 3>();
+  let b = array<i32, 5>();
+  var c : f32 = a[b[i]];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> i : u32;
+
+fn f() {
+  let a = array<f32, 3>();
+  let b = array<i32, 5>();
+  var c : f32 = a[min(u32(b[min(i, 4u)]), 2u)];
+}
+)",
+                          /* predicate */ R"(
+var<private> i : u32;
+
+fn f() {
+  let a = array<f32, 3>();
+  let b = array<i32, 5>();
+  let index = i;
+  let predicate = (u32(index) <= 4u);
+  var predicated_expr : i32;
+  if (predicate) {
+    predicated_expr = b[index];
+  }
+  let index_1 = predicated_expr;
+  let predicate_1 = (u32(index_1) <= 2u);
+  var predicated_expr_1 : f32;
+  if (predicate_1) {
+    predicated_expr_1 = a[index_1];
+  }
+  var c : f32 = predicated_expr_1;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayVal_IndexWithRuntimeExpression) {
+    auto* src = R"(
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = array<f32, 3>()[((c + 2) - 3)];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = array<f32, 3>()[min(u32(((c + 2) - 3)), 2u)];
+}
+)",
+                          /* predicate */ R"(
+var<private> c : i32;
+
+fn f() {
+  let index = ((c + 2) - 3);
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = array<f32, 3>()[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_NestedConstantSizedArraysVal_IndexWithRuntimeExpressions) {
+    auto* src = R"(
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let a = array<array<array<f32, 1>, 2>, 3>();
+  var r = a[x][y][z];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let a = array<array<array<f32, 1>, 2>, 3>();
+  var r = a[min(u32(x), 2u)][min(u32(y), 1u)][min(u32(z), 0u)];
+}
+)",
+                          /* predicate */ R"(
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let a = array<array<array<f32, 1>, 2>, 3>();
+  let index = x;
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : array<array<f32, 1u>, 2u>;
+  if (predicate) {
+    predicated_expr = a[index];
+  }
+  let index_1 = y;
+  let predicate_1 = (u32(index_1) <= 1u);
+  var predicated_expr_1 : array<f32, 1u>;
+  if (predicate_1) {
+    predicated_expr_1 = predicated_expr[index_1];
+  }
+  let index_2 = z;
+  let predicate_2 = (u32(index_2) <= 0u);
+  var predicated_expr_2 : f32;
+  if (predicate_2) {
+    predicated_expr_2 = predicated_expr_1[index_2];
+  }
+  var r = predicated_expr_2;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayVal_IndexWithOverride) {
+    auto* src = R"(
+@id(1300) override idx : i32;
+
+fn f() {
+  let a = array<f32, 4>();
+  var b : f32 = a[idx];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@id(1300) override idx : i32;
+
+fn f() {
+  let a = array<f32, 4>();
+  var b : f32 = a[min(u32(idx), 3u)];
+}
+)",
+                          /* predicate */ R"(
+@id(1300) override idx : i32;
+
+fn f() {
+  let a = array<f32, 4>();
+  let index = idx;
+  let predicate = (u32(index) <= 3u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithLiteral) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  var b : f32 = a[1i];
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithConst) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+const c : u32 = 1u;
+
+fn f() {
+  let b : f32 = a[c];
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithLet) {
     auto* src = R"(
 var<private> a : array<f32, 3>;
 
@@ -31,97 +365,37 @@
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 var<private> a : array<f32, 3>;
 
 fn f() {
   let l : u32 = 1u;
   let b : f32 = a[min(l, 2u)];
 }
-)";
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
 
-    auto got = Run<Robustness>(src);
+fn f() {
+  let l : u32 = 1u;
+  let index = l;
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index];
+  }
+  let b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Array_Let_Idx_Clamp_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  let c : u32 = 1u;
-  let b : f32 = a[c];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  let c : u32 = 1u;
-  let b : f32 = a[min(c, 2u)];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Const_Idx_Clamp) {
-    auto* src = R"(
-var<private> a : array<f32, 3>;
-
-const c : u32 = 1u;
-
-fn f() {
-  let b : f32 = a[c];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-const c : u32 = 1u;
-
-fn f() {
-  let b : f32 = a[c];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Const_Idx_Clamp_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  let b : f32 = a[c];
-}
-
-const c : u32 = 1u;
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  let b : f32 = a[c];
-}
-
-const c : u32 = 1u;
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Nested_Scalar) {
+TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithRuntimeArrayIndex) {
     auto* src = R"(
 var<private> a : array<f32, 3>;
 
@@ -130,11 +404,13 @@
 var<private> i : u32;
 
 fn f() {
-  var c : f32 = a[ b[i] ];
+  var c : f32 = a[b[i]];
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 var<private> a : array<f32, 3>;
 
 var<private> b : array<i32, 5>;
@@ -144,99 +420,50 @@
 fn f() {
   var c : f32 = a[min(u32(b[min(i, 4u)]), 2u)];
 }
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Nested_Scalar_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var c : f32 = a[ b[i] ];
-}
-
-var<private> i : u32;
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
 
 var<private> b : array<i32, 5>;
 
-var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var c : f32 = a[min(u32(b[min(i, 4u)]), 2u)];
-}
-
 var<private> i : u32;
 
-var<private> b : array<i32, 5>;
+fn f() {
+  let index = i;
+  let predicate = (u32(index) <= 4u);
+  var predicated_expr : i32;
+  if (predicate) {
+    predicated_expr = b[index];
+  }
+  let index_1 = predicated_expr;
+  let predicate_1 = (u32(index_1) <= 2u);
+  var predicated_expr_1 : f32;
+  if (predicate_1) {
+    predicated_expr_1 = a[index_1];
+  }
+  var c : f32 = predicated_expr_1;
+}
+)");
 
-var<private> a : array<f32, 3>;
-)";
-
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Array_Idx_Scalar) {
-    auto* src = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[1i];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[1i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Scalar_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[1i];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[1i];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Expr) {
+TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithRuntimeExpression) {
     auto* src = R"(
 var<private> a : array<f32, 3>;
 
 var<private> c : i32;
 
 fn f() {
-  var b : f32 = a[c + 2 - 3];
+  var b : f32 = a[((c + 2) - 3)];
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 var<private> a : array<f32, 3>;
 
 var<private> c : i32;
@@ -244,852 +471,3040 @@
 fn f() {
   var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
 }
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Expr_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[c + 2 - 3];
-}
-
-var<private> c : i32;
-
+)",
+                          /* predicate */ R"(
 var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
-}
 
 var<private> c : i32;
 
-var<private> a : array<f32, 3>;
-)";
+fn f() {
+  let index = ((c + 2) - 3);
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
 
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Vector_Idx_Scalar) {
+TEST_P(RobustnessTest, Read_NestedConstantSizedArraysRef_IndexWithRuntimeExpressions) {
     auto* src = R"(
-var<private> a : vec3<f32>;
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
 
 fn f() {
-  var b : f32 = a[1i];
+  var r = a[x][y][z];
 }
 )";
 
-    auto* expect = R"(
-var<private> a : vec3<f32>;
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
 
 fn f() {
-  var b : f32 = a[1i];
+  var r = a[min(u32(x), 2u)][min(u32(y), 1u)][min(u32(z), 0u)];
 }
-)";
+)",
+                          /* predicate */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
 
-    auto got = Run<Robustness>(src);
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let index = x;
+  let predicate = (u32(index) <= 2u);
+  let index_1 = y;
+  let predicate_1 = (predicate & (u32(index_1) <= 1u));
+  let index_2 = z;
+  let predicate_2 = (predicate_1 & (u32(index_2) <= 0u));
+  var predicated_expr : f32;
+  if (predicate_2) {
+    predicated_expr = a[index][index_1][index_2];
+  }
+  var r = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Vector_Idx_Scalar_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[1i];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[1i];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Expr) {
-    auto* src = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[c + 2 - 3];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Expr_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[c + 2 - 3];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Var) {
-    auto* src = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[c];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[min(u32(c), 1u)];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Var_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a.xy[c];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a.xy[min(u32(c), 1u)];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Expr) {
-    auto* src = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[c + 2 - 3];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : vec3<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Expr_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a.xy[c + 2 - 3];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
-}
-
-var<private> c : i32;
-
-var<private> a : vec3<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Scalar) {
-    auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Scalar_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Column) {
-    auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[c + 2 - 3][1];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Column_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[c + 2 - 3][1];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Row) {
-    auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[1][c + 2 - 3];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-var<private> c : i32;
-
-fn f() {
-  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Expr_Row_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[1][c + 2 - 3];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
-}
-
-var<private> c : i32;
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Constant_Id_Clamps) {
+TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithOverride) {
     auto* src = R"(
 @id(1300) override idx : i32;
-fn f() {
-  var a : vec3<f32>;
-  var b : f32 = a[idx];
-}
-)";
 
-    auto* expect = R"(
-@id(1300) override idx : i32;
-
-fn f() {
-  var a : vec3<f32>;
-  var b : f32 = a[min(u32(idx), 2u)];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Constant_Id_Clamps) {
-    auto* src = R"(
-@id(1300) override idx : i32;
 fn f() {
   var a : array<f32, 4>;
   var b : f32 = a[idx];
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 @id(1300) override idx : i32;
 
 fn f() {
   var a : array<f32, 4>;
   var b : f32 = a[min(u32(idx), 3u)];
 }
-)";
+)",
+                          /* predicate */ R"(
+@id(1300) override idx : i32;
 
-    auto got = Run<Robustness>(src);
+fn f() {
+  var a : array<f32, 4>;
+  let index = idx;
+  let predicate = (u32(index) <= 3u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Matrix_Column_Constant_Id_Clamps) {
+TEST_P(RobustnessTest, Read_ConstantSizedArrayPtr_IndexWithLet) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let p = &(a[l]);
+  let f : f32 = *(p);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let p = &(a[min(l, 2u)]);
+  let f : f32 = *(p);
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let index = l;
+  let predicate = (u32(index) <= 2u);
+  let p = &(a[index]);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = *(p);
+  }
+  let f : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_ConstantSizedArrayPtr_IndexWithRuntimeArrayIndex) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  let pa = &(a);
+  let pb = &(b);
+  let p0 = &((*(pb))[i]);
+  let p1 = &(a[*(p0)]);
+  var x : f32 = *(p1);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  let pa = &(a);
+  let pb = &(b);
+  let p0 = &((*(pb))[min(i, 4u)]);
+  let p1 = &(a[min(u32(*(p0)), 2u)]);
+  var x : f32 = *(p1);
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  let pa = &(a);
+  let pb = &(b);
+  let index = i;
+  let predicate = (u32(index) <= 4u);
+  let p0 = &((*(pb))[index]);
+  var predicated_expr : i32;
+  if (predicate) {
+    predicated_expr = *(p0);
+  }
+  let index_1 = predicated_expr;
+  let predicate_1 = (u32(index_1) <= 2u);
+  let p1 = &(a[index_1]);
+  var predicated_expr_1 : f32;
+  if (predicate_1) {
+    predicated_expr_1 = *(p1);
+  }
+  var x : f32 = predicated_expr_1;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_NestedConstantSizedArraysPtr_IndexWithRuntimeExpressions) {
+    auto* src = R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[x]);
+  let p2 = &((*(p1))[y]);
+  let p3 = &((*(p2))[z]);
+  var r = *(p3);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[min(u32(x), 2u)]);
+  let p2 = &((*(p1))[min(u32(y), 1u)]);
+  let p3 = &((*(p2))[min(u32(z), 0u)]);
+  var r = *(p3);
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let index = x;
+  let predicate = (u32(index) <= 2u);
+  let p1 = &((*(p0))[index]);
+  let index_1 = y;
+  let predicate_1 = (predicate & (u32(index_1) <= 1u));
+  let p2 = &((*(p1))[index_1]);
+  let index_2 = z;
+  let predicate_2 = (predicate_1 & (u32(index_2) <= 0u));
+  let p3 = &((*(p2))[index_2]);
+  var predicated_expr : f32;
+  if (predicate_2) {
+    predicated_expr = *(p3);
+  }
+  var r = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_NestedConstantSizedArrays_MixedAccess) {
+    auto* src = R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+const y = 1;
+
+override z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[x]);
+  let p2 = &((*(p1))[y]);
+  let p3 = &((*(p2))[z]);
+  var r = *(p3);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+const y = 1;
+
+override z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[min(u32(x), 2u)]);
+  let p2 = &((*(p1))[y]);
+  let p3 = &((*(p2))[min(u32(z), 0u)]);
+  var r = *(p3);
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+const y = 1;
+
+override z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let index = x;
+  let predicate = (u32(index) <= 2u);
+  let p1 = &((*(p0))[index]);
+  let p2 = &((*(p1))[y]);
+  let index_1 = z;
+  let predicate_1 = (predicate & (u32(index_1) <= 0u));
+  let p3 = &((*(p2))[index_1]);
+  var predicated_expr : f32;
+  if (predicate_1) {
+    predicated_expr = *(p3);
+  }
+  var r = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Assign_ConstantSizedArray_IndexWithLet) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  a[l] = 42.0f;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  a[min(l, 2u)] = 42.0f;
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let index = l;
+  let predicate = (u32(index) <= 2u);
+  if (predicate) {
+    a[index] = 42.0f;
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Assign_ConstantSizedArrayPtr_IndexWithLet) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let p = &(a[l]);
+  *(p) = 42.0f;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let p = &(a[min(l, 2u)]);
+  *(p) = 42.0f;
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let index = l;
+  let predicate = (u32(index) <= 2u);
+  let p = &(a[index]);
+  if (predicate) {
+    *(p) = 42.0f;
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Assign_ConstantSizedArrayPtr_IndexWithRuntimeArrayIndex) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  let pa = &(a);
+  let pb = &(b);
+  let p0 = &((*(pb))[i]);
+  let p1 = &(a[*(p0)]);
+  *(p1) = 42.0f;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  let pa = &(a);
+  let pb = &(b);
+  let p0 = &((*(pb))[min(i, 4u)]);
+  let p1 = &(a[min(u32(*(p0)), 2u)]);
+  *(p1) = 42.0f;
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+  let pa = &(a);
+  let pb = &(b);
+  let index = i;
+  let predicate = (u32(index) <= 4u);
+  let p0 = &((*(pb))[index]);
+  var predicated_expr : i32;
+  if (predicate) {
+    predicated_expr = *(p0);
+  }
+  let index_1 = predicated_expr;
+  let predicate_1 = (u32(index_1) <= 2u);
+  let p1 = &(a[index_1]);
+  if (predicate_1) {
+    *(p1) = 42.0f;
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Assign_NestedConstantSizedArraysPtr_IndexWithRuntimeExpressions) {
+    auto* src = R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[x]);
+  let p2 = &((*(p1))[y]);
+  let p3 = &((*(p2))[z]);
+  *(p3) = 42.0f;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[min(u32(x), 2u)]);
+  let p2 = &((*(p1))[min(u32(y), 1u)]);
+  let p3 = &((*(p2))[min(u32(z), 0u)]);
+  *(p3) = 42.0f;
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+var<private> y : i32;
+
+var<private> z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let index = x;
+  let predicate = (u32(index) <= 2u);
+  let p1 = &((*(p0))[index]);
+  let index_1 = y;
+  let predicate_1 = (predicate & (u32(index_1) <= 1u));
+  let p2 = &((*(p1))[index_1]);
+  let index_2 = z;
+  let predicate_2 = (predicate_1 & (u32(index_2) <= 0u));
+  let p3 = &((*(p2))[index_2]);
+  if (predicate_2) {
+    *(p3) = 42.0f;
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Assign_NestedConstantSizedArrays_MixedAccess) {
+    auto* src = R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+const y = 1;
+
+override z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[x]);
+  let p2 = &((*(p1))[y]);
+  let p3 = &((*(p2))[z]);
+  *(p3) = 42.0f;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+const y = 1;
+
+override z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let p1 = &((*(p0))[min(u32(x), 2u)]);
+  let p2 = &((*(p1))[y]);
+  let p3 = &((*(p2))[min(u32(z), 0u)]);
+  *(p3) = 42.0f;
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<array<array<f32, 1>, 2>, 3>;
+
+var<private> x : i32;
+
+const y = 1;
+
+override z : i32;
+
+fn f() {
+  let p0 = &(a);
+  let index = x;
+  let predicate = (u32(index) <= 2u);
+  let p1 = &((*(p0))[index]);
+  let p2 = &((*(p1))[y]);
+  let index_1 = z;
+  let predicate_1 = (predicate & (u32(index_1) <= 0u));
+  let p3 = &((*(p2))[index_1]);
+  if (predicate_1) {
+    *(p3) = 42.0f;
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, CompoundAssign_ConstantSizedArray_IndexWithLet) {
+    auto* src = R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  a[l] += 42.0f;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  a[min(l, 2u)] += 42.0f;
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<f32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let index = l;
+  let predicate = (u32(index) <= 2u);
+  if (predicate) {
+    a[index] += 42.0f;
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Increment_ConstantSizedArray_IndexWithLet) {
+    auto* src = R"(
+var<private> a : array<i32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  a[l]++;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : array<i32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  a[min(l, 2u)]++;
+}
+)",
+                          /* predicate */ R"(
+var<private> a : array<i32, 3>;
+
+fn f() {
+  let l : u32 = 1u;
+  let index = l;
+  let predicate = (u32(index) <= 2u);
+  if (predicate) {
+    a[index]++;
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Runtime sized array
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, Read_RuntimeArray_IndexWithLiteral) {
+    auto* src = R"(
+struct S {
+  a : f32,
+  b : array<f32>,
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn f() {
+  var d : f32 = s.b[25];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+struct S {
+  a : f32,
+  b : array<f32>,
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn f() {
+  var d : f32 = s.b[min(u32(25), (arrayLength(&(s.b)) - 1u))];
+}
+)",
+                          /* predicate */ R"(
+struct S {
+  a : f32,
+  b : array<f32>,
+}
+
+@group(0) @binding(0) var<storage, read> s : S;
+
+fn f() {
+  let index = 25;
+  let predicate = (u32(index) <= (arrayLength(&(s.b)) - 1u));
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = s.b[index];
+  }
+  var d : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Vector
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, Read_Vector_IndexWithLiteral) {
+    auto* src = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  var b : f32 = a[1i];
+}
+)";
+
+    auto* expect = src;
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_Vector_IndexWithConst) {
+    auto* src = R"(
+var<private> a : vec3<f32>;
+
+fn f() {
+  const i = 1;
+  var b : f32 = a[i];
+}
+)";
+
+    auto* expect = src;
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_Vector_IndexWithLet) {
+    auto* src = R"(
+fn f() {
+  let i = 99;
+  let v = vec4<f32>()[i];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+fn f() {
+  let i = 99;
+  let v = vec4<f32>()[min(u32(i), 3u)];
+}
+)",
+                          /* predicate */ R"(
+fn f() {
+  let i = 99;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = vec4<f32>()[index];
+  }
+  let v = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_Vector_IndexWithRuntimeExpression) {
+    auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[((c + 2) - 3)];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)];
+}
+)",
+                          /* predicate */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  let index = ((c + 2) - 3);
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_Vector_SwizzleIndexWithGlobalVar) {
+    auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[c];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[min(u32(c), 1u)];
+}
+)",
+                          /* predicate */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  let index = c;
+  let predicate = (u32(index) <= 1u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a.xy[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_Vector_SwizzleIndexWithRuntimeExpression) {
+    auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[((c + 2) - 3)];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a.xy[min(u32(((c + 2) - 3)), 1u)];
+}
+)",
+                          /* predicate */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  let index = ((c + 2) - 3);
+  let predicate = (u32(index) <= 1u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a.xy[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_Vector_IndexWithOverride) {
     auto* src = R"(
 @id(1300) override idx : i32;
+
+fn f() {
+  var a : vec3<f32>;
+  var b : f32 = a[idx];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@id(1300) override idx : i32;
+
+fn f() {
+  var a : vec3<f32>;
+  var b : f32 = a[min(u32(idx), 2u)];
+}
+)",
+                          /* predicate */ R"(
+@id(1300) override idx : i32;
+
+fn f() {
+  var a : vec3<f32>;
+  let index = idx;
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Matrix
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, Read_MatrixRef_IndexingWithLiterals) {
+    auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+fn f() {
+  var b : f32 = a[2i][1i];
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_MatrixRef_IndexWithRuntimeExpressionThenLiteral) {
+    auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[((c + 2) - 3)][1];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
+}
+)",
+                          /* predicate */ R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  let index = ((c + 2) - 3);
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index][1];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_MatrixRef_IndexWithLiteralThenRuntimeExpression) {
+    auto* src = R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[1][((c + 2) - 3)];
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
+}
+)",
+                          /* predicate */ R"(
+var<private> a : mat3x2<f32>;
+
+var<private> c : i32;
+
+fn f() {
+  let index = ((c + 2) - 3);
+  let predicate = (u32(index) <= 1u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[1][index];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_MatrixRef_IndexWithOverrideThenLiteral) {
+    auto* src = R"(
+@id(1300) override idx : i32;
+
 fn f() {
   var a : mat3x2<f32>;
   var b : f32 = a[idx][1];
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 @id(1300) override idx : i32;
 
 fn f() {
   var a : mat3x2<f32>;
   var b : f32 = a[min(u32(idx), 2u)][1];
 }
-)";
+)",
+                          /* predicate */ R"(
+@id(1300) override idx : i32;
 
-    auto got = Run<Robustness>(src);
+fn f() {
+  var a : mat3x2<f32>;
+  let index = idx;
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[index][1];
+  }
+  var b : f32 = predicated_expr;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Matrix_Row_Constant_Id_Clamps) {
+TEST_P(RobustnessTest, Read_MatrixRef_IndexWithLetThenSwizzle) {
+    auto* src = R"(
+fn f() {
+  let i = 1;
+  var m = mat3x2<f32>();
+  var v = m[i].yx;
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+fn f() {
+  let i = 1;
+  var m = mat3x2<f32>();
+  var v = m[min(u32(i), 2u)].yx;
+}
+)",
+                          /* predicate */ R"(
+fn f() {
+  let i = 1;
+  var m = mat3x2<f32>();
+  let index = i;
+  let predicate = (u32(index) <= 2u);
+  var predicated_expr : vec2<f32>;
+  if (predicate) {
+    predicated_expr = m[index];
+  }
+  var v = predicated_expr.yx;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_MatrixRef_IndexWithLiteralThenOverride) {
     auto* src = R"(
 @id(1300) override idx : i32;
+
 fn f() {
   var a : mat3x2<f32>;
   var b : f32 = a[1][idx];
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 @id(1300) override idx : i32;
 
 fn f() {
   var a : mat3x2<f32>;
   var b : f32 = a[1][min(u32(idx), 1u)];
 }
-)";
-
-    auto got = Run<Robustness>(src);
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, RuntimeArray_Clamps) {
-    auto* src = R"(
-struct S {
-  a : f32,
-  b : array<f32>,
-};
-@group(0) @binding(0) var<storage, read> s : S;
+)",
+                          /* predicate */ R"(
+@id(1300) override idx : i32;
 
 fn f() {
-  var d : f32 = s.b[25];
+  var a : mat3x2<f32>;
+  let index = idx;
+  let predicate = (u32(index) <= 1u);
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = a[1][index];
+  }
+  var b : f32 = predicated_expr;
 }
-)";
+)");
 
-    auto* expect = R"(
-struct S {
-  a : f32,
-  b : array<f32>,
+    auto got = Run<Robustness>(src, Config(GetParam()));
+    EXPECT_EQ(expect, str(got));
 }
 
-@group(0) @binding(0) var<storage, read> s : S;
+TEST_P(RobustnessTest, Assign_Matrix_IndexWithLet) {
+    auto* src = R"(
+var<private> m : mat3x4f;
 
 fn f() {
-  var d : f32 = s.b[min(u32(25), (arrayLength(&(s.b)) - 1u))];
+  let c = 1;
+  m[c] = vec4f(1);
 }
 )";
 
-    auto got = Run<Robustness>(src);
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> m : mat3x4f;
 
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, RuntimeArray_Clamps_OutOfOrder) {
-    auto* src = R"(
 fn f() {
-  var d : f32 = s.b[25];
+  let c = 1;
+  m[min(u32(c), 2u)] = vec4f(1);
 }
+)",
+                          /* predicate */ R"(
+var<private> m : mat3x4f;
 
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct S {
-  a : f32,
-  b : array<f32>,
-};
-)";
-
-    auto* expect = R"(
 fn f() {
-  var d : f32 = s.b[min(u32(25), (arrayLength(&(s.b)) - 1u))];
+  let c = 1;
+  let index = c;
+  let predicate = (u32(index) <= 2u);
+  if (predicate) {
+    m[index] = vec4f(1);
+  }
 }
+)");
 
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct S {
-  a : f32,
-  b : array<f32>,
-}
-)";
-
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-// Clamp textureLoad() coord, array_index and level values
-TEST_F(RobustnessTest, TextureLoad_Clamp) {
+TEST_P(RobustnessTest, CompoundAssign_Matrix_IndexWithLet) {
     auto* src = R"(
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-@group(0) @binding(0) var tex_external : texture_external;
+var<private> m : mat3x4f;
 
-fn idx_signed() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
-
-  _ = textureLoad(tex_1d, 1i, level_idx);
-  _ = textureLoad(tex_2d, vec2<i32>(1, 2), level_idx);
-  _ = textureLoad(tex_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_3d, vec3<i32>(1, 2, 3), level_idx);
-  _ = textureLoad(tex_ms_2d, vec2<i32>(1, 2), sample_idx);
-  _ = textureLoad(tex_depth_2d, vec2<i32>(1, 2), level_idx);
-  _ = textureLoad(tex_depth_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_external, vec2<i32>(1, 2));
-}
-
-fn idx_unsigned() {
-  var array_idx : u32;
-  var level_idx : u32;
-  var sample_idx : u32;
-
-  _ = textureLoad(tex_1d, 1u, level_idx);
-  _ = textureLoad(tex_2d, vec2<u32>(1, 2), level_idx);
-  _ = textureLoad(tex_2d_arr, vec2<u32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_3d, vec3<u32>(1, 2, 3), level_idx);
-  _ = textureLoad(tex_ms_2d, vec2<u32>(1, 2), sample_idx);
-  _ = textureLoad(tex_depth_2d, vec2<u32>(1, 2), level_idx);
-  _ = textureLoad(tex_depth_2d_arr, vec2<u32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_external, vec2<u32>(1, 2));
+fn f() {
+  let c = 1;
+  m[c] += vec4f(1);
 }
 )";
 
-    auto* expect =
-        R"(
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<private> m : mat3x4f;
 
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-
-@group(0) @binding(0) var tex_external : texture_external;
-
-fn idx_signed() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
-  _ = textureLoad(tex_1d, clamp(1i, 0, i32((u32(textureDimensions(tex_1d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_1d)) - 1))))) - 1))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_1d)) - 1))));
-  _ = textureLoad(tex_2d, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_2d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d)) - 1))))) - vec2(1)))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d)) - 1))));
-  _ = textureLoad(tex_2d_arr, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_2d_arr, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d_arr)) - 1))))) - vec2(1)))), clamp(array_idx, 0, i32((u32(textureNumLayers(tex_2d_arr)) - 1))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d_arr)) - 1))));
-  _ = textureLoad(tex_3d, clamp(vec3<i32>(1, 2, 3), vec3(0), vec3<i32>((vec3<u32>(textureDimensions(tex_3d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_3d)) - 1))))) - vec3(1)))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_3d)) - 1))));
-  _ = textureLoad(tex_ms_2d, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_ms_2d)) - vec2(1)))), sample_idx);
-  _ = textureLoad(tex_depth_2d, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_depth_2d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d)) - 1))))) - vec2(1)))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d)) - 1))));
-  _ = textureLoad(tex_depth_2d_arr, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_depth_2d_arr, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d_arr)) - 1))))) - vec2(1)))), clamp(array_idx, 0, i32((u32(textureNumLayers(tex_depth_2d_arr)) - 1))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d_arr)) - 1))));
-  _ = textureLoad(tex_external, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_external)) - vec2(1)))));
+fn f() {
+  let c = 1;
+  m[min(u32(c), 2u)] += vec4f(1);
 }
+)",
+                          /* predicate */ R"(
+var<private> m : mat3x4f;
 
-fn idx_unsigned() {
-  var array_idx : u32;
-  var level_idx : u32;
-  var sample_idx : u32;
-  _ = textureLoad(tex_1d, min(1u, (u32(textureDimensions(tex_1d, min(level_idx, (u32(textureNumLevels(tex_1d)) - 1)))) - 1)), min(level_idx, (u32(textureNumLevels(tex_1d)) - 1)));
-  _ = textureLoad(tex_2d, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_2d, min(level_idx, (u32(textureNumLevels(tex_2d)) - 1)))) - vec2(1))), min(level_idx, (u32(textureNumLevels(tex_2d)) - 1)));
-  _ = textureLoad(tex_2d_arr, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_2d_arr, min(level_idx, (u32(textureNumLevels(tex_2d_arr)) - 1)))) - vec2(1))), min(array_idx, (u32(textureNumLayers(tex_2d_arr)) - 1)), min(level_idx, (u32(textureNumLevels(tex_2d_arr)) - 1)));
-  _ = textureLoad(tex_3d, min(vec3<u32>(1, 2, 3), (vec3<u32>(textureDimensions(tex_3d, min(level_idx, (u32(textureNumLevels(tex_3d)) - 1)))) - vec3(1))), min(level_idx, (u32(textureNumLevels(tex_3d)) - 1)));
-  _ = textureLoad(tex_ms_2d, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_ms_2d)) - vec2(1))), sample_idx);
-  _ = textureLoad(tex_depth_2d, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_depth_2d, min(level_idx, (u32(textureNumLevels(tex_depth_2d)) - 1)))) - vec2(1))), min(level_idx, (u32(textureNumLevels(tex_depth_2d)) - 1)));
-  _ = textureLoad(tex_depth_2d_arr, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_depth_2d_arr, min(level_idx, (u32(textureNumLevels(tex_depth_2d_arr)) - 1)))) - vec2(1))), min(array_idx, (u32(textureNumLayers(tex_depth_2d_arr)) - 1)), min(level_idx, (u32(textureNumLevels(tex_depth_2d_arr)) - 1)));
-  _ = textureLoad(tex_external, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_external)) - vec2(1))));
+fn f() {
+  let c = 1;
+  let index = c;
+  let predicate = (u32(index) <= 2u);
+  if (predicate) {
+    m[index] += vec4f(1);
+  }
 }
-)";
+)");
 
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-// Clamp textureLoad() coord, array_index and level values
-TEST_F(RobustnessTest, TextureLoad_Clamp_OutOfOrder) {
+////////////////////////////////////////////////////////////////////////////////
+// Texture
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, TextureDimensions) {
     auto* src = R"(
-fn idx_signed() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
+@group(0) @binding(0) var t : texture_2d<f32>;
 
-  _ = textureLoad(tex_1d, 1i, level_idx);
-  _ = textureLoad(tex_2d, vec2<i32>(1, 2), level_idx);
-  _ = textureLoad(tex_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_3d, vec3<i32>(1, 2, 3), level_idx);
-  _ = textureLoad(tex_ms_2d, vec2<i32>(1, 2), sample_idx);
-  _ = textureLoad(tex_depth_2d, vec2<i32>(1, 2), level_idx);
-  _ = textureLoad(tex_depth_2d_arr, vec2<i32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_external, vec2<i32>(1, 2));
+fn dimensions() {
+  let l = textureDimensions(t);
 }
-
-fn idx_unsigned() {
-  var array_idx : u32;
-  var level_idx : u32;
-  var sample_idx : u32;
-
-  _ = textureLoad(tex_1d, 1u, level_idx);
-  _ = textureLoad(tex_2d, vec2<u32>(1, 2), level_idx);
-  _ = textureLoad(tex_2d_arr, vec2<u32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_3d, vec3<u32>(1, 2, 3), level_idx);
-  _ = textureLoad(tex_ms_2d, vec2<u32>(1, 2), sample_idx);
-  _ = textureLoad(tex_depth_2d, vec2<u32>(1, 2), level_idx);
-  _ = textureLoad(tex_depth_2d_arr, vec2<u32>(1, 2), array_idx, level_idx);
-  _ = textureLoad(tex_external, vec2<u32>(1, 2));
-}
-
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-@group(0) @binding(0) var tex_external : texture_external;
 )";
 
-    auto* expect =
-        R"(
-fn idx_signed() {
-  var array_idx : i32;
-  var level_idx : i32;
-  var sample_idx : i32;
-  _ = textureLoad(tex_1d, clamp(1i, 0, i32((u32(textureDimensions(tex_1d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_1d)) - 1))))) - 1))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_1d)) - 1))));
-  _ = textureLoad(tex_2d, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_2d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d)) - 1))))) - vec2(1)))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d)) - 1))));
-  _ = textureLoad(tex_2d_arr, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_2d_arr, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d_arr)) - 1))))) - vec2(1)))), clamp(array_idx, 0, i32((u32(textureNumLayers(tex_2d_arr)) - 1))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_2d_arr)) - 1))));
-  _ = textureLoad(tex_3d, clamp(vec3<i32>(1, 2, 3), vec3(0), vec3<i32>((vec3<u32>(textureDimensions(tex_3d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_3d)) - 1))))) - vec3(1)))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_3d)) - 1))));
-  _ = textureLoad(tex_ms_2d, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_ms_2d)) - vec2(1)))), sample_idx);
-  _ = textureLoad(tex_depth_2d, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_depth_2d, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d)) - 1))))) - vec2(1)))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d)) - 1))));
-  _ = textureLoad(tex_depth_2d_arr, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_depth_2d_arr, clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d_arr)) - 1))))) - vec2(1)))), clamp(array_idx, 0, i32((u32(textureNumLayers(tex_depth_2d_arr)) - 1))), clamp(level_idx, 0, i32((u32(textureNumLevels(tex_depth_2d_arr)) - 1))));
-  _ = textureLoad(tex_external, clamp(vec2<i32>(1, 2), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex_external)) - vec2(1)))));
-}
+    auto* expect = src;
 
-fn idx_unsigned() {
-  var array_idx : u32;
-  var level_idx : u32;
-  var sample_idx : u32;
-  _ = textureLoad(tex_1d, min(1u, (u32(textureDimensions(tex_1d, min(level_idx, (u32(textureNumLevels(tex_1d)) - 1)))) - 1)), min(level_idx, (u32(textureNumLevels(tex_1d)) - 1)));
-  _ = textureLoad(tex_2d, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_2d, min(level_idx, (u32(textureNumLevels(tex_2d)) - 1)))) - vec2(1))), min(level_idx, (u32(textureNumLevels(tex_2d)) - 1)));
-  _ = textureLoad(tex_2d_arr, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_2d_arr, min(level_idx, (u32(textureNumLevels(tex_2d_arr)) - 1)))) - vec2(1))), min(array_idx, (u32(textureNumLayers(tex_2d_arr)) - 1)), min(level_idx, (u32(textureNumLevels(tex_2d_arr)) - 1)));
-  _ = textureLoad(tex_3d, min(vec3<u32>(1, 2, 3), (vec3<u32>(textureDimensions(tex_3d, min(level_idx, (u32(textureNumLevels(tex_3d)) - 1)))) - vec3(1))), min(level_idx, (u32(textureNumLevels(tex_3d)) - 1)));
-  _ = textureLoad(tex_ms_2d, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_ms_2d)) - vec2(1))), sample_idx);
-  _ = textureLoad(tex_depth_2d, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_depth_2d, min(level_idx, (u32(textureNumLevels(tex_depth_2d)) - 1)))) - vec2(1))), min(level_idx, (u32(textureNumLevels(tex_depth_2d)) - 1)));
-  _ = textureLoad(tex_depth_2d_arr, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_depth_2d_arr, min(level_idx, (u32(textureNumLevels(tex_depth_2d_arr)) - 1)))) - vec2(1))), min(array_idx, (u32(textureNumLayers(tex_depth_2d_arr)) - 1)), min(level_idx, (u32(textureNumLevels(tex_depth_2d_arr)) - 1)));
-  _ = textureLoad(tex_external, min(vec2<u32>(1, 2), (vec2<u32>(textureDimensions(tex_external)) - vec2(1))));
-}
-
-@group(0) @binding(0) var tex_1d : texture_1d<f32>;
-
-@group(0) @binding(0) var tex_2d : texture_2d<f32>;
-
-@group(0) @binding(0) var tex_2d_arr : texture_2d_array<f32>;
-
-@group(0) @binding(0) var tex_3d : texture_3d<f32>;
-
-@group(0) @binding(0) var tex_ms_2d : texture_multisampled_2d<f32>;
-
-@group(0) @binding(0) var tex_depth_2d : texture_depth_2d;
-
-@group(0) @binding(0) var tex_depth_2d_arr : texture_depth_2d_array;
-
-@group(0) @binding(0) var tex_external : texture_external;
-)";
-
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-// Clamp textureStore() coord, array_index and level values
-TEST_F(RobustnessTest, TextureStore_Clamp) {
+TEST_P(RobustnessTest, TextureDimensions_Level) {
     auto* src = R"(
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
+@group(0) @binding(0) var t : texture_2d<f32>;
 
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-
-fn idx_signed() {
-  textureStore(tex1d, 10i, vec4<i32>());
-  textureStore(tex2d, vec2<i32>(10, 20), vec4<i32>());
-  textureStore(tex2d_arr, vec2<i32>(10, 20), 50i, vec4<i32>());
-  textureStore(tex3d, vec3<i32>(10, 20, 30), vec4<i32>());
+fn dimensions_signed(level : i32) {
+  let l = textureDimensions(t, level);
 }
 
-fn idx_unsigned() {
-  textureStore(tex1d, 10u, vec4<i32>());
-  textureStore(tex2d, vec2<u32>(10, 20), vec4<i32>());
-  textureStore(tex2d_arr, vec2<u32>(10, 20), 50u, vec4<i32>());
-  textureStore(tex3d, vec3<u32>(10, 20, 30), vec4<i32>());
+fn dimensions_unsigned(level : u32) {
+  let l = textureDimensions(t, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  dimensions_signed(i32(non_uniform.x));
+  dimensions_unsigned(u32(non_uniform.x));
 }
 )";
 
-    auto* expect = R"(
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
 
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-
-fn idx_signed() {
-  textureStore(tex1d, clamp(10i, 0, i32((u32(textureDimensions(tex1d)) - 1))), vec4<i32>());
-  textureStore(tex2d, clamp(vec2<i32>(10, 20), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex2d)) - vec2(1)))), vec4<i32>());
-  textureStore(tex2d_arr, clamp(vec2<i32>(10, 20), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex2d_arr)) - vec2(1)))), clamp(50i, 0, i32((u32(textureNumLayers(tex2d_arr)) - 1))), vec4<i32>());
-  textureStore(tex3d, clamp(vec3<i32>(10, 20, 30), vec3(0), vec3<i32>((vec3<u32>(textureDimensions(tex3d)) - vec3(1)))), vec4<i32>());
+fn dimensions_signed(level : i32) {
+  let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureDimensions(t, level_idx);
 }
 
-fn idx_unsigned() {
-  textureStore(tex1d, min(10u, (u32(textureDimensions(tex1d)) - 1)), vec4<i32>());
-  textureStore(tex2d, min(vec2<u32>(10, 20), (vec2<u32>(textureDimensions(tex2d)) - vec2(1))), vec4<i32>());
-  textureStore(tex2d_arr, min(vec2<u32>(10, 20), (vec2<u32>(textureDimensions(tex2d_arr)) - vec2(1))), min(50u, (u32(textureNumLayers(tex2d_arr)) - 1)), vec4<i32>());
-  textureStore(tex3d, min(vec3<u32>(10, 20, 30), (vec3<u32>(textureDimensions(tex3d)) - vec3(1))), vec4<i32>());
+fn dimensions_unsigned(level : u32) {
+  let level_idx_1 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureDimensions(t, level_idx_1);
 }
-)";
 
-    auto got = Run<Robustness>(src);
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  dimensions_signed(i32(non_uniform.x));
+  dimensions_unsigned(u32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn dimensions_signed(level : i32) {
+  let level_idx = u32(level);
+  let num_levels = textureNumLevels(t);
+  var predicated_value : vec2<u32>;
+  if ((level_idx < num_levels)) {
+    predicated_value = textureDimensions(t, level_idx);
+  }
+  let l = predicated_value;
+}
+
+fn dimensions_unsigned(level : u32) {
+  let level_idx_1 = u32(level);
+  let num_levels_1 = textureNumLevels(t);
+  var predicated_value_1 : vec2<u32>;
+  if ((level_idx_1 < num_levels_1)) {
+    predicated_value_1 = textureDimensions(t, level_idx_1);
+  }
+  let l = predicated_value_1;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  dimensions_signed(i32(non_uniform.x));
+  dimensions_unsigned(u32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-// Clamp textureStore() coord, array_index and level values
-TEST_F(RobustnessTest, TextureStore_Clamp_OutOfOrder) {
+TEST_P(RobustnessTest, TextureGather) {
     auto* src = R"(
-fn idx_signed() {
-  textureStore(tex1d, 10i, vec4<i32>());
-  textureStore(tex2d, vec2<i32>(10, 20), vec4<i32>());
-  textureStore(tex2d_arr, vec2<i32>(10, 20), 50i, vec4<i32>());
-  textureStore(tex3d, vec3<i32>(10, 20, 30), vec4<i32>());
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn gather(coords : vec2f) {
+  let l = textureGather(0, t, s, coords);
 }
-
-fn idx_unsigned() {
-  textureStore(tex1d, 10u, vec4<i32>());
-  textureStore(tex2d, vec2<u32>(10, 20), vec4<i32>());
-  textureStore(tex2d_arr, vec2<u32>(10, 20), 50u, vec4<i32>());
-  textureStore(tex3d, vec3<u32>(10, 20, 30), vec4<i32>());
-}
-
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
-
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-
 )";
 
-    auto* expect = R"(
-fn idx_signed() {
-  textureStore(tex1d, clamp(10i, 0, i32((u32(textureDimensions(tex1d)) - 1))), vec4<i32>());
-  textureStore(tex2d, clamp(vec2<i32>(10, 20), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex2d)) - vec2(1)))), vec4<i32>());
-  textureStore(tex2d_arr, clamp(vec2<i32>(10, 20), vec2(0), vec2<i32>((vec2<u32>(textureDimensions(tex2d_arr)) - vec2(1)))), clamp(50i, 0, i32((u32(textureNumLayers(tex2d_arr)) - 1))), vec4<i32>());
-  textureStore(tex3d, clamp(vec3<i32>(10, 20, 30), vec3(0), vec3<i32>((vec3<u32>(textureDimensions(tex3d)) - vec3(1)))), vec4<i32>());
-}
+    auto* expect = src;
 
-fn idx_unsigned() {
-  textureStore(tex1d, min(10u, (u32(textureDimensions(tex1d)) - 1)), vec4<i32>());
-  textureStore(tex2d, min(vec2<u32>(10, 20), (vec2<u32>(textureDimensions(tex2d)) - vec2(1))), vec4<i32>());
-  textureStore(tex2d_arr, min(vec2<u32>(10, 20), (vec2<u32>(textureDimensions(tex2d_arr)) - vec2(1))), min(50u, (u32(textureNumLayers(tex2d_arr)) - 1)), vec4<i32>());
-  textureStore(tex3d, min(vec3<u32>(10, 20, 30), (vec3<u32>(textureDimensions(tex3d)) - vec3(1))), vec4<i32>());
-}
-
-@group(0) @binding(0) var tex1d : texture_storage_1d<rgba8sint, write>;
-
-@group(0) @binding(1) var tex2d : texture_storage_2d<rgba8sint, write>;
-
-@group(0) @binding(2) var tex2d_arr : texture_storage_2d_array<rgba8sint, write>;
-
-@group(0) @binding(3) var tex3d : texture_storage_3d<rgba8sint, write>;
-)";
-
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Shadowed_Variable) {
+TEST_P(RobustnessTest, TextureGather_Array) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn gather_signed(coords : vec2f, array : i32) {
+  let l = textureGather(1, t, s, coords, array);
+}
+
+fn gather_unsigned(coords : vec2f, array : u32) {
+  let l = textureGather(1, t, s, coords, array);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  gather_signed(non_uniform.xy, i32(non_uniform.x));
+  gather_unsigned(non_uniform.xy, u32(non_uniform.x));
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureGatherCompare) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn gather(coords : vec2f, depth_ref : f32) {
+  let l = textureGatherCompare(t, s, coords, depth_ref);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureGatherCompare_Array) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d_array;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn gather_signed(coords : vec2f, array : i32, depth_ref : f32) {
+  let l = textureGatherCompare(t, s, coords, array, depth_ref);
+}
+
+fn gather_unsigned(coords : vec2f, array : u32, depth_ref : f32) {
+  let l = textureGatherCompare(t, s, coords, array, depth_ref);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  gather_signed(non_uniform.xy, i32(non_uniform.x), non_uniform.x);
+  gather_unsigned(non_uniform.xy, u32(non_uniform.x), non_uniform.x);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_1D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+fn load_signed(coords : i32, level : i32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_unsigned(coords : u32, level : u32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_mixed(coords : i32, level : u32) {
+  let l = textureLoad(t, coords, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(i32(non_uniform.x), u32(non_uniform.x));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+fn load_signed(coords : i32, level : i32) {
+  let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, 0, i32((textureDimensions(t, level_idx) - 1))), level_idx);
+}
+
+fn load_unsigned(coords : u32, level : u32) {
+  let level_idx_1 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_1) - 1)), level_idx_1);
+}
+
+fn load_mixed(coords : i32, level : u32) {
+  let level_idx_2 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, 0, i32((textureDimensions(t, level_idx_2) - 1))), level_idx_2);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(i32(non_uniform.x), u32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_1d<f32>;
+
+fn load_signed(coords : i32, level : i32) {
+  let level_idx = u32(level);
+  let num_levels = textureNumLevels(t);
+  let coords_1 = u32(coords);
+  var predicated_value : vec4<f32>;
+  if (((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1))))))) {
+    predicated_value = textureLoad(t, coords_1, level_idx);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : u32, level : u32) {
+  let level_idx_1 = u32(level);
+  let num_levels_1 = textureNumLevels(t);
+  let coords_2 = u32(coords);
+  var predicated_value_1 : vec4<f32>;
+  if (((level_idx_1 < num_levels_1) & all((coords_2 < textureDimensions(t, min(level_idx_1, (num_levels_1 - 1))))))) {
+    predicated_value_1 = textureLoad(t, coords_2, level_idx_1);
+  }
+  let l = predicated_value_1;
+}
+
+fn load_mixed(coords : i32, level : u32) {
+  let level_idx_2 = u32(level);
+  let num_levels_2 = textureNumLevels(t);
+  let coords_3 = u32(coords);
+  var predicated_value_2 : vec4<f32>;
+  if (((level_idx_2 < num_levels_2) & all((coords_3 < textureDimensions(t, min(level_idx_2, (num_levels_2 - 1))))))) {
+    predicated_value_2 = textureLoad(t, coords_3, level_idx_2);
+  }
+  let l = predicated_value_2;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(i32(non_uniform.x), u32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_2D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn load_signed(coords : vec2i, level : i32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_unsigned(coords : vec2u, level : u32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_mixed(coords : vec2u, level : i32) {
+  let l = textureLoad(t, coords, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn load_signed(coords : vec2i, level : i32) {
+  let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t, level_idx) - vec2(1)))), level_idx);
+}
+
+fn load_unsigned(coords : vec2u, level : u32) {
+  let level_idx_1 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_1) - vec2(1))), level_idx_1);
+}
+
+fn load_mixed(coords : vec2u, level : i32) {
+  let level_idx_2 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_2) - vec2(1))), level_idx_2);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+fn load_signed(coords : vec2i, level : i32) {
+  let level_idx = u32(level);
+  let num_levels = textureNumLevels(t);
+  let coords_1 = vec2<u32>(coords);
+  var predicated_value : vec4<f32>;
+  if (((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1))))))) {
+    predicated_value = textureLoad(t, coords_1, level_idx);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : vec2u, level : u32) {
+  let level_idx_1 = u32(level);
+  let num_levels_1 = textureNumLevels(t);
+  let coords_2 = vec2<u32>(coords);
+  var predicated_value_1 : vec4<f32>;
+  if (((level_idx_1 < num_levels_1) & all((coords_2 < textureDimensions(t, min(level_idx_1, (num_levels_1 - 1))))))) {
+    predicated_value_1 = textureLoad(t, coords_2, level_idx_1);
+  }
+  let l = predicated_value_1;
+}
+
+fn load_mixed(coords : vec2u, level : i32) {
+  let level_idx_2 = u32(level);
+  let num_levels_2 = textureNumLevels(t);
+  let coords_3 = vec2<u32>(coords);
+  var predicated_value_2 : vec4<f32>;
+  if (((level_idx_2 < num_levels_2) & all((coords_3 < textureDimensions(t, min(level_idx_2, (num_levels_2 - 1))))))) {
+    predicated_value_2 = textureLoad(t, coords_3, level_idx_2);
+  }
+  let l = predicated_value_2;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_2DArray) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+fn load_signed(coords : vec2i, array : i32, level : i32) {
+  let l = textureLoad(t, coords, array, level);
+}
+
+fn load_unsigned(coords : vec2u, array : u32, level : u32) {
+  let l = textureLoad(t, coords, array, level);
+}
+
+fn load_mixed(coords : vec2u, array : i32, level : u32) {
+  let l = textureLoad(t, coords, array, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x), u32(non_uniform.x));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+fn load_signed(coords : vec2i, array : i32, level : i32) {
+  let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t, level_idx) - vec2(1)))), clamp(array, 0, i32((textureNumLayers(t) - 1))), level_idx);
+}
+
+fn load_unsigned(coords : vec2u, array : u32, level : u32) {
+  let level_idx_1 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_1) - vec2(1))), min(array, (textureNumLayers(t) - 1)), level_idx_1);
+}
+
+fn load_mixed(coords : vec2u, array : i32, level : u32) {
+  let level_idx_2 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_2) - vec2(1))), clamp(array, 0, i32((textureNumLayers(t) - 1))), level_idx_2);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x), u32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+fn load_signed(coords : vec2i, array : i32, level : i32) {
+  let level_idx = u32(level);
+  let num_levels = textureNumLevels(t);
+  let coords_1 = vec2<u32>(coords);
+  let array_idx = u32(array);
+  var predicated_value : vec4<f32>;
+  if ((((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1)))))) & (array_idx < textureNumLayers(t)))) {
+    predicated_value = textureLoad(t, coords_1, array_idx, level_idx);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : vec2u, array : u32, level : u32) {
+  let level_idx_1 = u32(level);
+  let num_levels_1 = textureNumLevels(t);
+  let coords_2 = vec2<u32>(coords);
+  let array_idx_1 = u32(array);
+  var predicated_value_1 : vec4<f32>;
+  if ((((level_idx_1 < num_levels_1) & all((coords_2 < textureDimensions(t, min(level_idx_1, (num_levels_1 - 1)))))) & (array_idx_1 < textureNumLayers(t)))) {
+    predicated_value_1 = textureLoad(t, coords_2, array_idx_1, level_idx_1);
+  }
+  let l = predicated_value_1;
+}
+
+fn load_mixed(coords : vec2u, array : i32, level : u32) {
+  let level_idx_2 = u32(level);
+  let num_levels_2 = textureNumLevels(t);
+  let coords_3 = vec2<u32>(coords);
+  let array_idx_2 = u32(array);
+  var predicated_value_2 : vec4<f32>;
+  if ((((level_idx_2 < num_levels_2) & all((coords_3 < textureDimensions(t, min(level_idx_2, (num_levels_2 - 1)))))) & (array_idx_2 < textureNumLayers(t)))) {
+    predicated_value_2 = textureLoad(t, coords_3, array_idx_2, level_idx_2);
+  }
+  let l = predicated_value_2;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x), u32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_3D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_3d<f32>;
+
+fn load_signed(coords : vec3i, level : i32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_unsigned(coords : vec3u, level : u32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_mixed(coords : vec3u, level : i32) {
+  let l = textureLoad(t, coords, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec3i(non_uniform.xyz), i32(non_uniform.x));
+  load_unsigned(vec3u(non_uniform.xyz), u32(non_uniform.x));
+  load_mixed(vec3u(non_uniform.xyz), i32(non_uniform.x));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_3d<f32>;
+
+fn load_signed(coords : vec3i, level : i32) {
+  let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, vec3(0), vec3<i32>((textureDimensions(t, level_idx) - vec3(1)))), level_idx);
+}
+
+fn load_unsigned(coords : vec3u, level : u32) {
+  let level_idx_1 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_1) - vec3(1))), level_idx_1);
+}
+
+fn load_mixed(coords : vec3u, level : i32) {
+  let level_idx_2 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_2) - vec3(1))), level_idx_2);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec3i(non_uniform.xyz), i32(non_uniform.x));
+  load_unsigned(vec3u(non_uniform.xyz), u32(non_uniform.x));
+  load_mixed(vec3u(non_uniform.xyz), i32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_3d<f32>;
+
+fn load_signed(coords : vec3i, level : i32) {
+  let level_idx = u32(level);
+  let num_levels = textureNumLevels(t);
+  let coords_1 = vec3<u32>(coords);
+  var predicated_value : vec4<f32>;
+  if (((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1))))))) {
+    predicated_value = textureLoad(t, coords_1, level_idx);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : vec3u, level : u32) {
+  let level_idx_1 = u32(level);
+  let num_levels_1 = textureNumLevels(t);
+  let coords_2 = vec3<u32>(coords);
+  var predicated_value_1 : vec4<f32>;
+  if (((level_idx_1 < num_levels_1) & all((coords_2 < textureDimensions(t, min(level_idx_1, (num_levels_1 - 1))))))) {
+    predicated_value_1 = textureLoad(t, coords_2, level_idx_1);
+  }
+  let l = predicated_value_1;
+}
+
+fn load_mixed(coords : vec3u, level : i32) {
+  let level_idx_2 = u32(level);
+  let num_levels_2 = textureNumLevels(t);
+  let coords_3 = vec3<u32>(coords);
+  var predicated_value_2 : vec4<f32>;
+  if (((level_idx_2 < num_levels_2) & all((coords_3 < textureDimensions(t, min(level_idx_2, (num_levels_2 - 1))))))) {
+    predicated_value_2 = textureLoad(t, coords_3, level_idx_2);
+  }
+  let l = predicated_value_2;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec3i(non_uniform.xyz), i32(non_uniform.x));
+  load_unsigned(vec3u(non_uniform.xyz), u32(non_uniform.x));
+  load_mixed(vec3u(non_uniform.xyz), i32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_Multisampled2D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_multisampled_2d<f32>;
+
+fn load_signed(coords : vec2i, sample : i32) {
+  let l = textureLoad(t, coords, sample);
+}
+
+fn load_unsigned(coords : vec2u, sample : u32) {
+  let l = textureLoad(t, coords, sample);
+}
+
+fn load_mixed(coords : vec2i, sample : u32) {
+  let l = textureLoad(t, coords, sample);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2i(non_uniform.xy), u32(non_uniform.x));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_multisampled_2d<f32>;
+
+fn load_signed(coords : vec2i, sample : i32) {
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t) - vec2(1)))), sample);
+}
+
+fn load_unsigned(coords : vec2u, sample : u32) {
+  let l = textureLoad(t, min(coords, (textureDimensions(t) - vec2(1))), sample);
+}
+
+fn load_mixed(coords : vec2i, sample : u32) {
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t) - vec2(1)))), sample);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2i(non_uniform.xy), u32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_multisampled_2d<f32>;
+
+fn load_signed(coords : vec2i, sample : i32) {
+  let coords_1 = vec2<u32>(coords);
+  var predicated_value : vec4<f32>;
+  if (all((coords_1 < textureDimensions(t)))) {
+    predicated_value = textureLoad(t, coords_1, sample);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : vec2u, sample : u32) {
+  let coords_2 = vec2<u32>(coords);
+  var predicated_value_1 : vec4<f32>;
+  if (all((coords_2 < textureDimensions(t)))) {
+    predicated_value_1 = textureLoad(t, coords_2, sample);
+  }
+  let l = predicated_value_1;
+}
+
+fn load_mixed(coords : vec2i, sample : u32) {
+  let coords_3 = vec2<u32>(coords);
+  var predicated_value_2 : vec4<f32>;
+  if (all((coords_3 < textureDimensions(t)))) {
+    predicated_value_2 = textureLoad(t, coords_3, sample);
+  }
+  let l = predicated_value_2;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2i(non_uniform.xy), u32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_Depth2D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+fn load_signed(coords : vec2i, level : i32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_unsigned(coords : vec2u, level : u32) {
+  let l = textureLoad(t, coords, level);
+}
+
+fn load_mixed(coords : vec2i, level : u32) {
+  let l = textureLoad(t, coords, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2i(non_uniform.xy), u32(non_uniform.x));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+fn load_signed(coords : vec2i, level : i32) {
+  let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t, level_idx) - vec2(1)))), level_idx);
+}
+
+fn load_unsigned(coords : vec2u, level : u32) {
+  let level_idx_1 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_1) - vec2(1))), level_idx_1);
+}
+
+fn load_mixed(coords : vec2i, level : u32) {
+  let level_idx_2 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t, level_idx_2) - vec2(1)))), level_idx_2);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2i(non_uniform.xy), u32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+fn load_signed(coords : vec2i, level : i32) {
+  let level_idx = u32(level);
+  let num_levels = textureNumLevels(t);
+  let coords_1 = vec2<u32>(coords);
+  var predicated_value : f32;
+  if (((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1))))))) {
+    predicated_value = textureLoad(t, coords_1, level_idx);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : vec2u, level : u32) {
+  let level_idx_1 = u32(level);
+  let num_levels_1 = textureNumLevels(t);
+  let coords_2 = vec2<u32>(coords);
+  var predicated_value_1 : f32;
+  if (((level_idx_1 < num_levels_1) & all((coords_2 < textureDimensions(t, min(level_idx_1, (num_levels_1 - 1))))))) {
+    predicated_value_1 = textureLoad(t, coords_2, level_idx_1);
+  }
+  let l = predicated_value_1;
+}
+
+fn load_mixed(coords : vec2i, level : u32) {
+  let level_idx_2 = u32(level);
+  let num_levels_2 = textureNumLevels(t);
+  let coords_3 = vec2<u32>(coords);
+  var predicated_value_2 : f32;
+  if (((level_idx_2 < num_levels_2) & all((coords_3 < textureDimensions(t, min(level_idx_2, (num_levels_2 - 1))))))) {
+    predicated_value_2 = textureLoad(t, coords_3, level_idx_2);
+  }
+  let l = predicated_value_2;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x));
+  load_mixed(vec2i(non_uniform.xy), u32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_Depth2DArray) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d_array;
+
+fn load_signed(coords : vec2i, array : i32, level : i32) {
+  let l = textureLoad(t, coords, array, level);
+}
+
+fn load_unsigned(coords : vec2u, array : u32, level : u32) {
+  let l = textureLoad(t, coords, array, level);
+}
+
+fn load_mixed(coords : vec2u, array : i32, level : u32) {
+  let l = textureLoad(t, coords, array, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x), u32(non_uniform.x));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_depth_2d_array;
+
+fn load_signed(coords : vec2i, array : i32, level : i32) {
+  let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t, level_idx) - vec2(1)))), clamp(array, 0, i32((textureNumLayers(t) - 1))), level_idx);
+}
+
+fn load_unsigned(coords : vec2u, array : u32, level : u32) {
+  let level_idx_1 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_1) - vec2(1))), min(array, (textureNumLayers(t) - 1)), level_idx_1);
+}
+
+fn load_mixed(coords : vec2u, array : i32, level : u32) {
+  let level_idx_2 = min(u32(level), (textureNumLevels(t) - 1));
+  let l = textureLoad(t, min(coords, (textureDimensions(t, level_idx_2) - vec2(1))), clamp(array, 0, i32((textureNumLayers(t) - 1))), level_idx_2);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x), u32(non_uniform.x));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_depth_2d_array;
+
+fn load_signed(coords : vec2i, array : i32, level : i32) {
+  let level_idx = u32(level);
+  let num_levels = textureNumLevels(t);
+  let coords_1 = vec2<u32>(coords);
+  let array_idx = u32(array);
+  var predicated_value : f32;
+  if ((((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1)))))) & (array_idx < textureNumLayers(t)))) {
+    predicated_value = textureLoad(t, coords_1, array_idx, level_idx);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : vec2u, array : u32, level : u32) {
+  let level_idx_1 = u32(level);
+  let num_levels_1 = textureNumLevels(t);
+  let coords_2 = vec2<u32>(coords);
+  let array_idx_1 = u32(array);
+  var predicated_value_1 : f32;
+  if ((((level_idx_1 < num_levels_1) & all((coords_2 < textureDimensions(t, min(level_idx_1, (num_levels_1 - 1)))))) & (array_idx_1 < textureNumLayers(t)))) {
+    predicated_value_1 = textureLoad(t, coords_2, array_idx_1, level_idx_1);
+  }
+  let l = predicated_value_1;
+}
+
+fn load_mixed(coords : vec2u, array : i32, level : u32) {
+  let level_idx_2 = u32(level);
+  let num_levels_2 = textureNumLevels(t);
+  let coords_3 = vec2<u32>(coords);
+  let array_idx_2 = u32(array);
+  var predicated_value_2 : f32;
+  if ((((level_idx_2 < num_levels_2) & all((coords_3 < textureDimensions(t, min(level_idx_2, (num_levels_2 - 1)))))) & (array_idx_2 < textureNumLayers(t)))) {
+    predicated_value_2 = textureLoad(t, coords_3, array_idx_2, level_idx_2);
+  }
+  let l = predicated_value_2;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy), i32(non_uniform.x), i32(non_uniform.x));
+  load_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), u32(non_uniform.x));
+  load_mixed(vec2u(non_uniform.xy), i32(non_uniform.x), u32(non_uniform.x));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_External) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_external;
+
+fn load_signed(coords : vec2i) {
+  let l = textureLoad(t, coords);
+}
+
+fn load_unsigned(coords : vec2u) {
+  let l = textureLoad(t, coords);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy));
+  load_unsigned(vec2u(non_uniform.xy));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_external;
+
+fn load_signed(coords : vec2i) {
+  let l = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t) - vec2(1)))));
+}
+
+fn load_unsigned(coords : vec2u) {
+  let l = textureLoad(t, min(coords, (textureDimensions(t) - vec2(1))));
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy));
+  load_unsigned(vec2u(non_uniform.xy));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_external;
+
+fn load_signed(coords : vec2i) {
+  let coords_1 = vec2<u32>(coords);
+  var predicated_value : vec4<f32>;
+  if (all((coords_1 < textureDimensions(t)))) {
+    predicated_value = textureLoad(t, coords_1);
+  }
+  let l = predicated_value;
+}
+
+fn load_unsigned(coords : vec2u) {
+  let coords_2 = vec2<u32>(coords);
+  var predicated_value_1 : vec4<f32>;
+  if (all((coords_2 < textureDimensions(t)))) {
+    predicated_value_1 = textureLoad(t, coords_2);
+  }
+  let l = predicated_value_1;
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  load_signed(vec2i(non_uniform.xy));
+  load_unsigned(vec2u(non_uniform.xy));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureNumLayers) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d_array;
+
+fn num_layers(coords : vec2f, depth_ref : f32) {
+  let l = textureNumLayers(t);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureNumLevels) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+fn num_levels(coords : vec2f, depth_ref : f32) {
+  let l = textureNumLevels(t);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureNumSamples) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_multisampled_2d;
+
+fn num_levels(coords : vec2f, depth_ref : f32) {
+  let l = textureNumSamples(t);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSample) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample(coords : vec2f) {
+  let l = textureSample(t, s, coords);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSample_Offset) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample(coords : vec2f) {
+  const offset = vec2i(1);
+  let l = textureSample(t, s, coords, offset);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSample_ArrayIndex) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_signed(coords : vec2f, array : i32) {
+  let l = textureSample(t, s, coords, array);
+}
+
+fn sample_unsigned(coords : vec2f, array : u32) {
+  let l = textureSample(t, s, coords, array);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  sample_signed(non_uniform.xy, i32(non_uniform.x));
+  sample_unsigned(non_uniform.xy, u32(non_uniform.x));
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleBias) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_bias(coords : vec2f, bias : f32) {
+  let l = textureSampleBias(t, s, coords, bias);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleBias_Offset) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_bias(coords : vec2f, bias : f32) {
+  const offset = vec2i(1);
+  let l = textureSampleBias(t, s, coords, bias, offset);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleBias_ArrayIndex) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_bias_signed(coords : vec2f, array : i32, bias : f32) {
+  let l = textureSampleBias(t, s, coords, array, bias);
+}
+
+fn sample_bias_unsigned(coords : vec2f, array : u32, bias : f32) {
+  let l = textureSampleBias(t, s, coords, array, bias);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  sample_bias_signed(non_uniform.xy, i32(non_uniform.x), non_uniform.x);
+  sample_bias_unsigned(non_uniform.xy, u32(non_uniform.x), non_uniform.x);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleCompare) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn sample_compare(coords : vec2f, depth_ref : f32) {
+  let l = textureSampleCompare(t, s, coords, depth_ref);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleCompare_Offset) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn sample_compare(coords : vec2f, depth_ref : f32) {
+  const offset = vec2i(1);
+  let l = textureSampleCompare(t, s, coords, depth_ref, offset);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleCompare_ArrayIndex) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d_array;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn sample_compare_signed(coords : vec2f, array : i32, depth_ref : f32) {
+  let l = textureSampleCompare(t, s, coords, array, depth_ref);
+}
+
+fn sample_compare_unsigned(coords : vec2f, array : u32, depth_ref : f32) {
+  let l = textureSampleCompare(t, s, coords, array, depth_ref);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  sample_compare_signed(non_uniform.xy, i32(non_uniform.x), non_uniform.x);
+  sample_compare_unsigned(non_uniform.xy, u32(non_uniform.x), non_uniform.x);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleCompareLevel) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn sample_compare_level(coords : vec2f, depth_ref : f32) {
+  let l = textureSampleCompareLevel(t, s, coords, depth_ref);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleCompareLevel_Offset) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn sample_compare_level(coords : vec2f, depth_ref : f32) {
+  const offset = vec2i(1);
+  let l = textureSampleCompareLevel(t, s, coords, depth_ref, offset);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleCompareLevel_ArrayIndex) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_depth_2d_array;
+
+@group(0) @binding(1) var s : sampler_comparison;
+
+fn sample_compare_level_signed(coords : vec2f, array : i32, depth_ref : f32) {
+  let l = textureSampleCompareLevel(t, s, coords, array, depth_ref);
+}
+
+fn sample_compare_level_unsigned(coords : vec2f, array : u32, depth_ref : f32) {
+  let l = textureSampleCompareLevel(t, s, coords, array, depth_ref);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  sample_compare_level_signed(non_uniform.xy, i32(non_uniform.x), non_uniform.x);
+  sample_compare_level_unsigned(non_uniform.xy, u32(non_uniform.x), non_uniform.x);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleGrad) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_compare_level(coords : vec2f, ddx : vec2f, ddy : vec2f) {
+  let l = textureSampleGrad(t, s, coords, ddx, ddy);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleGrad_Offset) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_compare_level(coords : vec2f, ddx : vec2f, ddy : vec2f) {
+  const offset = vec2i(1);
+  let l = textureSampleGrad(t, s, coords, ddx, ddy, offset);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleGrad_ArrayIndex) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_grad_signed(coords : vec2f, array : i32, ddx : vec2f, ddy : vec2f) {
+  let l = textureSampleGrad(t, s, coords, array, ddx, ddy);
+}
+
+fn sample_grad_unsigned(coords : vec2f, array : u32, ddx : vec2f, ddy : vec2f) {
+  let l = textureSampleGrad(t, s, coords, array, ddx, ddy);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  sample_grad_signed(non_uniform.xy, i32(non_uniform.x), non_uniform.xy, non_uniform.xy);
+  sample_grad_unsigned(non_uniform.xy, u32(non_uniform.x), non_uniform.xy, non_uniform.xy);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleLevel) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_compare_level(coords : vec2f, level : f32) {
+  let l = textureSampleLevel(t, s, coords, level);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleLevel_Offset) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_compare_level(coords : vec2f, level : f32) {
+  const offset = vec2i(1);
+  let l = textureSampleLevel(t, s, coords, level, offset);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleLevel_ArrayIndex) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d_array<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_compare_level_signed(coords : vec2f, array : i32, level : f32) {
+  let l = textureSampleLevel(t, s, coords, array, level);
+}
+
+fn sample_compare_level_unsigned(coords : vec2f, array : u32, level : f32) {
+  let l = textureSampleLevel(t, s, coords, array, level);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  sample_compare_level_signed(non_uniform.xy, i32(non_uniform.x), non_uniform.x);
+  sample_compare_level_unsigned(non_uniform.xy, u32(non_uniform.x), non_uniform.x);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureSampleBaseClampToEdge) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_external;
+
+@group(0) @binding(1) var s : sampler;
+
+fn sample_base_clamp_to_edge(coords : vec2f) {
+  let l = textureSampleBaseClampToEdge(t, s, coords);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureStore_1D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn store_signed(coords : i32, value : vec4i) {
+  textureStore(t, coords, value);
+}
+
+fn store_unsigned(coords : u32, value : vec4i) {
+  textureStore(t, coords, value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(i32(non_uniform.x), vec4i(non_uniform));
+  store_unsigned(u32(non_uniform.x), vec4i(non_uniform));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn store_signed(coords : i32, value : vec4i) {
+  textureStore(t, clamp(coords, 0, i32((textureDimensions(t) - 1))), value);
+}
+
+fn store_unsigned(coords : u32, value : vec4i) {
+  textureStore(t, min(coords, (textureDimensions(t) - 1)), value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(i32(non_uniform.x), vec4i(non_uniform));
+  store_unsigned(u32(non_uniform.x), vec4i(non_uniform));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn store_signed(coords : i32, value : vec4i) {
+  let coords_1 = u32(coords);
+  if (all((coords_1 < textureDimensions(t)))) {
+    textureStore(t, coords_1, value);
+  }
+}
+
+fn store_unsigned(coords : u32, value : vec4i) {
+  let coords_2 = u32(coords);
+  if (all((coords_2 < textureDimensions(t)))) {
+    textureStore(t, coords_2, value);
+  }
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(i32(non_uniform.x), vec4i(non_uniform));
+  store_unsigned(u32(non_uniform.x), vec4i(non_uniform));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureStore_2D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_storage_2d<rgba8sint, write>;
+
+fn store_signed(coords : vec2i, value : vec4i) {
+  textureStore(t, coords, value);
+}
+
+fn store_unsigned(coords : vec2u, value : vec4i) {
+  textureStore(t, coords, value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec2i(non_uniform.xy), vec4i(non_uniform));
+  store_unsigned(vec2u(non_uniform.xy), vec4i(non_uniform));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_storage_2d<rgba8sint, write>;
+
+fn store_signed(coords : vec2i, value : vec4i) {
+  textureStore(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t) - vec2(1)))), value);
+}
+
+fn store_unsigned(coords : vec2u, value : vec4i) {
+  textureStore(t, min(coords, (textureDimensions(t) - vec2(1))), value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec2i(non_uniform.xy), vec4i(non_uniform));
+  store_unsigned(vec2u(non_uniform.xy), vec4i(non_uniform));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_storage_2d<rgba8sint, write>;
+
+fn store_signed(coords : vec2i, value : vec4i) {
+  let coords_1 = vec2<u32>(coords);
+  if (all((coords_1 < textureDimensions(t)))) {
+    textureStore(t, coords_1, value);
+  }
+}
+
+fn store_unsigned(coords : vec2u, value : vec4i) {
+  let coords_2 = vec2<u32>(coords);
+  if (all((coords_2 < textureDimensions(t)))) {
+    textureStore(t, coords_2, value);
+  }
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec2i(non_uniform.xy), vec4i(non_uniform));
+  store_unsigned(vec2u(non_uniform.xy), vec4i(non_uniform));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureStore_2DArray) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_storage_2d_array<rgba8sint, write>;
+
+fn store_signed(coords : vec2i, array : i32, value : vec4i) {
+  textureStore(t, coords, array, value);
+}
+
+fn store_unsigned(coords : vec2u, array : u32, value : vec4i) {
+  textureStore(t, coords, array, value);
+}
+
+fn store_mixed(coords : vec2i, array : u32, value : vec4i) {
+  textureStore(t, coords, array, value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec2i(non_uniform.xy), i32(non_uniform.x), vec4i(non_uniform));
+  store_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), vec4i(non_uniform));
+  store_mixed(vec2i(non_uniform.xy), u32(non_uniform.x), vec4i(non_uniform));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_storage_2d_array<rgba8sint, write>;
+
+fn store_signed(coords : vec2i, array : i32, value : vec4i) {
+  textureStore(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t) - vec2(1)))), clamp(array, 0, i32((textureNumLayers(t) - 1))), value);
+}
+
+fn store_unsigned(coords : vec2u, array : u32, value : vec4i) {
+  textureStore(t, min(coords, (textureDimensions(t) - vec2(1))), min(array, (textureNumLayers(t) - 1)), value);
+}
+
+fn store_mixed(coords : vec2i, array : u32, value : vec4i) {
+  textureStore(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t) - vec2(1)))), min(array, (textureNumLayers(t) - 1)), value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec2i(non_uniform.xy), i32(non_uniform.x), vec4i(non_uniform));
+  store_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), vec4i(non_uniform));
+  store_mixed(vec2i(non_uniform.xy), u32(non_uniform.x), vec4i(non_uniform));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_storage_2d_array<rgba8sint, write>;
+
+fn store_signed(coords : vec2i, array : i32, value : vec4i) {
+  let coords_1 = vec2<u32>(coords);
+  let array_idx = u32(array);
+  if ((all((coords_1 < textureDimensions(t))) & (array_idx < textureNumLayers(t)))) {
+    textureStore(t, coords_1, array_idx, value);
+  }
+}
+
+fn store_unsigned(coords : vec2u, array : u32, value : vec4i) {
+  let coords_2 = vec2<u32>(coords);
+  let array_idx_1 = u32(array);
+  if ((all((coords_2 < textureDimensions(t))) & (array_idx_1 < textureNumLayers(t)))) {
+    textureStore(t, coords_2, array_idx_1, value);
+  }
+}
+
+fn store_mixed(coords : vec2i, array : u32, value : vec4i) {
+  let coords_3 = vec2<u32>(coords);
+  let array_idx_2 = u32(array);
+  if ((all((coords_3 < textureDimensions(t))) & (array_idx_2 < textureNumLayers(t)))) {
+    textureStore(t, coords_3, array_idx_2, value);
+  }
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec2i(non_uniform.xy), i32(non_uniform.x), vec4i(non_uniform));
+  store_unsigned(vec2u(non_uniform.xy), u32(non_uniform.x), vec4i(non_uniform));
+  store_mixed(vec2i(non_uniform.xy), u32(non_uniform.x), vec4i(non_uniform));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureStore_3D) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_storage_3d<rgba8sint, write>;
+
+fn store_signed(coords : vec3i, value : vec4i) {
+  textureStore(t, coords, value);
+}
+
+fn store_unsigned(coords : vec3u, value : vec4i) {
+  textureStore(t, coords, value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec3i(non_uniform.xyz), vec4i(non_uniform));
+  store_unsigned(vec3u(non_uniform.xyz), vec4i(non_uniform));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_storage_3d<rgba8sint, write>;
+
+fn store_signed(coords : vec3i, value : vec4i) {
+  textureStore(t, clamp(coords, vec3(0), vec3<i32>((textureDimensions(t) - vec3(1)))), value);
+}
+
+fn store_unsigned(coords : vec3u, value : vec4i) {
+  textureStore(t, min(coords, (textureDimensions(t) - vec3(1))), value);
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec3i(non_uniform.xyz), vec4i(non_uniform));
+  store_unsigned(vec3u(non_uniform.xyz), vec4i(non_uniform));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_storage_3d<rgba8sint, write>;
+
+fn store_signed(coords : vec3i, value : vec4i) {
+  let coords_1 = vec3<u32>(coords);
+  if (all((coords_1 < textureDimensions(t)))) {
+    textureStore(t, coords_1, value);
+  }
+}
+
+fn store_unsigned(coords : vec3u, value : vec4i) {
+  let coords_2 = vec3<u32>(coords);
+  if (all((coords_2 < textureDimensions(t)))) {
+    textureStore(t, coords_2, value);
+  }
+}
+
+@fragment
+fn main(@builtin(position) non_uniform : vec4f) {
+  store_signed(vec3i(non_uniform.xyz), vec4i(non_uniform));
+  store_unsigned(vec3u(non_uniform.xyz), vec4i(non_uniform));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Other
+////////////////////////////////////////////////////////////////////////////////
+TEST_P(RobustnessTest, ShadowedVariable) {
     auto* src = R"(
 fn f() {
   var a : array<f32, 3>;
   var i : u32;
   {
-     var a : array<f32, 5>;
-     var b : f32 = a[i];
+    var a : array<f32, 5>;
+    var b : f32 = a[i];
   }
   var c : f32 = a[i];
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 fn f() {
   var a : array<f32, 3>;
   var i : u32;
@@ -1099,19 +3514,42 @@
   }
   var c : f32 = a[min(i, 2u)];
 }
-)";
+)",
+                          /* predicate */ R"(
+fn f() {
+  var a : array<f32, 3>;
+  var i : u32;
+  {
+    var a : array<f32, 5>;
+    let index = i;
+    let predicate = (u32(index) <= 4u);
+    var predicated_expr : f32;
+    if (predicate) {
+      predicated_expr = a[index];
+    }
+    var b : f32 = predicated_expr;
+  }
+  let index_1 = i;
+  let predicate_1 = (u32(index_1) <= 2u);
+  var predicated_expr_1 : f32;
+  if (predicate_1) {
+    predicated_expr_1 = a[index_1];
+  }
+  var c : f32 = predicated_expr_1;
+}
+)");
 
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
     EXPECT_EQ(expect, str(got));
 }
 
 // Check that existing use of min() and arrayLength() do not get renamed.
-TEST_F(RobustnessTest, DontRenameSymbols) {
+TEST_P(RobustnessTest, DontRenameSymbols) {
     auto* src = R"(
 struct S {
   a : f32,
   b : array<f32>,
-};
+}
 
 @group(0) @binding(0) var<storage, read> s : S;
 
@@ -1120,11 +3558,13 @@
 fn f() {
   let b : f32 = s.b[c];
   let x : i32 = min(1, 2);
-  let y : u32 = arrayLength(&s.b);
+  let y : u32 = arrayLength(&(s.b));
 }
 )";
 
-    auto* expect = R"(
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
 struct S {
   a : f32,
   b : array<f32>,
@@ -1139,257 +3579,39 @@
   let x : i32 = min(1, 2);
   let y : u32 = arrayLength(&(s.b));
 }
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-const char* kOmitSourceShader = R"(
+)",
+                          /* predicate */ R"(
 struct S {
-  vector : vec3<f32>,
-  fixed_arr : array<f32, 4>,
-  runtime_arr : array<f32>,
-};
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct U {
-  vector : vec4<f32>,
-  fixed_arr : array<vec4<f32>, 4>,
-};
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  // i32
-  {
-    let i = 0i;
-    var storage_vector : f32 = s.vector[i];
-    var storage_fixed_arr : f32 = s.fixed_arr[i];
-    var storage_runtime_arr : f32 = s.runtime_arr[i];
-    var uniform_vector : f32 = u.vector[i];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
-  }
-  // u32
-  {
-    let i = 0u;
-    var storage_vector : f32 = s.vector[i];
-    var storage_fixed_arr : f32 = s.fixed_arr[i];
-    var storage_runtime_arr : f32 = s.runtime_arr[i];
-    var uniform_vector : f32 = u.vector[i];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
-  }
-}
-)";
-
-TEST_F(RobustnessTest, OmitNone) {
-    auto* expect =
-        R"(
-struct S {
-  vector : vec3<f32>,
-  fixed_arr : array<f32, 4>,
-  runtime_arr : array<f32>,
+  a : f32,
+  b : array<f32>,
 }
 
 @group(0) @binding(0) var<storage, read> s : S;
 
-struct U {
-  vector : vec4<f32>,
-  fixed_arr : array<vec4<f32>, 4>,
-}
-
-@group(1) @binding(0) var<uniform> u : U;
+const c : u32 = 1u;
 
 fn f() {
-  {
-    let i = 0i;
-    var storage_vector : f32 = s.vector[min(u32(i), 2u)];
-    var storage_fixed_arr : f32 = s.fixed_arr[min(u32(i), 3u)];
-    var storage_runtime_arr : f32 = s.runtime_arr[min(u32(i), (arrayLength(&(s.runtime_arr)) - 1u))];
-    var uniform_vector : f32 = u.vector[min(u32(i), 3u)];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(u32(i), 3u)];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(u32(i), 3u)];
+  let index = c;
+  let predicate = (u32(index) <= (arrayLength(&(s.b)) - 1u));
+  var predicated_expr : f32;
+  if (predicate) {
+    predicated_expr = s.b[index];
   }
-  {
-    let i = 0u;
-    var storage_vector : f32 = s.vector[min(i, 2u)];
-    var storage_fixed_arr : f32 = s.fixed_arr[min(i, 3u)];
-    var storage_runtime_arr : f32 = s.runtime_arr[min(i, (arrayLength(&(s.runtime_arr)) - 1u))];
-    var uniform_vector : f32 = u.vector[min(i, 3u)];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(i, 3u)];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(i, 3u)];
-  }
+  let b : f32 = predicated_expr;
+  let x : i32 = min(1, 2);
+  let y : u32 = arrayLength(&(s.b));
 }
-)";
+)");
 
-    Robustness::Config cfg;
-    DataMap data;
-    data.Add<Robustness::Config>(cfg);
-
-    auto got = Run<Robustness>(kOmitSourceShader, data);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, OmitStorage) {
-    auto* expect =
-        R"(
-struct S {
-  vector : vec3<f32>,
-  fixed_arr : array<f32, 4>,
-  runtime_arr : array<f32>,
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct U {
-  vector : vec4<f32>,
-  fixed_arr : array<vec4<f32>, 4>,
-}
-
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  {
-    let i = 0i;
-    var storage_vector : f32 = s.vector[i];
-    var storage_fixed_arr : f32 = s.fixed_arr[i];
-    var storage_runtime_arr : f32 = s.runtime_arr[i];
-    var uniform_vector : f32 = u.vector[min(u32(i), 3u)];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(u32(i), 3u)];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(u32(i), 3u)];
-  }
-  {
-    let i = 0u;
-    var storage_vector : f32 = s.vector[i];
-    var storage_fixed_arr : f32 = s.fixed_arr[i];
-    var storage_runtime_arr : f32 = s.runtime_arr[i];
-    var uniform_vector : f32 = u.vector[min(i, 3u)];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(i, 3u)];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(i, 3u)];
-  }
-}
-)";
-
-    Robustness::Config cfg;
-    cfg.omitted_address_spaces.insert(Robustness::AddressSpace::kStorage);
-
-    DataMap data;
-    data.Add<Robustness::Config>(cfg);
-
-    auto got = Run<Robustness>(kOmitSourceShader, data);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, OmitUniform) {
-    auto* expect =
-        R"(
-struct S {
-  vector : vec3<f32>,
-  fixed_arr : array<f32, 4>,
-  runtime_arr : array<f32>,
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct U {
-  vector : vec4<f32>,
-  fixed_arr : array<vec4<f32>, 4>,
-}
-
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  {
-    let i = 0i;
-    var storage_vector : f32 = s.vector[min(u32(i), 2u)];
-    var storage_fixed_arr : f32 = s.fixed_arr[min(u32(i), 3u)];
-    var storage_runtime_arr : f32 = s.runtime_arr[min(u32(i), (arrayLength(&(s.runtime_arr)) - 1u))];
-    var uniform_vector : f32 = u.vector[i];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
-  }
-  {
-    let i = 0u;
-    var storage_vector : f32 = s.vector[min(i, 2u)];
-    var storage_fixed_arr : f32 = s.fixed_arr[min(i, 3u)];
-    var storage_runtime_arr : f32 = s.runtime_arr[min(i, (arrayLength(&(s.runtime_arr)) - 1u))];
-    var uniform_vector : f32 = u.vector[i];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
-  }
-}
-)";
-
-    Robustness::Config cfg;
-    cfg.omitted_address_spaces.insert(Robustness::AddressSpace::kUniform);
-
-    DataMap data;
-    data.Add<Robustness::Config>(cfg);
-
-    auto got = Run<Robustness>(kOmitSourceShader, data);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, OmitBoth) {
-    auto* expect =
-        R"(
-struct S {
-  vector : vec3<f32>,
-  fixed_arr : array<f32, 4>,
-  runtime_arr : array<f32>,
-}
-
-@group(0) @binding(0) var<storage, read> s : S;
-
-struct U {
-  vector : vec4<f32>,
-  fixed_arr : array<vec4<f32>, 4>,
-}
-
-@group(1) @binding(0) var<uniform> u : U;
-
-fn f() {
-  {
-    let i = 0i;
-    var storage_vector : f32 = s.vector[i];
-    var storage_fixed_arr : f32 = s.fixed_arr[i];
-    var storage_runtime_arr : f32 = s.runtime_arr[i];
-    var uniform_vector : f32 = u.vector[i];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
-  }
-  {
-    let i = 0u;
-    var storage_vector : f32 = s.vector[i];
-    var storage_fixed_arr : f32 = s.fixed_arr[i];
-    var storage_runtime_arr : f32 = s.runtime_arr[i];
-    var uniform_vector : f32 = u.vector[i];
-    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
-    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
-  }
-}
-)";
-
-    Robustness::Config cfg;
-    cfg.omitted_address_spaces.insert(Robustness::AddressSpace::kStorage);
-    cfg.omitted_address_spaces.insert(Robustness::AddressSpace::kUniform);
-
-    DataMap data;
-    data.Add<Robustness::Config>(cfg);
-
-    auto got = Run<Robustness>(kOmitSourceShader, data);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, WorkgroupOverrideCount) {
+TEST_P(RobustnessTest, WorkgroupOverrideCount) {
     auto* src = R"(
 override N = 123;
+
 var<workgroup> w : array<f32, N>;
 
 fn f() {
@@ -1398,13 +3620,1880 @@
 )";
 
     auto* expect =
-        R"(error: array size is an override-expression, when expected a constant-expression.
-Was the SubstituteOverride transform run?)";
+        Expect(GetParam(),
+               /* ignore */ src,
+               /* clamp */
+               R"(error: array size is an override-expression, when expected a constant-expression.
+Was the SubstituteOverride transform run?)",
+               /* predicate */
+               R"(error: array size is an override-expression, when expected a constant-expression.
+Was the SubstituteOverride transform run?)");
 
-    auto got = Run<Robustness>(src);
+    auto got = Run<Robustness>(src, Config(GetParam()));
 
     EXPECT_EQ(expect, str(got));
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// atomic predication
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, AtomicLoad) {
+    auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  let i = 0;
+  let r = atomicLoad(&(a[i]));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  let i = 0;
+  let r = atomicLoad(&(a[min(u32(i), 3u)]));
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  var predicated_value : i32;
+  if (predicate) {
+    predicated_value = atomicLoad(&(a[index]));
+  }
+  let r = predicated_value;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, AtomicAnd) {
+    auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  let i = 0;
+  atomicAnd(&(a[i]), 10);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  let i = 0;
+  atomicAnd(&(a[min(u32(i), 3u)]), 10);
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  if (predicate) {
+    atomicAnd(&(a[index]), 10);
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// workgroupUniformLoad
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, WorkgroupUniformLoad) {
+    auto* src = R"(
+var<workgroup> a : array<u32, 32>;
+
+@compute @workgroup_size(1)
+fn f() {
+  let i = 1;
+  let p = &(a[i]);
+  let v = workgroupUniformLoad(p);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+var<workgroup> a : array<u32, 32>;
+
+@compute @workgroup_size(1)
+fn f() {
+  let i = 1;
+  let p = &(a[min(u32(i), 31u)]);
+  let v = workgroupUniformLoad(p);
+}
+)",
+                          /* predicate */ R"(
+var<workgroup> a : array<u32, 32>;
+
+@compute @workgroup_size(1)
+fn f() {
+  let i = 1;
+  let index = i;
+  let predicate = (u32(index) <= 31u);
+  let p = &(a[index]);
+  var predicated_value : u32;
+  if (predicate) {
+    predicated_value = workgroupUniformLoad(p);
+  } else {
+    workgroupBarrier();
+  }
+  let v = predicated_value;
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Usage in loops
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, ArrayVal_ForLoopInit) {
+    auto* src = R"(
+fn f() {
+  let a = array<i32, 3>();
+  var v = 1;
+  for(var i = a[v]; (i < 3); i++) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+fn f() {
+  let a = array<i32, 3>();
+  var v = 1;
+  for(var i = a[min(u32(v), 2u)]; (i < 3); i++) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+fn f() {
+  let a = array<i32, 3>();
+  var v = 1;
+  {
+    let index = v;
+    let predicate = (u32(index) <= 2u);
+    var predicated_expr : i32;
+    if (predicate) {
+      predicated_expr = a[index];
+    }
+    var i = predicated_expr;
+    loop {
+      if (!((i < 3))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, ArrayVal_ForLoopCond) {
+    auto* src = R"(
+fn f() {
+  let a = array<i32, 3>();
+  var v = 1;
+  for(var i = 0; (i < a[v]); i++) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+fn f() {
+  let a = array<i32, 3>();
+  var v = 1;
+  for(var i = 0; (i < a[min(u32(v), 2u)]); i++) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+fn f() {
+  let a = array<i32, 3>();
+  var v = 1;
+  {
+    var i = 0;
+    loop {
+      let index = v;
+      let predicate = (u32(index) <= 2u);
+      var predicated_expr : i32;
+      if (predicate) {
+        predicated_expr = a[index];
+      }
+      if (!((i < predicated_expr))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, ArrayVal_ForLoopCont) {
+    auto* src = R"(
+fn f() {
+  let a = array<i32, 3>();
+  for(var i = 0; (i < 5); i = a[i]) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+fn f() {
+  let a = array<i32, 3>();
+  for(var i = 0; (i < 5); i = a[min(u32(i), 2u)]) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+fn f() {
+  let a = array<i32, 3>();
+  {
+    var i = 0;
+    loop {
+      if (!((i < 5))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        let index = i;
+        let predicate = (u32(index) <= 2u);
+        var predicated_expr : i32;
+        if (predicate) {
+          predicated_expr = a[index];
+        }
+        i = predicated_expr;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_ForLoopInit) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var coords = vec2(1);
+  var level = 1;
+  for(var i = textureLoad(t, coords, level).x; (i < 3); i++) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var coords = vec2(1);
+  var level = 1;
+  {
+    let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+    var i = textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t, level_idx) - vec2(1)))), level_idx).x;
+    loop {
+      if (!((i < 3))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var coords = vec2(1);
+  var level = 1;
+  {
+    let level_idx = u32(level);
+    let num_levels = textureNumLevels(t);
+    let coords_1 = vec2<u32>(coords);
+    var predicated_value : vec4<i32>;
+    if (((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1))))))) {
+      predicated_value = textureLoad(t, coords_1, level_idx);
+    }
+    var i = predicated_value.x;
+    loop {
+      if (!((i < 3))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_ForLoopCond) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var coords = vec2(1);
+  var level = 1;
+  for(var i = 0; (i < textureLoad(t, coords, level).x); i++) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var coords = vec2(1);
+  var level = 1;
+  {
+    var i = 0;
+    loop {
+      let level_idx = min(u32(level), (textureNumLevels(t) - 1));
+      if (!((i < textureLoad(t, clamp(coords, vec2(0), vec2<i32>((textureDimensions(t, level_idx) - vec2(1)))), level_idx).x))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var coords = vec2(1);
+  var level = 1;
+  {
+    var i = 0;
+    loop {
+      let level_idx = u32(level);
+      let num_levels = textureNumLevels(t);
+      let coords_1 = vec2<u32>(coords);
+      var predicated_value : vec4<i32>;
+      if (((level_idx < num_levels) & all((coords_1 < textureDimensions(t, min(level_idx, (num_levels - 1))))))) {
+        predicated_value = textureLoad(t, coords_1, level_idx);
+      }
+      if (!((i < predicated_value.x))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureLoad_ForLoopCont) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var level = 1;
+  for(var i = 0; (i < 5); i = textureLoad(t, vec2(i), i).x) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var level = 1;
+  {
+    var i = 0;
+    loop {
+      if (!((i < 5))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        let level_idx = min(u32(i), (textureNumLevels(t) - 1));
+        i = textureLoad(t, clamp(vec2(i), vec2(0), vec2<i32>((textureDimensions(t, level_idx) - vec2(1)))), level_idx).x;
+      }
+    }
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_2d<i32>;
+
+fn f() {
+  var level = 1;
+  {
+    var i = 0;
+    loop {
+      if (!((i < 5))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        let level_idx = u32(i);
+        let num_levels = textureNumLevels(t);
+        let coords = vec2<u32>(vec2(i));
+        var predicated_value : vec4<i32>;
+        if (((level_idx < num_levels) & all((coords < textureDimensions(t, min(level_idx, (num_levels - 1))))))) {
+          predicated_value = textureLoad(t, coords, level_idx);
+        }
+        i = predicated_value.x;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureStore_ForLoopInit) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn f() {
+  var coords = 1;
+  var value = vec4(1);
+  var i = 0;
+  for(textureStore(t, coords, value); (i < 3); i++) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn f() {
+  var coords = 1;
+  var value = vec4(1);
+  var i = 0;
+  for(textureStore(t, clamp(coords, 0, i32((textureDimensions(t) - 1))), value); (i < 3); i++) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn f() {
+  var coords = 1;
+  var value = vec4(1);
+  var i = 0;
+  {
+    let coords_1 = u32(coords);
+    if (all((coords_1 < textureDimensions(t)))) {
+      textureStore(t, coords_1, value);
+    }
+    loop {
+      if (!((i < 3))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, TextureStore_ForLoopCont) {
+    auto* src = R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn f() {
+  var level = 1;
+  var value = vec4(1);
+  for(var i = 0; (i < 3); textureStore(t, i, value)) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn f() {
+  var level = 1;
+  var value = vec4(1);
+  for(var i = 0; (i < 3); textureStore(t, clamp(i, 0, i32((textureDimensions(t) - 1))), value)) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var t : texture_storage_1d<rgba8sint, write>;
+
+fn f() {
+  var level = 1;
+  var value = vec4(1);
+  {
+    var i = 0;
+    loop {
+      if (!((i < 3))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        let coords = u32(i);
+        if (all((coords < textureDimensions(t)))) {
+          textureStore(t, coords, value);
+        }
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, AtomicXor_ForLoopInit) {
+    auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  var i = 0;
+  for(atomicXor(&(a[i]), 4); (i < 3); i++) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  var i = 0;
+  for(atomicXor(&(a[min(u32(i), 3u)]), 4); (i < 3); i++) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  var i = 0;
+  {
+    let index = i;
+    let predicate = (u32(index) <= 3u);
+    if (predicate) {
+      atomicXor(&(a[index]), 4);
+    }
+    loop {
+      if (!((i < 3))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, AtomicXor_ForLoopCond) {
+    auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  for(var i = 0; (i < atomicXor(&(a[i]), 4)); i++) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  for(var i = 0; (i < atomicXor(&(a[min(u32(i), 3u)]), 4)); i++) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  {
+    var i = 0;
+    loop {
+      let index = i;
+      let predicate = (u32(index) <= 3u);
+      var predicated_value : i32;
+      if (predicate) {
+        predicated_value = atomicXor(&(a[index]), 4);
+      }
+      if (!((i < predicated_value))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        i++;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, AtomicXor_ForLoopCont) {
+    auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  for(var i = 0; (i < 5); i = atomicXor(&(a[i]), 4)) {
+    var in_loop = 42;
+  }
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  for(var i = 0; (i < 5); i = atomicXor(&(a[min(u32(i), 3u)]), 4)) {
+    var in_loop = 42;
+  }
+}
+)",
+                          /* predicate */ R"(
+@group(0) @binding(0) var<storage, read_write> a : array<atomic<i32>, 4>;
+
+fn f() {
+  {
+    var i = 0;
+    loop {
+      if (!((i < 5))) {
+        break;
+      }
+      {
+        var in_loop = 42;
+      }
+
+      continuing {
+        let index = i;
+        let predicate = (u32(index) <= 3u);
+        var predicated_value : i32;
+        if (predicate) {
+          predicated_value = atomicXor(&(a[index]), 4);
+        }
+        i = predicated_value;
+      }
+    }
+  }
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// User pointer parameters
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_P(RobustnessTest, Read_PrivatePointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  y(1, &(a[1]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, p_predicate : bool, post : i32) -> i32 {
+  var predicated_expr : i32;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, p_predicate_1 : bool, post : i32) -> i32 {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  y(1, &(a[1]), true, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_WorkgroupPointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  y(1, &(a[1]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, p_predicate : bool, post : i32) -> i32 {
+  var predicated_expr : i32;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, p_predicate_1 : bool, post : i32) -> i32 {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  y(1, &(a[1]), true, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_UniformPointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<uniform, vec4i>, post : vec4i) -> vec4i {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : vec4i, p : ptr<uniform, vec4i>, post : vec4i) -> vec4i {
+  return x(pre, p, post);
+}
+
+fn z() {
+  y(vec4(1), &(a[1]), vec4(2));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<uniform, vec4i>, p_predicate : bool, post : vec4i) -> vec4i {
+  var predicated_expr : vec4<i32>;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : vec4i, p : ptr<uniform, vec4i>, p_predicate_1 : bool, post : vec4i) -> vec4i {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  y(vec4(1), &(a[1]), true, vec4(2));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_StoragePointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<storage, vec4i>, post : vec4i) -> vec4i {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : vec4i, p : ptr<storage, vec4i>, post : vec4i) -> vec4i {
+  return x(pre, p, post);
+}
+
+fn z() {
+  y(vec4(1), &(a[1]), vec4(2));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<storage, vec4i>, p_predicate : bool, post : vec4i) -> vec4i {
+  var predicated_expr : vec4<i32>;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : vec4i, p : ptr<storage, vec4i>, p_predicate_1 : bool, post : vec4i) -> vec4i {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  y(vec4(1), &(a[1]), true, vec4(2));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_FunctionPointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  y(1, &(a[1]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, p_predicate : bool, post : i32) -> i32 {
+  var predicated_expr : i32;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, p_predicate_1 : bool, post : i32) -> i32 {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  y(1, &(a[1]), true, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_PrivatePointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[i]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[min(u32(i), 3u)]), 2);
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, p_predicate : bool, post : i32) -> i32 {
+  var predicated_expr : i32;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, p_predicate_1 : bool, post : i32) -> i32 {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(1, &(a[index]), predicate, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_WorkgroupPointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[i]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[min(u32(i), 3u)]), 2);
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, p_predicate : bool, post : i32) -> i32 {
+  var predicated_expr : i32;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, p_predicate_1 : bool, post : i32) -> i32 {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(1, &(a[index]), predicate, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_UniformPointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<uniform, vec4i>, post : vec4i) -> vec4i {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : vec4i, p : ptr<uniform, vec4i>, post : vec4i) -> vec4i {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(vec4(1), &(a[i]), vec4(2));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<uniform, vec4i>, post : vec4i) -> vec4i {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : vec4i, p : ptr<uniform, vec4i>, post : vec4i) -> vec4i {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(vec4(1), &(a[min(u32(i), 3u)]), vec4(2));
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<uniform, vec4i>, p_predicate : bool, post : vec4i) -> vec4i {
+  var predicated_expr : vec4<i32>;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : vec4i, p : ptr<uniform, vec4i>, p_predicate_1 : bool, post : vec4i) -> vec4i {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(vec4(1), &(a[index]), predicate, vec4(2));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_StoragePointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<storage, vec4i>, post : vec4i) -> vec4i {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : vec4i, p : ptr<storage, vec4i>, post : vec4i) -> vec4i {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(vec4(1), &(a[i]), vec4(2));
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<storage, vec4i>, post : vec4i) -> vec4i {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : vec4i, p : ptr<storage, vec4i>, post : vec4i) -> vec4i {
+  return x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(vec4(1), &(a[min(u32(i), 3u)]), vec4(2));
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> a : array<vec4i, 4>;
+
+fn x(pre : vec4i, p : ptr<storage, vec4i>, p_predicate : bool, post : vec4i) -> vec4i {
+  var predicated_expr : vec4<i32>;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : vec4i, p : ptr<storage, vec4i>, p_predicate_1 : bool, post : vec4i) -> vec4i {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(vec4(1), &(a[index]), predicate, vec4(2));
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Read_FunctionPointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  let i = 0;
+  y(1, &(a[i]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return ((pre + *(p)) + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return x(pre, p, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  let i = 0;
+  y(1, &(a[min(u32(i), 3u)]), 2);
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, p_predicate : bool, post : i32) -> i32 {
+  var predicated_expr : i32;
+  if (p_predicate) {
+    predicated_expr = *(p);
+  }
+  return ((pre + predicated_expr) + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, p_predicate_1 : bool, post : i32) -> i32 {
+  return x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(1, &(a[index]), predicate, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_PrivatePointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  y(1, &(a[1]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<private, i32>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  y(1, &(a[1]), true, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_WorkgroupPointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  y(1, &(a[1]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  y(1, &(a[1]), true, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_StoragePointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<storage, i32, read_write>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<storage, i32, read_write>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  y(1, &(a[1]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<storage, i32, read_write>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<storage, i32, read_write>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  y(1, &(a[1]), true, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_FunctionPointerParameter_IndexWithConstant) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  y(1, &(a[1]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ src,
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<function, i32>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  y(1, &(a[1]), true, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_PrivatePointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[i]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<private, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[min(u32(i), 3u)]), 2);
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<private, i32>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<private, i32>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(1, &(a[index]), predicate, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_WorkgroupPointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[i]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[min(u32(i), 3u)]), 2);
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<workgroup, i32>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<workgroup, i32>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(1, &(a[index]), predicate, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_StoragePointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<storage, i32, read_write>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<storage, i32, read_write>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[i]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<storage, i32, read_write>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<storage, i32, read_write>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  let i = 0;
+  y(1, &(a[min(u32(i), 3u)]), 2);
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> a : array<i32, 4>;
+
+fn x(pre : i32, p : ptr<storage, i32, read_write>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<storage, i32, read_write>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(1, &(a[index]), predicate, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_P(RobustnessTest, Write_FunctionPointerParameter_IndexWithLet) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  let i = 0;
+  y(1, &(a[i]), 2);
+}
+)";
+
+    auto* expect = Expect(GetParam(),
+                          /* ignore */ src,
+                          /* clamp */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, post : i32) {
+  *(p) = (pre + post);
+}
+
+fn y(pre : i32, p : ptr<function, i32>, post : i32) {
+  x(pre, p, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  let i = 0;
+  y(1, &(a[min(u32(i), 3u)]), 2);
+}
+)",
+                          /* predicate */ R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn x(pre : i32, p : ptr<function, i32>, p_predicate : bool, post : i32) {
+  if (p_predicate) {
+    *(p) = (pre + post);
+  }
+}
+
+fn y(pre : i32, p : ptr<function, i32>, p_predicate_1 : bool, post : i32) {
+  x(pre, p, p_predicate_1, post);
+}
+
+fn z() {
+  var a : array<i32, 4>;
+  let i = 0;
+  let index = i;
+  let predicate = (u32(index) <= 3u);
+  y(1, &(a[index]), predicate, 2);
+}
+)");
+
+    auto got = Run<Robustness>(src, Config(GetParam()));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         RobustnessTest,
+                         testing::Values(Robustness::Action::kIgnore,
+                                         Robustness::Action::kClamp,
+                                         Robustness::Action::kPredicate));
 }  // namespace
 }  // namespace tint::transform
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index b3924cc..043861b 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -294,7 +294,7 @@
 SpirvAtomic::~SpirvAtomic() = default;
 
 SpirvAtomic::Stub::Stub(ProgramID pid, ast::NodeID nid, sem::BuiltinType b)
-    : Base(pid, nid), builtin(b) {}
+    : Base(pid, nid, utils::Empty), builtin(b) {}
 SpirvAtomic::Stub::~Stub() = default;
 std::string SpirvAtomic::Stub::InternalName() const {
     return "@internal(spirv-atomic " + std::string(sem::str(builtin)) + ")";
diff --git a/src/tint/utils/unique_vector.h b/src/tint/utils/unique_vector.h
index 6cb8f88..eca0d4c 100644
--- a/src/tint/utils/unique_vector.h
+++ b/src/tint/utils/unique_vector.h
@@ -30,6 +30,9 @@
 /// Attempting to add a duplicate is a no-op.
 template <typename T, size_t N, typename HASH = std::hash<T>, typename EQUAL = std::equal_to<T>>
 struct UniqueVector {
+    /// STL-friendly alias to T. Used by gmock.
+    using value_type = T;
+
     /// Constructor
     UniqueVector() = default;
 
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index c31667d..98d9798 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -156,13 +156,12 @@
                 }));
     auto* ctor_target = b->create<sem::ValueConstructor>(
         packed_sem_ty,
-        utils::Transform(
-            packed,
-            [&](const tint::sem::ValueExpression* arg, size_t i) -> const sem::Parameter* {
-                return b->create<sem::Parameter>(
-                    nullptr, static_cast<uint32_t>(i), arg->Type()->UnwrapRef(),
-                    builtin::AddressSpace::kUndefined, builtin::Access::kUndefined);
-            }),
+        utils::Transform(packed,
+                         [&](const tint::sem::ValueExpression* arg, size_t i) {
+                             return b->create<sem::Parameter>(
+                                 nullptr, static_cast<uint32_t>(i), arg->Type()->UnwrapRef(),
+                                 builtin::AddressSpace::kUndefined, builtin::Access::kUndefined);
+                         }),
         sem::EvaluationStage::kRuntime);
     auto* ctor_sem = b->create<sem::Call>(ctor_ast, ctor_target, sem::EvaluationStage::kRuntime,
                                           std::move(packed), statement,
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 24c6386..02c1bac 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -166,7 +166,8 @@
     manager.Add<transform::PromoteSideEffectsToDecl>();
 
     if (!options.disable_robustness) {
-        // Robustness must come before BuiltinPolyfill
+        // Robustness must come after PromoteSideEffectsToDecl
+        // Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
         manager.Add<transform::Robustness>();
     }
 
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 823e6bd..f0247c4 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -182,7 +182,8 @@
     manager.Add<transform::PromoteSideEffectsToDecl>();
 
     if (!options.disable_robustness) {
-        // Robustness must come before BuiltinPolyfill
+        // Robustness must come after PromoteSideEffectsToDecl
+        // Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
         manager.Add<transform::Robustness>();
     }
 
@@ -1125,7 +1126,7 @@
     utils::StringStream& out,
     const ast::CallExpression* expr,
     const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
-    auto const buffer = program_->Symbols().NameFor(intrinsic->buffer);
+    auto const buffer = program_->Symbols().NameFor(intrinsic->Buffer()->identifier->symbol);
     auto* const offset = expr->args[0];
 
     // offset in bytes
@@ -1413,7 +1414,7 @@
     utils::StringStream& out,
     const ast::CallExpression* expr,
     const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
-    auto const buffer = program_->Symbols().NameFor(intrinsic->buffer);
+    auto const buffer = program_->Symbols().NameFor(intrinsic->Buffer()->identifier->symbol);
     auto* const offset = expr->args[0];
     auto* const value = expr->args[1];
 
@@ -1581,7 +1582,7 @@
     const auto name = builder_.Symbols().NameFor(func->name->symbol);
     auto& buf = *current_buffer_;
 
-    auto const buffer = program_->Symbols().NameFor(intrinsic->buffer);
+    auto const buffer = program_->Symbols().NameFor(intrinsic->Buffer()->identifier->symbol);
 
     auto rmw = [&](const char* hlsl) -> bool {
         {
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index b2c9ffa..5c9e11b 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -207,7 +207,8 @@
     manager.Add<transform::PromoteSideEffectsToDecl>();
 
     if (!options.disable_robustness) {
-        // Robustness must come before BuiltinPolyfill
+        // Robustness must come after PromoteSideEffectsToDecl
+        // Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
         manager.Add<transform::Robustness>();
     }
 
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index c46bf9a..72a3629 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -68,7 +68,8 @@
     manager.Add<transform::MergeReturn>();
 
     if (!options.disable_robustness) {
-        // Robustness must come before BuiltinPolyfill
+        // Robustness must come after PromoteSideEffectsToDecl
+        // Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
         manager.Add<transform::Robustness>();
     }
 
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index c052075..69aaabd 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -1149,7 +1149,7 @@
 
 bool GeneratorImpl::EmitConstAssert(const ast::ConstAssert* stmt) {
     auto out = line();
-    out << "static_assert ";
+    out << "const_assert ";
     if (!EmitExpression(out, stmt->condition)) {
         return false;
     }
diff --git a/src/tint/writer/wgsl/generator_impl_const_assert_test.cc b/src/tint/writer/wgsl/generator_impl_const_assert_test.cc
index 9f76d65..b785357 100644
--- a/src/tint/writer/wgsl/generator_impl_const_assert_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_const_assert_test.cc
@@ -27,7 +27,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_EQ(gen.result(), R"(static_assert true;
+    EXPECT_EQ(gen.result(), R"(const_assert true;
 )");
 }
 
@@ -38,7 +38,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(fn f() {
-  static_assert true;
+  const_assert true;
 }
 )");
 }