[spirv-fuzz] Generate resource bindings configuration.

The fuzzer passes in randomized options to the SPIR-V writer. The
resource bindings are required to match up to the module itself, so
replace the random resource_binding options with generated options.

Fixed: 442161264
Change-Id: Ife9cc326f61a8af52e7930b0ce17728a0e9f8673
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/260734
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 9d1953b..a6208a9 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -41,12 +41,12 @@
 #include "spirv-tools/libspirv.hpp"
 #endif  // TINT_BUILD_SPV_READER || TINT_BUILD_SPV_WRITER
 
-#include "src/tint/api/common/resource_binding_config.h"
 #include "src/tint/api/tint.h"
 #include "src/tint/cmd/common/helper.h"
 #include "src/tint/lang/core/ir/core_builtin_call.h"
 #include "src/tint/lang/core/ir/disassembler.h"
 #include "src/tint/lang/core/ir/load.h"
+#include "src/tint/lang/core/ir/transform/resource_binding_helper.h"
 #include "src/tint/lang/core/ir/transform/single_entry_point.h"
 #include "src/tint/lang/core/ir/transform/substitute_overrides.h"
 #include "src/tint/lang/core/ir/var.h"
@@ -775,74 +775,6 @@
 }
 TINT_END_DISABLE_WARNING(UNSAFE_BUFFER_USAGE);
 
-[[maybe_unused]] std::optional<tint::ResourceBindingConfig> GenerateResourceBindingConfig(
-    tint::core::ir::Module& mod) {
-    tint::ResourceBindingConfig cfg;
-
-    uint32_t max_group = 0;
-    std::vector<tint::core::ir::Var*> resource_bindings;
-
-    for (auto* inst : *mod.root_block) {
-        auto* var = inst->As<tint::core::ir::Var>();
-        if (!var) {
-            continue;
-        }
-        if (!var->BindingPoint().has_value()) {
-            continue;
-        }
-
-        auto* ty = var->Result()->Type()->UnwrapPtr()->As<tint::core::type::ResourceBinding>();
-        if (!ty) {
-            continue;
-        }
-        resource_bindings.push_back(var);
-
-        max_group = std::max(max_group, var->BindingPoint()->group);
-    }
-    if (resource_bindings.empty()) {
-        return std::nullopt;
-    }
-
-    max_group += 1;
-    uint32_t binding = 0;
-    for (auto* var : resource_bindings) {
-        tint::ResourceBindingInfo info{
-            .storage_buffer_binding = tint::BindingPoint{.group = max_group, .binding = binding++},
-            .default_binding_type_order = {},
-        };
-
-        std::vector<tint::core::ir::Value*> to_check = {var->Result()};
-        while (!to_check.empty()) {
-            auto* next = to_check.back();
-            to_check.pop_back();
-
-            for (auto& usage : next->UsagesUnsorted()) {
-                tint::Switch(
-                    usage->instruction,
-                    [&](tint::core::ir::Load* l) { to_check.push_back(l->Result()); },
-                    [&](tint::core::ir::CoreBuiltinCall* call) {
-                        if (call->Func() != tint::core::BuiltinFn::kHasBinding &&
-                            call->Func() != tint::core::BuiltinFn::kGetBinding) {
-                            return;
-                        }
-
-                        auto exp = call->ExplicitTemplateParams();
-                        TINT_ASSERT(exp.Length() == 1);
-                        info.default_binding_type_order.push_back(
-                            tint::core::type::TypeToResourceType(exp[0]));
-                    },
-                    TINT_ICE_ON_NO_MATCH);
-            }
-        }
-        // Sort so we get stable generated results
-        std::sort(info.default_binding_type_order.begin(), info.default_binding_type_order.end());
-
-        cfg.bindings.insert({var->BindingPoint().value(), std::move(info)});
-    }
-
-    return cfg;
-}
-
 [[maybe_unused]] tint::diag::Result<tint::core::ir::transform::SubstituteOverridesConfig>
 CreateOverrideMap(const Options& options, tint::inspector::Inspector& inspector) {
     auto override_names = inspector.GetNamedOverrideIds();
@@ -945,7 +877,7 @@
     }
 
     gen_options.bindings = tint::spirv::writer::GenerateBindings(ir);
-    gen_options.resource_binding = GenerateResourceBindingConfig(ir);
+    gen_options.resource_binding = tint::core::ir::transform::GenerateResourceBindingConfig(ir);
 
     // Enable the Vulkan Memory Model if needed.
     for (auto* ty : ir.Types()) {
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index 64ff860..3bf2b3e 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -62,6 +62,7 @@
     "remove_terminator_args.cc",
     "rename_conflicts.cc",
     "resource_binding.cc",
+    "resource_binding_helper.cc",
     "robustness.cc",
     "shader_io.cc",
     "signed_integer_polyfill.cc",
@@ -98,6 +99,7 @@
     "remove_terminator_args.h",
     "rename_conflicts.h",
     "resource_binding.h",
+    "resource_binding_helper.h",
     "robustness.h",
     "shader_io.h",
     "signed_integer_polyfill.h",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index 5ea8d42..d980be2 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -86,6 +86,8 @@
   lang/core/ir/transform/rename_conflicts.h
   lang/core/ir/transform/resource_binding.cc
   lang/core/ir/transform/resource_binding.h
+  lang/core/ir/transform/resource_binding_helper.cc
+  lang/core/ir/transform/resource_binding_helper.h
   lang/core/ir/transform/robustness.cc
   lang/core/ir/transform/robustness.h
   lang/core/ir/transform/shader_io.cc
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 689cef3..249e3ed 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -92,6 +92,8 @@
     "rename_conflicts.h",
     "resource_binding.cc",
     "resource_binding.h",
+    "resource_binding_helper.cc",
+    "resource_binding_helper.h",
     "robustness.cc",
     "robustness.h",
     "shader_io.cc",
diff --git a/src/tint/lang/core/ir/transform/resource_binding_helper.cc b/src/tint/lang/core/ir/transform/resource_binding_helper.cc
new file mode 100644
index 0000000..fa962ef
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/resource_binding_helper.cc
@@ -0,0 +1,107 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/transform/resource_binding_helper.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "src/tint/lang/core/ir/core_builtin_call.h"
+#include "src/tint/lang/core/ir/load.h"
+#include "src/tint/lang/core/ir/var.h"
+#include "src/tint/lang/core/type/resource_type.h"
+#include "src/tint/utils/rtti/switch.h"
+
+namespace tint::core::ir::transform {
+
+std::optional<ResourceBindingConfig> GenerateResourceBindingConfig(Module& mod) {
+    ResourceBindingConfig cfg;
+
+    uint32_t max_group = 0;
+    std::vector<Var*> resource_bindings;
+
+    for (auto* inst : *mod.root_block) {
+        auto* var = inst->As<Var>();
+        if (!var) {
+            continue;
+        }
+        if (!var->BindingPoint().has_value()) {
+            continue;
+        }
+
+        auto* ty = var->Result()->Type()->UnwrapPtr()->As<type::ResourceBinding>();
+        if (!ty) {
+            continue;
+        }
+        resource_bindings.push_back(var);
+
+        max_group = std::max(max_group, var->BindingPoint()->group);
+    }
+    if (resource_bindings.empty()) {
+        return std::nullopt;
+    }
+
+    max_group += 1;
+    uint32_t binding = 0;
+    for (auto* var : resource_bindings) {
+        ResourceBindingInfo info{
+            .storage_buffer_binding = BindingPoint{.group = max_group, .binding = binding++},
+            .default_binding_type_order = {},
+        };
+
+        std::vector<Value*> to_check = {var->Result()};
+        while (!to_check.empty()) {
+            auto* next = to_check.back();
+            to_check.pop_back();
+
+            for (auto& usage : next->UsagesUnsorted()) {
+                Switch(
+                    usage->instruction, [&](Load* l) { to_check.push_back(l->Result()); },
+                    [&](CoreBuiltinCall* call) {
+                        if (call->Func() != BuiltinFn::kHasBinding &&
+                            call->Func() != BuiltinFn::kGetBinding) {
+                            return;
+                        }
+
+                        auto exp = call->ExplicitTemplateParams();
+                        TINT_ASSERT(exp.Length() == 1);
+                        info.default_binding_type_order.push_back(type::TypeToResourceType(exp[0]));
+                    },
+                    TINT_ICE_ON_NO_MATCH);
+            }
+        }
+        // Sort so we get stable generated results
+        std::sort(info.default_binding_type_order.begin(), info.default_binding_type_order.end());
+
+        cfg.bindings.insert({var->BindingPoint().value(), std::move(info)});
+    }
+
+    return cfg;
+}
+
+}  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/resource_binding_helper.h b/src/tint/lang/core/ir/transform/resource_binding_helper.h
new file mode 100644
index 0000000..ee087a9
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/resource_binding_helper.h
@@ -0,0 +1,43 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_RESOURCE_BINDING_HELPER_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_RESOURCE_BINDING_HELPER_H_
+
+#include <optional>
+
+#include "src/tint/api/common/resource_binding_config.h"
+#include "src/tint/lang/core/ir/module.h"
+
+namespace tint::core::ir::transform {
+
+std::optional<tint::ResourceBindingConfig> GenerateResourceBindingConfig(
+    tint::core::ir::Module& mod);
+
+}
+
+#endif  // SRC_TINT_LANG_CORE_IR_TRANSFORM_RESOURCE_BINDING_HELPER_H_
diff --git a/src/tint/lang/spirv/writer/BUILD.cmake b/src/tint/lang/spirv/writer/BUILD.cmake
index 36ffd03..e2fe748 100644
--- a/src/tint/lang/spirv/writer/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/BUILD.cmake
@@ -174,6 +174,7 @@
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_ir
+  tint_lang_core_ir_transform
   tint_lang_core_type
   tint_utils
   tint_utils_bytes
diff --git a/src/tint/lang/spirv/writer/BUILD.gn b/src/tint/lang/spirv/writer/BUILD.gn
index 8121453..c01e8b7 100644
--- a/src/tint/lang/spirv/writer/BUILD.gn
+++ b/src/tint/lang/spirv/writer/BUILD.gn
@@ -156,6 +156,7 @@
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
       "${tint_src_dir}/lang/core/ir",
+      "${tint_src_dir}/lang/core/ir/transform",
       "${tint_src_dir}/lang/core/type",
       "${tint_src_dir}/utils",
       "${tint_src_dir}/utils/bytes",
diff --git a/src/tint/lang/spirv/writer/writer_fuzz.cc b/src/tint/lang/spirv/writer/writer_fuzz.cc
index bce4dc8..f74299d 100644
--- a/src/tint/lang/spirv/writer/writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/writer_fuzz.cc
@@ -29,6 +29,7 @@
 
 #include "src/tint/cmd/fuzz/ir/fuzz.h"
 #include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/transform/resource_binding_helper.h"
 #include "src/tint/lang/spirv/validate/validate.h"
 #include "src/tint/lang/spirv/writer/helpers/generate_bindings.h"
 #include "src/tint/lang/spirv/writer/printer/printer.h"
@@ -43,6 +44,8 @@
     }
 
     options.bindings = GenerateBindings(module);
+    options.resource_binding = tint::core::ir::transform::GenerateResourceBindingConfig(module);
+
     auto output = Generate(module, options);
     if (output != Success) {
         TINT_ICE() << "Generate() failed after CanGenerate() succeeded: "