[dawn] IR Reflection for single entry and overrides

This adds 'reflection.h' as scaffolding for future API.

Bug: 380043635
Change-Id: I5021a1c30c2019630347b1a6982cd0043dd31d63
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/232335
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Peter McNeeley <petermcneeley@google.com>
diff --git a/include/tint/tint.h b/include/tint/tint.h
index ebfd5a5..be2bb51 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -36,6 +36,7 @@
 
 #include "src/tint/api/common/vertex_pulling_config.h"
 #include "src/tint/api/tint.h"
+#include "src/tint/lang/core/ir/reflection.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/type/manager.h"
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index 4b0cd77..9399a89 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -479,35 +479,52 @@
 MaybeError ComputePipeline::InitializeImpl() {
     const ProgrammableStage& computeStage = GetStage(SingleShaderStage::Compute);
 
-    tint::Program transformedProgram;
-    tint::ast::transform::Manager transformManager;
-    tint::ast::transform::DataMap transformInputs;
-
+    std::optional<tint::ast::transform::SubstituteOverride::Config> substituteOverrideConfig;
     if (!computeStage.metadata->overrides.empty()) {
-        transformManager.Add<tint::ast::transform::SingleEntryPoint>();
-        transformInputs.Add<tint::ast::transform::SingleEntryPoint::Config>(
-            computeStage.entryPoint.c_str());
-
-        // This needs to run after SingleEntryPoint transform which removes unused overrides for
-        // current entry point.
-        transformManager.Add<tint::ast::transform::SubstituteOverride>();
-        transformInputs.Add<tint::ast::transform::SubstituteOverride::Config>(
-            BuildSubstituteOverridesTransformConfig(computeStage));
+        substituteOverrideConfig = BuildSubstituteOverridesTransformConfig(computeStage);
     }
 
-    auto tintProgram = computeStage.module->GetTintProgram();
-    DAWN_TRY_ASSIGN(transformedProgram, RunTransforms(&transformManager, &(tintProgram->program),
-                                                      transformInputs, nullptr, nullptr));
+    // Convert the AST program to an IR module.
+    auto ir =
+        tint::wgsl::reader::ProgramToLoweredIR(computeStage.module->GetTintProgram()->program);
+    DAWN_INVALID_IF(ir != tint::Success, "An error occurred while generating Tint IR\n%s",
+                    ir.Failure().reason);
 
-    // Do the workgroup size validation.
+    auto singleEntryPointResult =
+        tint::core::ir::transform::SingleEntryPoint(ir.Get(), computeStage.entryPoint.c_str());
+    DAWN_INVALID_IF(singleEntryPointResult != tint::Success,
+                    "Pipeline single entry point (IR) failed:\n%s",
+                    singleEntryPointResult.Failure().reason);
+
+    if (substituteOverrideConfig) {
+        // this needs to run after SingleEntryPoint transform which removes unused
+        // overrides for the current entry point.
+        tint::core::ir::transform::SubstituteOverridesConfig cfg;
+        cfg.map = substituteOverrideConfig->map;
+        auto substituteOverridesResult =
+            tint::core::ir::transform::SubstituteOverrides(ir.Get(), cfg);
+        DAWN_INVALID_IF(substituteOverridesResult != tint::Success,
+                        "Pipeline override substitution (IR) failed:\n%s",
+                        substituteOverridesResult.Failure().reason);
+    }
+
+    auto limits = LimitsForCompilationRequest::Create(GetDevice()->GetLimits().v1);
+    auto adapterSupportedLimits =
+        LimitsForCompilationRequest::Create(GetDevice()->GetAdapter()->GetLimits().v1);
+    auto maxSubgroupSize = GetDevice()->GetAdapter()->GetPhysicalDevice()->GetSubgroupMaxSize();
+
+    //  Workgroup validation has to come after overrides to have been substituted.
+    auto wgInfo = tint::core::ir::GetWorkgroupInfo(ir.Get());
+
+    DAWN_INVALID_IF(wgInfo != tint::Success, "Getting workgroup info has failed (IR):\n%s",
+                    wgInfo.Failure().reason);
+
     Extent3D _;
-    DAWN_TRY_ASSIGN(
-        _, ValidateComputeStageWorkgroupSize(
-               transformedProgram, computeStage.entryPoint.c_str(),
-               computeStage.metadata->usesSubgroupMatrix,
-               GetDevice()->GetAdapter()->GetPhysicalDevice()->GetSubgroupMaxSize(),
-               LimitsForCompilationRequest::Create(GetDevice()->GetLimits().v1),
-               LimitsForCompilationRequest::Create(GetDevice()->GetAdapter()->GetLimits().v1)));
+    DAWN_TRY_ASSIGN(_, ValidateComputeStageWorkgroupSize(
+                           wgInfo.Get().x, wgInfo.Get().y, wgInfo.Get().z,
+                           wgInfo.Get().storage_size, computeStage.metadata->usesSubgroupMatrix,
+                           maxSubgroupSize, limits, adapterSupportedLimits));
+
     return {};
 }
 
diff --git a/src/tint/lang/core/ir/BUILD.bazel b/src/tint/lang/core/ir/BUILD.bazel
index d45214d..c9e5f0c 100644
--- a/src/tint/lang/core/ir/BUILD.bazel
+++ b/src/tint/lang/core/ir/BUILD.bazel
@@ -82,6 +82,7 @@
     "operand_instruction.cc",
     "override.cc",
     "phony.cc",
+    "reflection.cc",
     "return.cc",
     "store.cc",
     "store_vector_element.cc",
@@ -145,6 +146,7 @@
     "referenced_functions.h",
     "referenced_module_decls.h",
     "referenced_module_vars.h",
+    "reflection.h",
     "return.h",
     "store.h",
     "store_vector_element.h",
@@ -224,6 +226,7 @@
     "referenced_functions_test.cc",
     "referenced_module_decls_test.cc",
     "referenced_module_vars_test.cc",
+    "reflection_test.cc",
     "return_test.cc",
     "store_test.cc",
     "store_vector_element_test.cc",
diff --git a/src/tint/lang/core/ir/BUILD.cmake b/src/tint/lang/core/ir/BUILD.cmake
index ce7d11a..6c92812 100644
--- a/src/tint/lang/core/ir/BUILD.cmake
+++ b/src/tint/lang/core/ir/BUILD.cmake
@@ -134,6 +134,8 @@
   lang/core/ir/referenced_functions.h
   lang/core/ir/referenced_module_decls.h
   lang/core/ir/referenced_module_vars.h
+  lang/core/ir/reflection.cc
+  lang/core/ir/reflection.h
   lang/core/ir/return.cc
   lang/core/ir/return.h
   lang/core/ir/store.cc
@@ -230,6 +232,7 @@
   lang/core/ir/referenced_functions_test.cc
   lang/core/ir/referenced_module_decls_test.cc
   lang/core/ir/referenced_module_vars_test.cc
+  lang/core/ir/reflection_test.cc
   lang/core/ir/return_test.cc
   lang/core/ir/store_test.cc
   lang/core/ir/store_vector_element_test.cc
diff --git a/src/tint/lang/core/ir/BUILD.gn b/src/tint/lang/core/ir/BUILD.gn
index 019dfc3..3cdec1d 100644
--- a/src/tint/lang/core/ir/BUILD.gn
+++ b/src/tint/lang/core/ir/BUILD.gn
@@ -135,6 +135,8 @@
     "referenced_functions.h",
     "referenced_module_decls.h",
     "referenced_module_vars.h",
+    "reflection.cc",
+    "reflection.h",
     "return.cc",
     "return.h",
     "store.cc",
@@ -225,6 +227,7 @@
       "referenced_functions_test.cc",
       "referenced_module_decls_test.cc",
       "referenced_module_vars_test.cc",
+      "reflection_test.cc",
       "return_test.cc",
       "store_test.cc",
       "store_vector_element_test.cc",
diff --git a/src/tint/lang/core/ir/reflection.cc b/src/tint/lang/core/ir/reflection.cc
new file mode 100644
index 0000000..824b0b6
--- /dev/null
+++ b/src/tint/lang/core/ir/reflection.cc
@@ -0,0 +1,71 @@
+// 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/reflection.h"
+
+#include <string>
+#include <utility>
+
+#include "src/tint/lang/core/ir/var.h"
+#include "src/tint/lang/core/type/pointer.h"
+
+namespace tint::core::ir {
+
+Result<WorkgroupInfo> GetWorkgroupInfo(core::ir::Module& ir) {
+    std::optional<std::array<uint32_t, 3>> const_wg_size;
+    for (auto func : ir.functions) {
+        if (!func->IsEntryPoint()) {
+            continue;
+        }
+        const_wg_size = func->WorkgroupSizeAsConst();
+    }
+
+    if (!const_wg_size) {
+        return Failure{"IR GetWorkgroupInfo: Could not find workgroup size"};
+    }
+
+    size_t wg_storage_size = 0u;
+    for (auto* inst : *ir.root_block) {
+        if (auto* as_var = inst->As<core::ir::Var>()) {
+            auto* ptr = as_var->Result(0)->Type()->As<core::type::Pointer>();
+            if (ptr->AddressSpace() != core::AddressSpace::kWorkgroup) {
+                continue;
+            }
+            auto* ty = ptr->StoreType();
+            uint32_t align = ty->Align();
+            uint32_t size = ty->Size();
+
+            // This essentially matches std430 layout rules from GLSL, which are in
+            // turn specified as an upper bound for Vulkan layout sizing.
+            wg_storage_size += tint::RoundUp(16u, tint::RoundUp(align, size));
+        }
+    }
+    return WorkgroupInfo{(*const_wg_size)[0], (*const_wg_size)[1], (*const_wg_size)[2],
+                         wg_storage_size};
+}
+
+}  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/reflection.h b/src/tint/lang/core/ir/reflection.h
new file mode 100644
index 0000000..a44040f
--- /dev/null
+++ b/src/tint/lang/core/ir/reflection.h
@@ -0,0 +1,55 @@
+// 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_REFLECTION_H_
+#define SRC_TINT_LANG_CORE_IR_REFLECTION_H_
+
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/utils/result.h"
+
+namespace tint::core::ir {
+/// Workgroup size information
+struct WorkgroupInfo {
+    /// The x-component
+    uint32_t x = 0;
+    /// The y-component
+    uint32_t y = 0;
+    /// The z-component
+    uint32_t z = 0;
+
+    /// The needed workgroup storage size
+    size_t storage_size = 0;
+};
+
+/// Generate WorkgroupInfo for an IR module input.
+/// @param ir the workgroup info for the IR module
+/// @returns the resulting WorkgroupInfo for the IR, or failure.
+Result<WorkgroupInfo> GetWorkgroupInfo(core::ir::Module& ir);
+
+}  // namespace  tint::core::ir
+
+#endif  // SRC_TINT_LANG_CORE_IR_REFLECTION_H_
diff --git a/src/tint/lang/core/ir/reflection_test.cc b/src/tint/lang/core/ir/reflection_test.cc
new file mode 100644
index 0000000..b43b1dc
--- /dev/null
+++ b/src/tint/lang/core/ir/reflection_test.cc
@@ -0,0 +1,175 @@
+// 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/reflection.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+#include "src/tint/utils/result.h"
+
+namespace tint::core::ir {
+namespace {
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+class IR_ReflectionTest : public IRTestHelper {
+  protected:
+    /// @returns the module as a disassembled string
+    std::string Disassemble() const { return "\n" + ir::Disassembler(mod).Plain(); }
+};
+
+TEST_F(IR_ReflectionTest, GetWorkgroupInfoBasic) {
+    auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a"));
+    auto* foo = b.ComputeFunction("foo", 3_u, 5_u, 7_u);
+    b.Append(foo->Block(), [&] {  //
+        b.Load(var_a);
+        b.Return(foo);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %a:ptr<workgroup, u32, read_write> = var undef
+}
+
+%foo = @compute @workgroup_size(3u, 5u, 7u) func():void {
+  $B2: {
+    %3:u32 = load %a
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, Disassemble());
+
+    auto res = GetWorkgroupInfo(mod);
+    EXPECT_TRUE(res == tint::Success);
+    EXPECT_EQ(res->x, 3u);
+    EXPECT_EQ(res->y, 5u);
+    EXPECT_EQ(res->z, 7u);
+    EXPECT_EQ(res->storage_size, 16u);
+}
+
+TEST_F(IR_ReflectionTest, GetWorkgroupInfoMultiVar) {
+    auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a"));
+    auto* var_b = mod.root_block->Append(b.Var<workgroup, u32>("b"));
+    auto* var_c = mod.root_block->Append(b.Var<workgroup, u32>("c"));
+    auto* var_d = mod.root_block->Append(b.Var<workgroup, u32>("d"));
+    auto* foo = b.ComputeFunction("foo", 128_u, 1_u, 1_u);
+    b.Append(foo->Block(), [&] {  //
+        b.Load(var_a);
+        b.Load(var_b);
+        b.Load(var_c);
+        b.Load(var_d);
+        b.Return(foo);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %a:ptr<workgroup, u32, read_write> = var undef
+  %b:ptr<workgroup, u32, read_write> = var undef
+  %c:ptr<workgroup, u32, read_write> = var undef
+  %d:ptr<workgroup, u32, read_write> = var undef
+}
+
+%foo = @compute @workgroup_size(128u, 1u, 1u) func():void {
+  $B2: {
+    %6:u32 = load %a
+    %7:u32 = load %b
+    %8:u32 = load %c
+    %9:u32 = load %d
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, Disassemble());
+
+    auto res = GetWorkgroupInfo(mod);
+    EXPECT_TRUE(res == tint::Success);
+    EXPECT_EQ(res->x, 128u);
+    EXPECT_EQ(res->y, 1u);
+    EXPECT_EQ(res->z, 1u);
+    EXPECT_EQ(res->storage_size, 64u);
+}
+
+TEST_F(IR_ReflectionTest, GetWorkgroupInfoNoVar) {
+    auto* foo = b.ComputeFunction("foo", 128_u, 1_u, 1_u);
+    b.Append(foo->Block(), [&] {  //
+        b.Return(foo);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(128u, 1u, 1u) func():void {
+  $B1: {
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, Disassemble());
+
+    auto res = GetWorkgroupInfo(mod);
+    EXPECT_TRUE(res == tint::Success);
+    EXPECT_EQ(res->x, 128u);
+    EXPECT_EQ(res->y, 1u);
+    EXPECT_EQ(res->z, 1u);
+    EXPECT_EQ(res->storage_size, 0u);
+}
+
+TEST_F(IR_ReflectionTest, GetWorkgroupInfoFailNoWorkgroupSize) {
+    // Referenced.
+    auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a"));
+    auto* foo = b.Function("foo", ty.void_(), Function::PipelineStage::kCompute);
+    b.Append(foo->Block(), [&] {  //
+        b.Load(var_a);
+        b.Return(foo);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %a:ptr<workgroup, u32, read_write> = var undef
+}
+
+%foo = @compute func():void {
+  $B2: {
+    %3:u32 = load %a
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, Disassemble());
+
+    auto res = GetWorkgroupInfo(mod);
+    EXPECT_FALSE(res == tint::Success);
+
+    auto* failure_msg = R"(IR GetWorkgroupInfo: Could not find workgroup size)";
+    EXPECT_EQ(failure_msg, res.Failure().reason);
+}
+
+}  // namespace
+}  // namespace tint::core::ir