HLSL-IR: implement NumWorkgroups builtin support

Bug: 357896924
Bug: 42251045
Change-Id: Icd426b9a62cd077affae59ccacdbe51b5aa8276f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/206514
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/tint/lang/core/ir/transform/helper_test.h b/src/tint/lang/core/ir/transform/helper_test.h
index e0e2583..22ed109 100644
--- a/src/tint/lang/core/ir/transform/helper_test.h
+++ b/src/tint/lang/core/ir/transform/helper_test.h
@@ -51,7 +51,7 @@
     template <typename TRANSFORM, typename... ARGS>
     void Run(TRANSFORM&& transform_func, ARGS&&... args) {
         // Run the transform.
-        auto result = transform_func(mod, args...);
+        auto result = transform_func(mod, std::forward<ARGS>(args)...);
         EXPECT_EQ(result, Success);
         if (result != Success) {
             return;
diff --git a/src/tint/lang/hlsl/writer/raise/raise.cc b/src/tint/lang/hlsl/writer/raise/raise.cc
index 607fcd43..3e49693 100644
--- a/src/tint/lang/hlsl/writer/raise/raise.cc
+++ b/src/tint/lang/hlsl/writer/raise/raise.cc
@@ -153,6 +153,19 @@
         RUN_TRANSFORM(core::ir::transform::Robustness, module, config);
     }
 
+    if (!options.disable_workgroup_init) {
+        // Must run before ShaderIO as it may introduce a builtin parameter (local_invocation_index)
+        RUN_TRANSFORM(core::ir::transform::ZeroInitWorkgroupMemory, module);
+    }
+
+    // ShaderIO must be run before DecomposeUniformAccess because it might
+    // introduce a uniform buffer for kNumWorkgroups.
+    {
+        raise::ShaderIOConfig config;
+        config.num_workgroups_binding = options.root_constant_binding_point;
+        RUN_TRANSFORM(raise::ShaderIO, module, config);
+    }
+
     RUN_TRANSFORM(core::ir::transform::DirectVariableAccess, module,
                   core::ir::transform::DirectVariableAccessOptions{});
     // DecomposeStorageAccess must come after Robustness and DirectVariableAccess
@@ -160,18 +173,12 @@
     // Comes after DecomposeStorageAccess.
     RUN_TRANSFORM(raise::DecomposeUniformAccess, module);
 
-    if (!options.disable_workgroup_init) {
-        RUN_TRANSFORM(core::ir::transform::ZeroInitWorkgroupMemory, module);
-    }
-
     // TODO(dsinclair): LocalizeStructArrayAssignment
     // TODO(dsinclair): PixelLocal transform
     // TODO(dsinclair): TruncateInterstageVariables
-    // TODO(dsinclair): NumWorkgroupsFromUniform
     // TODO(dsinclair): CalculateArrayLength
     // TODO(dsinclair): RemoveContinueInSwitch
 
-    RUN_TRANSFORM(raise::ShaderIO, module);
     // DemoteToHelper must come before any transform that introduces non-core instructions.
     // Run after ShaderIO to ensure the discards are added to the entry point it introduces.
     RUN_TRANSFORM(core::ir::transform::DemoteToHelper, module);
diff --git a/src/tint/lang/hlsl/writer/raise/shader_io.cc b/src/tint/lang/hlsl/writer/raise/shader_io.cc
index df7e3dc..0da0c57 100644
--- a/src/tint/lang/hlsl/writer/raise/shader_io.cc
+++ b/src/tint/lang/hlsl/writer/raise/shader_io.cc
@@ -49,6 +49,9 @@
 /// For HLSL, move all inputs to a struct passed as an entry point parameter, and wrap outputs in
 /// a structure returned by the entry point.
 struct StateImpl : core::ir::transform::ShaderIOBackendState {
+    /// The config
+    const ShaderIOConfig& config;
+
     /// The input parameter
     core::ir::FunctionParam* input_param = nullptr;
 
@@ -61,12 +64,14 @@
     /// The output values to return from the entry point.
     Vector<core::ir::Value*, 4> output_values;
 
-    // Indices of subgroup invocation id and size, if set
+    // Indices of inputs that require special handling
     std::optional<uint32_t> subgroup_invocation_id_index;
     std::optional<uint32_t> subgroup_size_index;
+    std::optional<uint32_t> num_workgroups_index;
 
     /// Constructor
-    StateImpl(core::ir::Module& mod, core::ir::Function* f) : ShaderIOBackendState(mod, f) {}
+    StateImpl(core::ir::Module& mod, core::ir::Function* f, const ShaderIOConfig& c)
+        : ShaderIOBackendState(mod, f), config(c) {}
 
     /// Destructor
     ~StateImpl() override {}
@@ -183,6 +188,9 @@
                 } else if (*builtin == core::BuiltinValue::kSubgroupSize) {
                     subgroup_size_index = i;
                     continue;
+                } else if (*builtin == core::BuiltinValue::kNumWorkgroups) {
+                    num_workgroups_index = i;
+                    continue;
                 }
             }
 
@@ -257,12 +265,45 @@
                 output_struct->AddUsage(core::type::PipelineStageUsage::kVertexOutput);
                 break;
             case core::ir::Function::PipelineStage::kCompute:
+                output_struct->AddUsage(core::type::PipelineStageUsage::kComputeOutput);
+                break;
             case core::ir::Function::PipelineStage::kUndefined:
                 TINT_UNREACHABLE();
         }
         return output_struct;
     }
 
+    /// Handles kNumWorkgroups builtin by emitting a UBO to hold the num_workgroups value,
+    /// along with the load of the value. Returns the loaded value.
+    core::ir::Value* GetInputForNumWorkgroups(core::ir::Builder& builder) {
+        // Create uniform var that will receive the number of workgroups
+        core::ir::Var* num_wg_var = nullptr;
+        builder.Append(ir.root_block, [&] {
+            num_wg_var = builder.Var("tint_num_workgroups", ty.ptr(uniform, ty.vec3<u32>()));
+        });
+        if (config.num_workgroups_binding.has_value()) {
+            // If config.num_workgroups_binding holds a value, use it.
+            auto bp = *config.num_workgroups_binding;
+            num_wg_var->SetBindingPoint(bp.group, bp.binding);
+        } else {
+            // Otherwise, use the binding 0 of the largest used group plus 1, or group 0 if no
+            // resources are bound.
+            uint32_t group = 0;
+            for (auto* inst : *ir.root_block.Get()) {
+                if (auto* var = inst->As<core::ir::Var>()) {
+                    if (const auto& bp = var->BindingPoint()) {
+                        if (bp->group >= group) {
+                            group = bp->group + 1;
+                        }
+                    }
+                }
+            }
+            num_wg_var->SetBindingPoint(group, 0);
+        }
+        auto* load = builder.Load(num_wg_var);
+        return load->Result(0);
+    }
+
     /// @copydoc ShaderIO::BackendState::GetInput
     core::ir::Value* GetInput(core::ir::Builder& builder, uint32_t idx) override {
         if (subgroup_invocation_id_index == idx) {
@@ -275,6 +316,9 @@
                 .Call<hlsl::ir::BuiltinCall>(ty.u32(), hlsl::BuiltinFn::kWaveGetLaneCount)
                 ->Result(0);
         }
+        if (num_workgroups_index == idx) {
+            return GetInputForNumWorkgroups(builder);
+        }
 
         auto index = input_indices[idx];
 
@@ -307,14 +351,14 @@
 };
 }  // namespace
 
-Result<SuccessType> ShaderIO(core::ir::Module& ir) {
+Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "ShaderIO transform");
     if (result != Success) {
         return result;
     }
 
     core::ir::transform::RunShaderIOBase(ir, [&](core::ir::Module& mod, core::ir::Function* func) {
-        return std::make_unique<StateImpl>(mod, func);
+        return std::make_unique<StateImpl>(mod, func, config);
     });
 
     return Success;
diff --git a/src/tint/lang/hlsl/writer/raise/shader_io.h b/src/tint/lang/hlsl/writer/raise/shader_io.h
index 8bc3b86..af0e3e5 100644
--- a/src/tint/lang/hlsl/writer/raise/shader_io.h
+++ b/src/tint/lang/hlsl/writer/raise/shader_io.h
@@ -28,6 +28,9 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_SHADER_IO_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_SHADER_IO_H_
 
+#include <optional>
+
+#include "src/tint/api/common/binding_point.h"
 #include "src/tint/utils/result/result.h"
 
 // Forward declarations.
@@ -37,10 +40,17 @@
 
 namespace tint::hlsl::writer::raise {
 
+struct ShaderIOConfig {
+    /// The binding point to use for the num_workgroups generated uniform buffer. If it contains
+    /// no value, a free binding point will be used. Specifically, binding 0 of the largest used
+    /// group plus 1 is used if at least one resource is bound, otherwise group 0 binding 0 is used.
+    std::optional<BindingPoint> num_workgroups_binding;
+};
+
 /// ShaderIO is a transform that prepares entry point inputs and outputs for HLSL codegen.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> ShaderIO(core::ir::Module& module);
+Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/shader_io_test.cc b/src/tint/lang/hlsl/writer/raise/shader_io_test.cc
index bd0d91d..f7cedb5 100644
--- a/src/tint/lang/hlsl/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/shader_io_test.cc
@@ -55,7 +55,7 @@
 
     auto* expect = src;
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -140,7 +140,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -294,7 +294,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -419,7 +419,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -461,7 +461,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -502,7 +502,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -607,7 +607,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -687,7 +687,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -835,7 +835,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -929,7 +929,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -1016,7 +1016,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -1064,7 +1064,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -1116,7 +1116,7 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
@@ -1209,7 +1209,278 @@
 }
 )";
 
-    Run(ShaderIO);
+    Run(ShaderIO, ShaderIOConfig{});
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_NonStruct) {
+    auto* num_workgroups = b.FunctionParam("num_wgs", ty.vec3<u32>());
+    num_workgroups->SetBuiltin(core::BuiltinValue::kNumWorkgroups);
+
+    auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute);
+    ep->SetParams({num_workgroups});
+    ep->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(ep->Block(), [&] {
+        auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups);
+        b.Return(ep, r);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1, 1, 1) func(%num_wgs:vec3<u32> [@num_workgroups]):vec3<u32> {
+  $B1: {
+    %3:vec3<u32> = mul %num_wgs, %num_wgs
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+foo_outputs = struct @align(16) {
+  tint_symbol:vec3<u32> @offset(0)
+}
+
+$B1: {  # root
+  %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(0, 0)
+}
+
+%foo_inner = func(%num_wgs:vec3<u32>):vec3<u32> {
+  $B2: {
+    %4:vec3<u32> = mul %num_wgs, %num_wgs
+    ret %4
+  }
+}
+%foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs {
+  $B3: {
+    %6:vec3<u32> = load %tint_num_workgroups
+    %7:vec3<u32> = call %foo_inner, %6
+    %8:foo_outputs = construct %7
+    ret %8
+  }
+}
+)";
+
+    Run(ShaderIO, ShaderIOConfig{});
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_Struct) {
+    auto* str_ty = ty.Struct(mod.symbols.New("Inputs"),
+                             {
+                                 {
+                                     mod.symbols.New("num_wgs"),
+                                     ty.vec3<u32>(),
+                                     core::IOAttributes{
+                                         /* location */ std::nullopt,
+                                         /* blend_src */ std::nullopt,
+                                         /* color */ std::nullopt,
+                                         /* builtin */ core::BuiltinValue::kNumWorkgroups,
+                                         /* interpolation */ std::nullopt,
+                                         /* invariant */ false,
+                                     },
+                                 },
+                             });
+
+    auto* str_param = b.FunctionParam("inputs", str_ty);
+
+    auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute);
+    ep->SetParams({str_param});
+    ep->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(ep->Block(), [&] {
+        auto* num_workgroups = b.Access(ty.vec3<u32>(), str_param, 0_i);
+        auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups);
+        b.Return(ep, r);
+    });
+
+    auto* src = R"(
+Inputs = struct @align(16) {
+  num_wgs:vec3<u32> @offset(0), @builtin(num_workgroups)
+}
+
+%foo = @compute @workgroup_size(1, 1, 1) func(%inputs:Inputs):vec3<u32> {
+  $B1: {
+    %3:vec3<u32> = access %inputs, 0i
+    %4:vec3<u32> = mul %3, %3
+    ret %4
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+Inputs = struct @align(16) {
+  num_wgs:vec3<u32> @offset(0)
+}
+
+foo_outputs = struct @align(16) {
+  tint_symbol:vec3<u32> @offset(0)
+}
+
+$B1: {  # root
+  %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(0, 0)
+}
+
+%foo_inner = func(%inputs:Inputs):vec3<u32> {
+  $B2: {
+    %4:vec3<u32> = access %inputs, 0i
+    %5:vec3<u32> = mul %4, %4
+    ret %5
+  }
+}
+%foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs {
+  $B3: {
+    %7:vec3<u32> = load %tint_num_workgroups
+    %8:Inputs = construct %7
+    %9:vec3<u32> = call %foo_inner, %8
+    %10:foo_outputs = construct %9
+    ret %10
+  }
+}
+)";
+
+    Run(ShaderIO, ShaderIOConfig{});
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_ExplicitBinding) {
+    auto* num_workgroups = b.FunctionParam("num_wgs", ty.vec3<u32>());
+    num_workgroups->SetBuiltin(core::BuiltinValue::kNumWorkgroups);
+
+    auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute);
+    ep->SetParams({num_workgroups});
+    ep->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(ep->Block(), [&] {
+        auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups);
+        b.Return(ep, r);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1, 1, 1) func(%num_wgs:vec3<u32> [@num_workgroups]):vec3<u32> {
+  $B1: {
+    %3:vec3<u32> = mul %num_wgs, %num_wgs
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+foo_outputs = struct @align(16) {
+  tint_symbol:vec3<u32> @offset(0)
+}
+
+$B1: {  # root
+  %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(1, 23)
+}
+
+%foo_inner = func(%num_wgs:vec3<u32>):vec3<u32> {
+  $B2: {
+    %4:vec3<u32> = mul %num_wgs, %num_wgs
+    ret %4
+  }
+}
+%foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs {
+  $B3: {
+    %6:vec3<u32> = load %tint_num_workgroups
+    %7:vec3<u32> = call %foo_inner, %6
+    %8:foo_outputs = construct %7
+    ret %8
+  }
+}
+)";
+
+    ShaderIOConfig config;
+    config.num_workgroups_binding = {1u, 23u};
+    Run(ShaderIO, config);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterTransformTest, ShaderIOParameters_NumWorkgroups_AutoBinding) {
+    auto* num_workgroups = b.FunctionParam("num_wgs", ty.vec3<u32>());
+    num_workgroups->SetBuiltin(core::BuiltinValue::kNumWorkgroups);
+
+    auto* ep = b.Function("foo", ty.vec3<u32>(), core::ir::Function::PipelineStage::kCompute);
+    ep->SetParams({num_workgroups});
+    ep->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(ep->Block(), [&] {
+        auto* r = b.Multiply(ty.vec3<u32>(), num_workgroups, num_workgroups);
+        b.Return(ep, r);
+    });
+
+    b.Append(mod.root_block, [&] {
+        for (uint32_t group = 0; group < 10; ++group) {
+            auto* v = b.Var<core::AddressSpace::kStorage, i32>();
+            v->SetBindingPoint(group, group + 1u);
+        }
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %1:ptr<storage, i32, read_write> = var @binding_point(0, 1)
+  %2:ptr<storage, i32, read_write> = var @binding_point(1, 2)
+  %3:ptr<storage, i32, read_write> = var @binding_point(2, 3)
+  %4:ptr<storage, i32, read_write> = var @binding_point(3, 4)
+  %5:ptr<storage, i32, read_write> = var @binding_point(4, 5)
+  %6:ptr<storage, i32, read_write> = var @binding_point(5, 6)
+  %7:ptr<storage, i32, read_write> = var @binding_point(6, 7)
+  %8:ptr<storage, i32, read_write> = var @binding_point(7, 8)
+  %9:ptr<storage, i32, read_write> = var @binding_point(8, 9)
+  %10:ptr<storage, i32, read_write> = var @binding_point(9, 10)
+}
+
+%foo = @compute @workgroup_size(1, 1, 1) func(%num_wgs:vec3<u32> [@num_workgroups]):vec3<u32> {
+  $B2: {
+    %13:vec3<u32> = mul %num_wgs, %num_wgs
+    ret %13
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+foo_outputs = struct @align(16) {
+  tint_symbol:vec3<u32> @offset(0)
+}
+
+$B1: {  # root
+  %1:ptr<storage, i32, read_write> = var @binding_point(0, 1)
+  %2:ptr<storage, i32, read_write> = var @binding_point(1, 2)
+  %3:ptr<storage, i32, read_write> = var @binding_point(2, 3)
+  %4:ptr<storage, i32, read_write> = var @binding_point(3, 4)
+  %5:ptr<storage, i32, read_write> = var @binding_point(4, 5)
+  %6:ptr<storage, i32, read_write> = var @binding_point(5, 6)
+  %7:ptr<storage, i32, read_write> = var @binding_point(6, 7)
+  %8:ptr<storage, i32, read_write> = var @binding_point(7, 8)
+  %9:ptr<storage, i32, read_write> = var @binding_point(8, 9)
+  %10:ptr<storage, i32, read_write> = var @binding_point(9, 10)
+  %tint_num_workgroups:ptr<uniform, vec3<u32>, read> = var @binding_point(10, 0)
+}
+
+%foo_inner = func(%num_wgs:vec3<u32>):vec3<u32> {
+  $B2: {
+    %14:vec3<u32> = mul %num_wgs, %num_wgs
+    ret %14
+  }
+}
+%foo = @compute @workgroup_size(1, 1, 1) func():foo_outputs {
+  $B3: {
+    %16:vec3<u32> = load %tint_num_workgroups
+    %17:vec3<u32> = call %foo_inner, %16
+    %18:foo_outputs = construct %17
+    ret %18
+  }
+}
+)";
+
+    Run(ShaderIO, ShaderIOConfig{});
 
     EXPECT_EQ(expect, str());
 }
diff --git a/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.dxc.hlsl b/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.dxc.hlsl
index 7568472..127819c 100644
--- a/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.dxc.hlsl
+++ b/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.dxc.hlsl
@@ -1,11 +1,20 @@
-SKIP: FAILED
+struct main_inputs {
+  uint3 local_invocation_id : SV_GroupThreadID;
+  uint local_invocation_index : SV_GroupIndex;
+  uint3 global_invocation_id : SV_DispatchThreadID;
+  uint3 workgroup_id : SV_GroupID;
+};
 
-..\..\src\tint\lang\hlsl\writer\printer\printer.cc:1505 internal compiler error: TINT_ASSERT(!name.empty())
-********************************************************************
-*  The tint shader compiler has encountered an unexpected error.   *
-*                                                                  *
-*  Please help us fix this issue by submitting a bug report at     *
-*  crbug.com/tint with the source program that triggered the bug.  *
-********************************************************************
 
-tint executable returned error: exit status 0xc000001d
+cbuffer cbuffer_tint_num_workgroups : register(b0) {
+  uint4 tint_num_workgroups[1];
+};
+void main_inner(uint3 local_invocation_id, uint local_invocation_index, uint3 global_invocation_id, uint3 workgroup_id, uint3 num_workgroups) {
+  uint foo = ((((local_invocation_id[0u] + local_invocation_index) + global_invocation_id[0u]) + workgroup_id[0u]) + num_workgroups[0u]);
+}
+
+[numthreads(1, 1, 1)]
+void main(main_inputs inputs) {
+  main_inner(inputs.local_invocation_id, inputs.local_invocation_index, inputs.global_invocation_id, inputs.workgroup_id, tint_num_workgroups[0u].xyz);
+}
+
diff --git a/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.fxc.hlsl b/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.fxc.hlsl
index 7568472..127819c 100644
--- a/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/types/functions/shader_io/compute_input_builtins.wgsl.expected.ir.fxc.hlsl
@@ -1,11 +1,20 @@
-SKIP: FAILED
+struct main_inputs {
+  uint3 local_invocation_id : SV_GroupThreadID;
+  uint local_invocation_index : SV_GroupIndex;
+  uint3 global_invocation_id : SV_DispatchThreadID;
+  uint3 workgroup_id : SV_GroupID;
+};
 
-..\..\src\tint\lang\hlsl\writer\printer\printer.cc:1505 internal compiler error: TINT_ASSERT(!name.empty())
-********************************************************************
-*  The tint shader compiler has encountered an unexpected error.   *
-*                                                                  *
-*  Please help us fix this issue by submitting a bug report at     *
-*  crbug.com/tint with the source program that triggered the bug.  *
-********************************************************************
 
-tint executable returned error: exit status 0xc000001d
+cbuffer cbuffer_tint_num_workgroups : register(b0) {
+  uint4 tint_num_workgroups[1];
+};
+void main_inner(uint3 local_invocation_id, uint local_invocation_index, uint3 global_invocation_id, uint3 workgroup_id, uint3 num_workgroups) {
+  uint foo = ((((local_invocation_id[0u] + local_invocation_index) + global_invocation_id[0u]) + workgroup_id[0u]) + num_workgroups[0u]);
+}
+
+[numthreads(1, 1, 1)]
+void main(main_inputs inputs) {
+  main_inner(inputs.local_invocation_id, inputs.local_invocation_index, inputs.global_invocation_id, inputs.workgroup_id, tint_num_workgroups[0u].xyz);
+}
+
diff --git a/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.dxc.hlsl b/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.dxc.hlsl
index 7568472..8c73304 100644
--- a/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.dxc.hlsl
+++ b/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.dxc.hlsl
@@ -1,11 +1,29 @@
-SKIP: FAILED
+struct ComputeInputs {
+  uint3 local_invocation_id;
+  uint local_invocation_index;
+  uint3 global_invocation_id;
+  uint3 workgroup_id;
+  uint3 num_workgroups;
+};
 
-..\..\src\tint\lang\hlsl\writer\printer\printer.cc:1505 internal compiler error: TINT_ASSERT(!name.empty())
-********************************************************************
-*  The tint shader compiler has encountered an unexpected error.   *
-*                                                                  *
-*  Please help us fix this issue by submitting a bug report at     *
-*  crbug.com/tint with the source program that triggered the bug.  *
-********************************************************************
+struct main_inputs {
+  uint3 ComputeInputs_local_invocation_id : SV_GroupThreadID;
+  uint ComputeInputs_local_invocation_index : SV_GroupIndex;
+  uint3 ComputeInputs_global_invocation_id : SV_DispatchThreadID;
+  uint3 ComputeInputs_workgroup_id : SV_GroupID;
+};
 
-tint executable returned error: exit status 0xc000001d
+
+cbuffer cbuffer_tint_num_workgroups : register(b0) {
+  uint4 tint_num_workgroups[1];
+};
+void main_inner(ComputeInputs inputs) {
+  uint foo = ((((inputs.local_invocation_id[0u] + inputs.local_invocation_index) + inputs.global_invocation_id[0u]) + inputs.workgroup_id[0u]) + inputs.num_workgroups[0u]);
+}
+
+[numthreads(1, 1, 1)]
+void main(main_inputs inputs) {
+  ComputeInputs v = {inputs.ComputeInputs_local_invocation_id, inputs.ComputeInputs_local_invocation_index, inputs.ComputeInputs_global_invocation_id, inputs.ComputeInputs_workgroup_id, tint_num_workgroups[0u].xyz};
+  main_inner(v);
+}
+
diff --git a/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.fxc.hlsl b/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.fxc.hlsl
index 7568472..8c73304 100644
--- a/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/types/functions/shader_io/compute_input_builtins_struct.wgsl.expected.ir.fxc.hlsl
@@ -1,11 +1,29 @@
-SKIP: FAILED
+struct ComputeInputs {
+  uint3 local_invocation_id;
+  uint local_invocation_index;
+  uint3 global_invocation_id;
+  uint3 workgroup_id;
+  uint3 num_workgroups;
+};
 
-..\..\src\tint\lang\hlsl\writer\printer\printer.cc:1505 internal compiler error: TINT_ASSERT(!name.empty())
-********************************************************************
-*  The tint shader compiler has encountered an unexpected error.   *
-*                                                                  *
-*  Please help us fix this issue by submitting a bug report at     *
-*  crbug.com/tint with the source program that triggered the bug.  *
-********************************************************************
+struct main_inputs {
+  uint3 ComputeInputs_local_invocation_id : SV_GroupThreadID;
+  uint ComputeInputs_local_invocation_index : SV_GroupIndex;
+  uint3 ComputeInputs_global_invocation_id : SV_DispatchThreadID;
+  uint3 ComputeInputs_workgroup_id : SV_GroupID;
+};
 
-tint executable returned error: exit status 0xc000001d
+
+cbuffer cbuffer_tint_num_workgroups : register(b0) {
+  uint4 tint_num_workgroups[1];
+};
+void main_inner(ComputeInputs inputs) {
+  uint foo = ((((inputs.local_invocation_id[0u] + inputs.local_invocation_index) + inputs.global_invocation_id[0u]) + inputs.workgroup_id[0u]) + inputs.num_workgroups[0u]);
+}
+
+[numthreads(1, 1, 1)]
+void main(main_inputs inputs) {
+  ComputeInputs v = {inputs.ComputeInputs_local_invocation_id, inputs.ComputeInputs_local_invocation_index, inputs.ComputeInputs_global_invocation_id, inputs.ComputeInputs_workgroup_id, tint_num_workgroups[0u].xyz};
+  main_inner(v);
+}
+