Support "immediate" address space in HLSL IR backend
This CL adds support for "immediate" address space in the HLSL IR
backend.
It emits "immediate" address space in global variables with cbuffer
and a options to provide binding point info for it.
Bug: 366291600
Change-Id: I139f72077cc25c30733a42352ce29d9b0096fd5b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/237895
Commit-Queue: Shaobo Yan <shaoboyan@microsoft.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/hlsl/writer/common/options.h b/src/tint/lang/hlsl/writer/common/options.h
index d49e4dd..3e33ec8 100644
--- a/src/tint/lang/hlsl/writer/common/options.h
+++ b/src/tint/lang/hlsl/writer/common/options.h
@@ -242,6 +242,9 @@
/// The binding point to use for information passed via root constants.
std::optional<BindingPoint> root_constant_binding_point;
+ /// Immediate binding point info
+ std::optional<BindingPoint> immediate_binding_point;
+
/// The bindings
Bindings bindings;
@@ -263,6 +266,7 @@
array_length_from_uniform,
interstage_locations,
root_constant_binding_point,
+ immediate_binding_point,
bindings,
pixel_local);
};
diff --git a/src/tint/lang/hlsl/writer/printer/printer.cc b/src/tint/lang/hlsl/writer/printer/printer.cc
index 2fb4814..81cd4f7 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.cc
+++ b/src/tint/lang/hlsl/writer/printer/printer.cc
@@ -27,6 +27,7 @@
#include "src/tint/lang/hlsl/writer/printer/printer.h"
+#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
@@ -579,6 +580,8 @@
break;
}
+ // All immediate address space instructions should have been converted to
+ // uniform address space by the ChangeImmediateToUniform transform.
case core::AddressSpace::kImmediate:
default: {
TINT_ICE() << "unhandled address space " << space;
@@ -589,9 +592,6 @@
}
void EmitUniformVariable(const core::ir::Var* var) {
- auto* ptr = var->Result()->Type()->As<core::type::Pointer>();
- TINT_ASSERT(ptr);
-
auto bp = var->BindingPoint();
TINT_ASSERT(bp.has_value());
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.bazel b/src/tint/lang/hlsl/writer/raise/BUILD.bazel
index 68e98ba..f74f3b8 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.bazel
@@ -41,6 +41,7 @@
srcs = [
"binary_polyfill.cc",
"builtin_polyfill.cc",
+ "change_immediate_to_uniform.cc",
"decompose_storage_access.cc",
"decompose_uniform_access.cc",
"localize_struct_array_assignment.cc",
@@ -54,6 +55,7 @@
hdrs = [
"binary_polyfill.h",
"builtin_polyfill.h",
+ "change_immediate_to_uniform.h",
"decompose_storage_access.h",
"decompose_uniform_access.h",
"localize_struct_array_assignment.h",
@@ -98,6 +100,7 @@
srcs = [
"binary_polyfill_test.cc",
"builtin_polyfill_test.cc",
+ "change_immediate_to_uniform_test.cc",
"decompose_storage_access_test.cc",
"decompose_uniform_access_test.cc",
"localize_struct_array_assignment_test.cc",
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.cmake b/src/tint/lang/hlsl/writer/raise/BUILD.cmake
index 94703ba..9a6552c 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.cmake
@@ -43,6 +43,8 @@
lang/hlsl/writer/raise/binary_polyfill.h
lang/hlsl/writer/raise/builtin_polyfill.cc
lang/hlsl/writer/raise/builtin_polyfill.h
+ lang/hlsl/writer/raise/change_immediate_to_uniform.cc
+ lang/hlsl/writer/raise/change_immediate_to_uniform.h
lang/hlsl/writer/raise/decompose_storage_access.cc
lang/hlsl/writer/raise/decompose_storage_access.h
lang/hlsl/writer/raise/decompose_uniform_access.cc
@@ -99,6 +101,7 @@
tint_add_target(tint_lang_hlsl_writer_raise_test test
lang/hlsl/writer/raise/binary_polyfill_test.cc
lang/hlsl/writer/raise/builtin_polyfill_test.cc
+ lang/hlsl/writer/raise/change_immediate_to_uniform_test.cc
lang/hlsl/writer/raise/decompose_storage_access_test.cc
lang/hlsl/writer/raise/decompose_uniform_access_test.cc
lang/hlsl/writer/raise/localize_struct_array_assignment_test.cc
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.gn b/src/tint/lang/hlsl/writer/raise/BUILD.gn
index dee99dd..edfd835 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.gn
@@ -49,6 +49,8 @@
"binary_polyfill.h",
"builtin_polyfill.cc",
"builtin_polyfill.h",
+ "change_immediate_to_uniform.cc",
+ "change_immediate_to_uniform.h",
"decompose_storage_access.cc",
"decompose_storage_access.h",
"decompose_uniform_access.cc",
@@ -99,6 +101,7 @@
sources = [
"binary_polyfill_test.cc",
"builtin_polyfill_test.cc",
+ "change_immediate_to_uniform_test.cc",
"decompose_storage_access_test.cc",
"decompose_uniform_access_test.cc",
"localize_struct_array_assignment_test.cc",
diff --git a/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform.cc b/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform.cc
new file mode 100644
index 0000000..335d5ef
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform.cc
@@ -0,0 +1,148 @@
+// 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/hlsl/writer/raise/change_immediate_to_uniform.h"
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::hlsl::writer::raise {
+namespace {
+
+/// PIMPL state for the transform.
+///
+struct State {
+ /// The transform config.
+ const ChangeImmediateToUniformConfig& config;
+ /// The IR module.
+ core::ir::Module& ir;
+ /// The IR builder.
+ core::ir::Builder b{ir};
+
+ /// The type manager.
+ core::type::Manager& ty{ir.Types()};
+
+ /// Process the module.
+ void Process() {
+ core::ir::Var* immediate = nullptr;
+
+ // candidate group number if immediate_binding_point is not available.
+ uint32_t candidate_group = 0;
+
+ for (auto* inst : *ir.root_block) {
+ // Allow this to run before or after PromoteInitializers by handling non-var root_block
+ // entries
+ auto* var = inst->As<core::ir::Var>();
+ if (!var) {
+ continue;
+ }
+
+ // use the largest used group plus 1, or group 0 if no
+ // resources are bound as candidate group number.
+ if (const auto& bp_visited = var->BindingPoint()) {
+ candidate_group = std::max(candidate_group, bp_visited->group + 1);
+ }
+
+ auto* var_ty = var->Result()->Type()->As<core::type::Pointer>();
+
+ // DecomposeStorageAccess may have converted the var pointers into ByteAddressBuffer
+ // objects. Since they've been changed, then they're Storage buffers and we don't care
+ // about them here.
+ if (!var_ty) {
+ continue;
+ }
+
+ // Only care about immediate space variables.
+ if (var_ty->AddressSpace() != core::AddressSpace::kImmediate) {
+ continue;
+ }
+
+ if (immediate) {
+ TINT_ICE() << "multiple immediate variables";
+ }
+
+ immediate = var;
+ }
+
+ if (!immediate) {
+ return;
+ }
+
+ BindingPoint bp = {};
+ if (config.immediate_binding_point.has_value()) {
+ bp = *config.immediate_binding_point;
+ } else {
+ // Otherwise, use the binding 0 of candidate group.
+ bp = {candidate_group, 0};
+ }
+ immediate->SetBindingPoint(bp.group, bp.binding);
+
+ ReplaceImmediateAddressSpace(immediate);
+ }
+
+ /// Replace an output pointer address space to make it `uniform`.
+ /// @param value the output variable
+ void ReplaceImmediateAddressSpace(core::ir::Instruction* value) {
+ Vector<core::ir::Instruction*, 8> to_replace;
+ to_replace.Push(value);
+
+ // Update all uses of the module-scope variable.
+ while (!to_replace.IsEmpty()) {
+ auto* inst = to_replace.Pop();
+ auto* new_ptr_type =
+ ty.ptr(core::AddressSpace::kUniform, inst->Result()->Type()->UnwrapPtr());
+ inst->Result()->SetType(new_ptr_type);
+
+ for (auto usage : inst->Result()->UsagesUnsorted()) {
+ if (!usage->instruction->Is<core::ir::Let>() &&
+ !usage->instruction->Is<core::ir::Access>()) {
+ continue;
+ }
+ to_replace.Push(usage->instruction);
+ }
+ }
+ }
+};
+
+} // namespace
+
+Result<SuccessType> ChangeImmediateToUniform(core::ir::Module& ir,
+ const ChangeImmediateToUniformConfig& config) {
+ auto result = ValidateAndDumpIfNeeded(
+ ir, "hlsl.ChangeImmediateToUniform",
+ core::ir::Capabilities{core::ir::Capability::kAllowClipDistancesOnF32});
+ if (result != Success) {
+ return result.Failure();
+ }
+
+ State{config, ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform.h b/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform.h
new file mode 100644
index 0000000..bee0909
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform.h
@@ -0,0 +1,62 @@
+// 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_HLSL_WRITER_RAISE_CHANGE_IMMEDIATE_TO_UNIFORM_H_
+#define SRC_TINT_LANG_HLSL_WRITER_RAISE_CHANGE_IMMEDIATE_TO_UNIFORM_H_
+
+#include <algorithm>
+#include <optional>
+
+#include "src/tint/api/common/binding_point.h"
+#include "src/tint/utils/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+} // namespace tint::core::ir
+
+namespace tint::hlsl::writer::raise {
+
+struct ChangeImmediateToUniformConfig {
+ /// The binding point info.
+ std::optional<BindingPoint> immediate_binding_point;
+};
+
+/// ChangeImmediateToUniform is a transform used to replace immediate global var accesses to
+/// uniform address space. Doing this transform to leverage DecomposeUniformAccess to generate
+/// correct loading of the HLSL primitive objects. This transform should apply after all immediate
+/// related transforms and before DecomposeUniformAccess transform.
+///
+///
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> ChangeImmediateToUniform(core::ir::Module& module,
+ const ChangeImmediateToUniformConfig& config);
+
+} // namespace tint::hlsl::writer::raise
+
+#endif // SRC_TINT_LANG_HLSL_WRITER_RAISE_CHANGE_IMMEDIATE_TO_UNIFORM_H_
diff --git a/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform_test.cc b/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform_test.cc
new file mode 100644
index 0000000..b3ff8aa
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform_test.cc
@@ -0,0 +1,661 @@
+// 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/hlsl/writer/raise/change_immediate_to_uniform.h"
+
+#include <gtest/gtest.h>
+
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+#include "src/tint/lang/core/number.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::hlsl::writer::raise {
+namespace {
+
+using HlslWriterChangeImmediateToUniformTest = core::ir::transform::TransformTest;
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, NoPushConstantVariable) {
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] { b.Return(func); });
+
+ auto* src = R"(
+%foo = @fragment func():void {
+ $B1: {
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, PushConstantAccessChain) {
+ auto* Inner = ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.New("c"), ty.f32()},
+ {mod.symbols.New("d"), ty.u32()},
+ });
+
+ tint::Vector<const core::type::StructMember*, 2> members;
+ members.Push(ty.Get<core::type::StructMember>(mod.symbols.New("a"), ty.i32(), 0u, 0u, 4u,
+ ty.i32()->Size(), core::IOAttributes{}));
+ members.Push(ty.Get<core::type::StructMember>(mod.symbols.New("b"), Inner, 1u, 16u, 16u,
+ Inner->Size(), core::IOAttributes{}));
+ auto* sb = ty.Struct(mod.symbols.New("SB"), members);
+
+ auto* var = b.Var("v", immediate, sb, core::Access::kRead);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ auto* x = b.Access(ty.ptr(immediate, Inner, core::Access::kRead), var, 1_u);
+ b.Let("b",
+ b.Load(b.Access(ty.ptr(immediate, ty.u32(), core::Access::kRead), x->Result(), 1_u)));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+Inner = struct @align(4) {
+ c:f32 @offset(0)
+ d:u32 @offset(4)
+}
+
+SB = struct @align(16) {
+ a:i32 @offset(0)
+ b:Inner @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<immediate, SB, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<immediate, Inner, read> = access %v, 1u
+ %4:ptr<immediate, u32, read> = access %3, 1u
+ %5:u32 = load %4
+ %b:u32 = let %5
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(4) {
+ c:f32 @offset(0)
+ d:u32 @offset(4)
+}
+
+SB = struct @align(16) {
+ a:i32 @offset(0)
+ b:Inner @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<uniform, SB, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<uniform, Inner, read> = access %v, 1u
+ %4:ptr<uniform, u32, read> = access %3, 1u
+ %5:u32 = load %4
+ %b:u32 = let %5
+ ret
+ }
+}
+)";
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, PushConstantAccessChainFromLetAccessChain) {
+ auto* Inner = ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.New("c"), ty.f32()},
+ });
+
+ tint::Vector<const core::type::StructMember*, 2> members;
+ members.Push(ty.Get<core::type::StructMember>(mod.symbols.New("a"), ty.i32(), 0u, 0u, 4u,
+ ty.i32()->Size(), core::IOAttributes{}));
+ members.Push(ty.Get<core::type::StructMember>(mod.symbols.New("b"), Inner, 1u, 16u, 16u,
+ Inner->Size(), core::IOAttributes{}));
+ auto* sb = ty.Struct(mod.symbols.New("SB"), members);
+
+ auto* var = b.Var("v", immediate, sb, core::Access::kRead);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ auto* x = b.Let("x", var);
+ auto* y =
+ b.Let("y", b.Access(ty.ptr(immediate, Inner, core::Access::kRead), x->Result(), 1_u));
+ auto* z = b.Let(
+ "z", b.Access(ty.ptr(immediate, ty.f32(), core::Access::kRead), y->Result(), 0_u));
+ b.Let("a", b.Load(z));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+Inner = struct @align(4) {
+ c:f32 @offset(0)
+}
+
+SB = struct @align(16) {
+ a:i32 @offset(0)
+ b:Inner @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<immediate, SB, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %x:ptr<immediate, SB, read> = let %v
+ %4:ptr<immediate, Inner, read> = access %x, 1u
+ %y:ptr<immediate, Inner, read> = let %4
+ %6:ptr<immediate, f32, read> = access %y, 0u
+ %z:ptr<immediate, f32, read> = let %6
+ %8:f32 = load %z
+ %a:f32 = let %8
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(4) {
+ c:f32 @offset(0)
+}
+
+SB = struct @align(16) {
+ a:i32 @offset(0)
+ b:Inner @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<uniform, SB, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %x:ptr<uniform, SB, read> = let %v
+ %4:ptr<uniform, Inner, read> = access %x, 1u
+ %y:ptr<uniform, Inner, read> = let %4
+ %6:ptr<uniform, f32, read> = access %y, 0u
+ %z:ptr<uniform, f32, read> = let %6
+ %8:f32 = load %z
+ %a:f32 = let %8
+ ret
+ }
+}
+)";
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, PushConstantAccessVectorLoad) {
+ auto* var = b.Var<immediate, vec4<f32>, core::Access::kRead>("v");
+
+ b.ir.root_block->Append(var);
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("a", b.Load(var));
+ b.Let("b", b.LoadVectorElement(var, 0_u));
+ b.Let("c", b.LoadVectorElement(var, 1_u));
+ b.Let("d", b.LoadVectorElement(var, 2_u));
+ b.Let("e", b.LoadVectorElement(var, 3_u));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %v:ptr<immediate, vec4<f32>, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:vec4<f32> = load %v
+ %a:vec4<f32> = let %3
+ %5:f32 = load_vector_element %v, 0u
+ %b:f32 = let %5
+ %7:f32 = load_vector_element %v, 1u
+ %c:f32 = let %7
+ %9:f32 = load_vector_element %v, 2u
+ %d:f32 = let %9
+ %11:f32 = load_vector_element %v, 3u
+ %e:f32 = let %11
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %v:ptr<uniform, vec4<f32>, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:vec4<f32> = load %v
+ %a:vec4<f32> = let %3
+ %5:f32 = load_vector_element %v, 0u
+ %b:f32 = let %5
+ %7:f32 = load_vector_element %v, 1u
+ %c:f32 = let %7
+ %9:f32 = load_vector_element %v, 2u
+ %d:f32 = let %9
+ %11:f32 = load_vector_element %v, 3u
+ %e:f32 = let %11
+ ret
+ }
+}
+)";
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, PushConstantAccessMatrix) {
+ auto* var = b.Var<immediate, mat4x4<f32>, core::Access::kRead>("v");
+
+ b.ir.root_block->Append(var);
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("a", b.Load(var));
+ b.Let("b", b.Load(b.Access(ty.ptr<immediate, vec4<f32>, core::Access::kRead>(), var, 3_u)));
+ b.Let("c",
+ b.LoadVectorElement(
+ b.Access(ty.ptr<immediate, vec4<f32>, core::Access::kRead>(), var, 1_u), 2_u));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %v:ptr<immediate, mat4x4<f32>, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:mat4x4<f32> = load %v
+ %a:mat4x4<f32> = let %3
+ %5:ptr<immediate, vec4<f32>, read> = access %v, 3u
+ %6:vec4<f32> = load %5
+ %b:vec4<f32> = let %6
+ %8:ptr<immediate, vec4<f32>, read> = access %v, 1u
+ %9:f32 = load_vector_element %8, 2u
+ %c:f32 = let %9
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %v:ptr<uniform, mat4x4<f32>, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:mat4x4<f32> = load %v
+ %a:mat4x4<f32> = let %3
+ %5:ptr<uniform, vec4<f32>, read> = access %v, 3u
+ %6:vec4<f32> = load %5
+ %b:vec4<f32> = let %6
+ %8:ptr<uniform, vec4<f32>, read> = access %v, 1u
+ %9:f32 = load_vector_element %8, 2u
+ %c:f32 = let %9
+ ret
+ }
+}
+)";
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, PushConstantAccessStruct) {
+ auto* SB = ty.Struct(mod.symbols.New("SB"), {
+ {mod.symbols.New("a"), ty.i32()},
+ {mod.symbols.New("b"), ty.f32()},
+ });
+
+ auto* var = b.Var("v", immediate, SB, core::Access::kRead);
+
+ b.ir.root_block->Append(var);
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("a", b.Load(var));
+ b.Let("b", b.Load(b.Access(ty.ptr<immediate, f32, core::Access::kRead>(), var, 1_u)));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+SB = struct @align(4) {
+ a:i32 @offset(0)
+ b:f32 @offset(4)
+}
+
+$B1: { # root
+ %v:ptr<immediate, SB, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:SB = load %v
+ %a:SB = let %3
+ %5:ptr<immediate, f32, read> = access %v, 1u
+ %6:f32 = load %5
+ %b:f32 = let %6
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+SB = struct @align(4) {
+ a:i32 @offset(0)
+ b:f32 @offset(4)
+}
+
+$B1: { # root
+ %v:ptr<uniform, SB, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:SB = load %v
+ %a:SB = let %3
+ %5:ptr<uniform, f32, read> = access %v, 1u
+ %6:f32 = load %5
+ %b:f32 = let %6
+ ret
+ }
+}
+)";
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, PushConstantStructNested) {
+ auto* Inner =
+ ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.New("s"), ty.mat3x3<f32>()},
+ {mod.symbols.New("t"), ty.array<vec3<f32>, 5>()},
+ });
+ auto* Outer = ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.New("x"), ty.f32()},
+ {mod.symbols.New("y"), Inner},
+ });
+
+ auto* SB = ty.Struct(mod.symbols.New("SB"), {
+ {mod.symbols.New("a"), ty.i32()},
+ {mod.symbols.New("b"), Outer},
+ });
+
+ auto* var = b.Var("v", immediate, SB, core::Access::kRead);
+
+ b.ir.root_block->Append(var);
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("a", b.Load(var));
+ b.Let("b", b.LoadVectorElement(b.Access(ty.ptr<immediate, vec3<f32>, core::Access::kRead>(),
+ var, 1_u, 1_u, 1_u, 3_u),
+ 2_u));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+Inner = struct @align(16) {
+ s:mat3x3<f32> @offset(0)
+ t:array<vec3<f32>, 5> @offset(48)
+}
+
+Outer = struct @align(16) {
+ x:f32 @offset(0)
+ y:Inner @offset(16)
+}
+
+SB = struct @align(16) {
+ a:i32 @offset(0)
+ b:Outer @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<immediate, SB, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:SB = load %v
+ %a:SB = let %3
+ %5:ptr<immediate, vec3<f32>, read> = access %v, 1u, 1u, 1u, 3u
+ %6:f32 = load_vector_element %5, 2u
+ %b:f32 = let %6
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(16) {
+ s:mat3x3<f32> @offset(0)
+ t:array<vec3<f32>, 5> @offset(48)
+}
+
+Outer = struct @align(16) {
+ x:f32 @offset(0)
+ y:Inner @offset(16)
+}
+
+SB = struct @align(16) {
+ a:i32 @offset(0)
+ b:Outer @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<uniform, SB, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:SB = load %v
+ %a:SB = let %3
+ %5:ptr<uniform, vec3<f32>, read> = access %v, 1u, 1u, 1u, 3u
+ %6:f32 = load_vector_element %5, 2u
+ %b:f32 = let %6
+ ret
+ }
+}
+)";
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, PushConstantAccessChainReused) {
+ auto* sb = ty.Struct(mod.symbols.New("SB"), {
+ {mod.symbols.New("c"), ty.f32()},
+ {mod.symbols.New("d"), ty.vec3<f32>()},
+ });
+
+ auto* var = b.Var("v", immediate, sb, core::Access::kRead);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ auto* x = b.Access(ty.ptr(immediate, ty.vec3<f32>(), core::Access::kRead), var, 1_u);
+ b.Let("b", b.LoadVectorElement(x, 1_u));
+ b.Let("c", b.LoadVectorElement(x, 2_u));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+SB = struct @align(16) {
+ c:f32 @offset(0)
+ d:vec3<f32> @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<immediate, SB, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<immediate, vec3<f32>, read> = access %v, 1u
+ %4:f32 = load_vector_element %3, 1u
+ %b:f32 = let %4
+ %6:f32 = load_vector_element %3, 2u
+ %c:f32 = let %6
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+SB = struct @align(16) {
+ c:f32 @offset(0)
+ d:vec3<f32> @offset(16)
+}
+
+$B1: { # root
+ %v:ptr<uniform, SB, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<uniform, vec3<f32>, read> = access %v, 1u
+ %4:f32 = load_vector_element %3, 1u
+ %b:f32 = let %4
+ %6:f32 = load_vector_element %3, 2u
+ %c:f32 = let %6
+ ret
+ }
+}
+)";
+
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterChangeImmediateToUniformTest, Determinism_MultipleUsesOfLetFromVar) {
+ auto* sb =
+ ty.Struct(mod.symbols.New("SB"), {
+ {mod.symbols.New("a"), ty.array<vec4<f32>, 2>()},
+ {mod.symbols.New("b"), ty.array<vec4<i32>, 2>()},
+ });
+
+ auto* var = b.Var("v", immediate, sb, core::Access::kRead);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ auto* let = b.Let("l", var);
+ auto* pa =
+ b.Access(ty.ptr(immediate, ty.array<vec4<f32>, 2>(), core::Access::kRead), let, 0_u);
+ b.Let("a", b.Load(pa));
+ auto* pb =
+ b.Access(ty.ptr(immediate, ty.array<vec4<i32>, 2>(), core::Access::kRead), let, 1_u);
+ b.Let("b", b.Load(pb));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+SB = struct @align(16) {
+ a:array<vec4<f32>, 2> @offset(0)
+ b:array<vec4<i32>, 2> @offset(32)
+}
+
+$B1: { # root
+ %v:ptr<immediate, SB, read> = var undef
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %l:ptr<immediate, SB, read> = let %v
+ %4:ptr<immediate, array<vec4<f32>, 2>, read> = access %l, 0u
+ %5:array<vec4<f32>, 2> = load %4
+ %a:array<vec4<f32>, 2> = let %5
+ %7:ptr<immediate, array<vec4<i32>, 2>, read> = access %l, 1u
+ %8:array<vec4<i32>, 2> = load %7
+ %b:array<vec4<i32>, 2> = let %8
+ ret
+ }
+}
+)";
+ ASSERT_EQ(src, str());
+
+ auto* expect = R"(
+SB = struct @align(16) {
+ a:array<vec4<f32>, 2> @offset(0)
+ b:array<vec4<i32>, 2> @offset(32)
+}
+
+$B1: { # root
+ %v:ptr<uniform, SB, read> = var undef @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %l:ptr<uniform, SB, read> = let %v
+ %4:ptr<uniform, array<vec4<f32>, 2>, read> = access %l, 0u
+ %5:array<vec4<f32>, 2> = load %4
+ %a:array<vec4<f32>, 2> = let %5
+ %7:ptr<uniform, array<vec4<i32>, 2>, read> = access %l, 1u
+ %8:array<vec4<i32>, 2> = load %7
+ %b:array<vec4<i32>, 2> = let %8
+ ret
+ }
+}
+)";
+
+ ChangeImmediateToUniformConfig config = {};
+ Run(ChangeImmediateToUniform, config);
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/raise.cc b/src/tint/lang/hlsl/writer/raise/raise.cc
index 298a7af..c6f5cec 100644
--- a/src/tint/lang/hlsl/writer/raise/raise.cc
+++ b/src/tint/lang/hlsl/writer/raise/raise.cc
@@ -51,6 +51,7 @@
#include "src/tint/lang/hlsl/writer/common/options.h"
#include "src/tint/lang/hlsl/writer/raise/binary_polyfill.h"
#include "src/tint/lang/hlsl/writer/raise/builtin_polyfill.h"
+#include "src/tint/lang/hlsl/writer/raise/change_immediate_to_uniform.h"
#include "src/tint/lang/hlsl/writer/raise/decompose_storage_access.h"
#include "src/tint/lang/hlsl/writer/raise/decompose_uniform_access.h"
#include "src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.h"
@@ -185,9 +186,20 @@
}
RUN_TRANSFORM(core::ir::transform::DirectVariableAccess, module,
core::ir::transform::DirectVariableAccessOptions{});
+
// DecomposeStorageAccess must come after Robustness and DirectVariableAccess
RUN_TRANSFORM(raise::DecomposeStorageAccess, module);
- // Comes after DecomposeStorageAccess.
+
+ // ChangeImmediateToUniformConfig must come before DecomposeUniformAccess(to write correct
+ // uniform access instructions) and after DirectVariableAccess(to handle immediate pointers
+ // being passed as function parameters).
+ {
+ raise::ChangeImmediateToUniformConfig config = {
+ .immediate_binding_point = options.immediate_binding_point,
+ };
+ RUN_TRANSFORM(raise::ChangeImmediateToUniform, module, config);
+ }
+ // Comes after DecomposeStorageAccess and ChangeImmediateToUniform.
RUN_TRANSFORM(raise::DecomposeUniformAccess, module);
// PixelLocal must run after DirectVariableAccess to avoid chasing pointer parameters.
diff --git a/src/tint/lang/hlsl/writer/writer.cc b/src/tint/lang/hlsl/writer/writer.cc
index e6afb2c..1d09c2a 100644
--- a/src/tint/lang/hlsl/writer/writer.cc
+++ b/src/tint/lang/hlsl/writer/writer.cc
@@ -53,9 +53,6 @@
for (auto* inst : *ir.root_block) {
auto* var = inst->As<core::ir::Var>();
auto* ptr = var->Result()->Type()->As<core::type::Pointer>();
- if (ptr->AddressSpace() == core::AddressSpace::kImmediate) {
- return Failure("immediate data is not supported by the HLSL backend");
- }
if (ptr->AddressSpace() == core::AddressSpace::kPixelLocal) {
// Check the pixel_local variables have corresponding entries in the PLS attachment map.
auto* str = ptr->StoreType()->As<core::type::Struct>();
diff --git a/test/tint/var/uses/immediate.wgsl.expected.ir.dxc.hlsl b/test/tint/var/uses/immediate.wgsl.expected.ir.dxc.hlsl
index 4fe59a8..196a67d 100644
--- a/test/tint/var/uses/immediate.wgsl.expected.ir.dxc.hlsl
+++ b/test/tint/var/uses/immediate.wgsl.expected.ir.dxc.hlsl
@@ -1,17 +1,57 @@
SKIP: FAILED
-immediate data are not supported by the HLSL backend
-immediate data are not supported by the HLSL backend
-immediate data are not supported by the HLSL backend
//
// main1
//
+
+cbuffer cbuffer_a : register(b0) {
+ uint4 a[1];
+};
+void uses_a() {
+ int foo = asint(a[0u].x);
+}
+
+[numthreads(1, 1, 1)]
+void main1() {
+ uses_a();
+}
+
//
// main2
//
+
+cbuffer cbuffer_a : register(b0) {
+ uint4 a[1];
+};
+void uses_a() {
+ int foo = asint(a[0u].x);
+}
+
+void uses_uses_a() {
+ uses_a();
+}
+
+[numthreads(1, 1, 1)]
+void main2() {
+ uses_uses_a();
+}
+
//
// main3
//
+
+cbuffer cbuffer_b : register(b0) {
+ uint4 b[1];
+};
+void uses_b() {
+ int foo = asint(b[0u].x);
+}
+
+[numthreads(1, 1, 1)]
+void main3() {
+ uses_b();
+}
+
//
// main4
//
@@ -20,5 +60,3 @@
void main4() {
}
-
-tint executable returned error: exit status 1
diff --git a/test/tint/var/uses/immediate.wgsl.expected.ir.fxc.hlsl b/test/tint/var/uses/immediate.wgsl.expected.ir.fxc.hlsl
index 4fe59a8..196a67d 100644
--- a/test/tint/var/uses/immediate.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/var/uses/immediate.wgsl.expected.ir.fxc.hlsl
@@ -1,17 +1,57 @@
SKIP: FAILED
-immediate data are not supported by the HLSL backend
-immediate data are not supported by the HLSL backend
-immediate data are not supported by the HLSL backend
//
// main1
//
+
+cbuffer cbuffer_a : register(b0) {
+ uint4 a[1];
+};
+void uses_a() {
+ int foo = asint(a[0u].x);
+}
+
+[numthreads(1, 1, 1)]
+void main1() {
+ uses_a();
+}
+
//
// main2
//
+
+cbuffer cbuffer_a : register(b0) {
+ uint4 a[1];
+};
+void uses_a() {
+ int foo = asint(a[0u].x);
+}
+
+void uses_uses_a() {
+ uses_a();
+}
+
+[numthreads(1, 1, 1)]
+void main2() {
+ uses_uses_a();
+}
+
//
// main3
//
+
+cbuffer cbuffer_b : register(b0) {
+ uint4 b[1];
+};
+void uses_b() {
+ int foo = asint(b[0u].x);
+}
+
+[numthreads(1, 1, 1)]
+void main3() {
+ uses_b();
+}
+
//
// main4
//
@@ -20,5 +60,3 @@
void main4() {
}
-
-tint executable returned error: exit status 1
diff --git a/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.dxc.hlsl b/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.dxc.hlsl
index 1aea3bb..216f89f 100644
--- a/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.dxc.hlsl
+++ b/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.dxc.hlsl
@@ -1,5 +1,24 @@
SKIP: FAILED
-immediate data are not supported by the HLSL backend
+struct main_outputs {
+ float4 tint_symbol : SV_Position;
+};
-tint executable returned error: exit status 1
+struct main_inputs {
+ uint b : SV_InstanceID;
+};
+
+
+cbuffer cbuffer_a : register(b0) {
+ uint4 a[1];
+};
+float4 main_inner(uint b) {
+ float v = asfloat(a[0u].x);
+ return float4(((v + float(b))).xxxx);
+}
+
+main_outputs main(main_inputs inputs) {
+ main_outputs v_1 = {main_inner(inputs.b)};
+ return v_1;
+}
+
diff --git a/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.fxc.hlsl b/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.fxc.hlsl
index 1aea3bb..216f89f 100644
--- a/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/var/uses/immediate_and_instance_index.wgsl.expected.ir.fxc.hlsl
@@ -1,5 +1,24 @@
SKIP: FAILED
-immediate data are not supported by the HLSL backend
+struct main_outputs {
+ float4 tint_symbol : SV_Position;
+};
-tint executable returned error: exit status 1
+struct main_inputs {
+ uint b : SV_InstanceID;
+};
+
+
+cbuffer cbuffer_a : register(b0) {
+ uint4 a[1];
+};
+float4 main_inner(uint b) {
+ float v = asfloat(a[0u].x);
+ return float4(((v + float(b))).xxxx);
+}
+
+main_outputs main(main_inputs inputs) {
+ main_outputs v_1 = {main_inner(inputs.b)};
+ return v_1;
+}
+