[clang] [llvm] [HLSL][DirectX] Correct codegen of `dx.load.input`/`dx.store.output` intrinsic calls (PR #212656)
Finn Plummer via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 29 08:52:52 PDT 2026
https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/212656
>From 5a5d75d02f02eac883a7deedfec18bfc0277d603 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 24 Jul 2026 23:34:32 +0000
Subject: [PATCH 1/5] remove operands
---
clang/lib/CodeGen/CGHLSLRuntime.cpp | 11 ++++------
.../CodeGenHLSL/semantics/SV_Position.ps.hlsl | 4 ++--
.../CodeGenHLSL/semantics/SV_Position.vs.hlsl | 4 ++--
.../CodeGenHLSL/semantics/SV_Target.ps.hlsl | 2 +-
.../CodeGenHLSL/semantics/SV_VertexID.vs.hlsl | 2 +-
.../semantics/semantic.arbitrary.hlsl | 6 +++---
.../CodeGenHLSL/semantics/semantic.array.hlsl | 9 ++++++---
.../semantics/semantic.array.output.hlsl | 9 ++++++---
...antic.explicit-location-output-struct.hlsl | 2 +-
.../semantics/semantic.explicit-location.hlsl | 2 +-
.../semantics/semantic.struct.output.hlsl | 4 ++--
clang/test/CodeGenHLSL/sret_output.hlsl | 2 +-
llvm/include/llvm/IR/IntrinsicsDirectX.td | 20 +++++++++++--------
13 files changed, 42 insertions(+), 35 deletions(-)
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 8794579166b6a..03cc39de49638 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1150,10 +1150,9 @@ CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
// DXIL packing rules etc shall be handled here.
- // FIXME: generate proper sigpoint, index, col, row values.
+ // FIXME: generate proper index, col, row values.
// FIXME: also DXIL loads vectors element by element.
- SmallVector<Value *> Args{B.getInt32(4), B.getInt32(0), B.getInt32(0),
- B.getInt8(0),
+ SmallVector<Value *> Args{B.getInt32(0), B.getInt32(0), B.getInt8(0),
llvm::PoisonValue::get(B.getInt32Ty())};
llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_load_input;
@@ -1175,12 +1174,10 @@ void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
// DXIL packing rules etc shall be handled here.
- // FIXME: generate proper sigpoint, index, col, row values.
- SmallVector<Value *> Args{B.getInt32(4),
- B.getInt32(0),
+ // FIXME: generate proper sigid, index, col, row values.
+ SmallVector<Value *> Args{B.getInt32(0),
B.getInt32(0),
B.getInt8(0),
- llvm::PoisonValue::get(B.getInt32Ty()),
Source};
llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_store_output;
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
index b118ee395f7de..ac695b1f7d5eb 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
@@ -9,9 +9,9 @@ float4 main(float4 p : SV_Position) : A {
// CHECK-SPIRV: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
// CHECK-SPIRV: store <4 x float> %[[#R]], ptr addrspace(8) @A0, align 4
- // CHECK-DXIL: %SV_Position0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
+ // CHECK-DXIL: %SV_Position0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 0, i8 0, i32 poison)
// CHECK-DXIL: %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> %SV_Position0)
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#TMP]])
return p;
}
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl
index caab744c1fa98..350b5e3612e32 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl
@@ -10,9 +10,9 @@ float4 main(float4 p : SV_Position) : SV_Position {
// CHECK-SPIRV: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
// CHECK-SPIRV: store <4 x float> %[[#R]], ptr addrspace(8) @SV_Position, align 4
- // CHECK-DXIL: %SV_Position0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
+ // CHECK-DXIL: %SV_Position0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 0, i8 0, i32 poison)
// CHECK-DXIL: %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> %SV_Position0)
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#TMP]])
return p;
}
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl
index 1ab4df4f61bd7..73255e3a9b939 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl
@@ -9,7 +9,7 @@ float4 main(float4 p : SV_Position) : SV_Target {
// CHECK-SPIRV: store <4 x float> %[[#R]], ptr addrspace(8) @SV_Target0, align 4
// CHECK-DXIL: %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> %SV_Position0)
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#TMP]])
return p;
}
diff --git a/clang/test/CodeGenHLSL/semantics/SV_VertexID.vs.hlsl b/clang/test/CodeGenHLSL/semantics/SV_VertexID.vs.hlsl
index e2c184ac7948c..491f10b530c38 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_VertexID.vs.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_VertexID.vs.hlsl
@@ -8,7 +8,7 @@ uint main(uint id : SV_VertexID) : A {
// CHECK-SPIRV: %[[#P:]] = load i32, ptr addrspace(7) @SV_VertexID, align 4
// CHECK-SPIRV: %[[#]] = call spir_func i32 @_Z4mainj(i32 %[[#P]])
- // CHECK-DXIL: %SV_VertexID0 = call i32 @llvm.dx.load.input.i32(i32 4, i32 0, i32 0, i8 0, i32 poison)
+ // CHECK-DXIL: %SV_VertexID0 = call i32 @llvm.dx.load.input.i32(i32 0, i32 0, i8 0, i32 poison)
// CHECK-DXIL: %[[#]] = call i32 @_Z4mainj(i32 %SV_VertexID0)
return id;
}
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl
index 1ea2827660307..720dafcd4f1b6 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl
@@ -14,9 +14,9 @@ void main(float a : AAA, int b : B, float2 c : CC) {
// CHECK: define void @main()
-// CHECK-DXIL: %AAA0 = call float @llvm.dx.load.input.f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
-// CHECK-DXIL: %B0 = call i32 @llvm.dx.load.input.i32(i32 4, i32 0, i32 0, i8 0, i32 poison)
-// CHECK-DXIL %CC0 = call <2 x float> @llvm.dx.load.input.v2f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %AAA0 = call float @llvm.dx.load.input.f32(i32 0, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %B0 = call i32 @llvm.dx.load.input.i32(i32 1, i32 0, i8 0, i32 poison)
+// CHECK-DXIL %CC0 = call <2 x float> @llvm.dx.load.input.v2f32(i32 2, i32 0, i8 0, i32 poison)
// CHECK-DXIL: call void @_Z4mainfiDv2_f(float %AAA0, i32 %B0, <2 x float> %CC0)
// CHECK-SPIRV: %[[#AAA0:]] = load float, ptr addrspace(7) @AAA0, align 4
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
index c62c4d4a517f2..b50d2da9a0114 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
@@ -12,9 +12,12 @@ struct S0 {
// CHECK-SPIRV: @A2 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_2:]]
// CHECK: define void @main0()
-// CHECK-DXIL: %A0 = call [2 x <4 x float>] @llvm.dx.load.input.a2v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
-// CHECK-DXIL: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %A0, 0
-// CHECK-DXIL: %A2 = call <4 x float> @llvm.dx.load.input.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %A0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %[[#POS0:]] = insertvalue [2 x <4 x float>] poison, <4 x float> %A0, 0
+// CHECK-DXIL: %A1 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 1, i8 0, i32 poison)
+// CHECK-DXIL: %[[#POS1:]] = insertvalue [2 x <4 x float>] %[[#POS0]], <4 x float> %A1, 1
+// CHECK-DXIL: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %[[#POS1]], 0
+// CHECK-DXIL: %A2 = call <4 x float> @llvm.dx.load.input.v4f32(i32 1, i32 0, i8 0, i32 poison)
// CHECK-DXIL: %[[#TMP1:]] = insertvalue %struct.S0 %[[#TMP0]], <4 x float> %A2, 1
// CHECK-SPIRV: %[[#A0:]] = load [2 x <4 x float>], ptr addrspace(7) @A0, align 4
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
index 18942699aaa2c..9d3afc5e8fba6 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
@@ -12,17 +12,20 @@ struct S0 {
S0 main1(float4 input : A) : B {
// CHECK: %[[#ARG:]] = alloca %struct.S0
// CHECK-SPIRV: %[[#INPUT:]] = load <4 x float>, ptr addrspace(7) @A0, align 4
-// CHECK-DXIL: %A0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %A0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 0, i8 0, i32 poison)
// CHECK-DXIL: call void @{{.*}}main1{{.*}}(ptr %[[#ARG]], <4 x float> %A0)
// CHECK-SPIRV: call spir_func void @{{.*}}main1{{.*}}(ptr %[[#ARG]], <4 x float> %[[#INPUT]])
// CHECK: %[[#ST:]] = load %struct.S0, ptr %[[#ARG]]
// CHECK: %[[#TMP:]] = extractvalue %struct.S0 %[[#ST]], 0
// CHECK-SPIRV: store [2 x <4 x float>] %[[#TMP]], ptr addrspace(8) @B0, align 4
- // CHECK-DXIL: call void @llvm.dx.store.output.a2v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, [2 x <4 x float>] %[[#TMP]])
+ // CHECK-DXIL: %[[#POS0:]] = extractvalue [2 x <4 x float>] %[[#TMP]], 0
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#POS0]])
+ // CHECK-DXIL: %[[#POS1:]] = extractvalue [2 x <4 x float>] %[[#TMP]], 1
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 1, i8 0, <4 x float> %[[#POS1]])
// CHECK: %[[#TMP:]] = extractvalue %struct.S0 %[[#ST]], 1
// CHECK-SPIRV: store <4 x float> %[[#TMP]], ptr addrspace(8) @B2, align 4
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 1, i32 0, i8 0, <4 x float> %[[#TMP]])
S0 output;
output.position[0] = input;
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl
index 8bd8998cdceb5..b24cee7baf8ae 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl
@@ -21,7 +21,7 @@ Output main(float4 p : SV_Position) {
// CHECK: %[[#FIELD:]] = extractvalue %struct.Output %[[#TMP]], 0
// CHECK-SPIRV: store <4 x float> %[[#FIELD]], ptr addrspace(8) @SV_Target0, align 4
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#FIELD]])
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#FIELD]])
Output o;
o.field = p;
return o;
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl
index 625d5dcb02ec4..2ca0f3fe65a57 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl
@@ -9,7 +9,7 @@
// CHECK-SPIRV: store <4 x float> %[[#R]], ptr addrspace(8) @SV_Target0, align 4
// CHECK-DXIL: %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> %SV_Position0)
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#TMP]])
return p;
}
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.struct.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.struct.output.hlsl
index 0f2444d21724a..426738adfe7ef 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.struct.output.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.struct.output.hlsl
@@ -43,10 +43,10 @@ Output main(Input input) {
// CHECK: %[[#TMP:]] = load %struct.Output, ptr %[[#OUTPUT]], align 4
// CHECK: %[[#VAL:]] = extractvalue %struct.Output %[[#TMP]], 0
// CHECK-SPIRV: store float %[[#VAL]], ptr addrspace(8) @A4, align 4
-// CHECK-DXIL: call void @llvm.dx.store.output.f32(i32 4, i32 0, i32 0, i8 0, i32 poison, float %[[#VAL]])
+// CHECK-DXIL: call void @llvm.dx.store.output.f32(i32 0, i32 0, i8 0, float %[[#VAL]])
// CHECK: %[[#VAL:]] = extractvalue %struct.Output %[[#TMP]], 1
// CHECK-SPIRV: store float %[[#VAL]], ptr addrspace(8) @A2, align 4
-// CHECK-DXIL: call void @llvm.dx.store.output.f32(i32 4, i32 0, i32 0, i8 0, i32 poison, float %[[#VAL]])
+// CHECK-DXIL: call void @llvm.dx.store.output.f32(i32 1, i32 0, i8 0, float %[[#VAL]])
// CHECK-SPIRV-DAG: ![[#METADATA_0]] = !{![[#METADATA_1:]]}
// CHECK-SPIRV-DAG: ![[#METADATA_2]] = !{![[#METADATA_3:]]}
diff --git a/clang/test/CodeGenHLSL/sret_output.hlsl b/clang/test/CodeGenHLSL/sret_output.hlsl
index e1aa0973fe5db..325c31541f473 100644
--- a/clang/test/CodeGenHLSL/sret_output.hlsl
+++ b/clang/test/CodeGenHLSL/sret_output.hlsl
@@ -25,7 +25,7 @@ S vs_main() {
// CHECK-VK: call spir_func void @_Z7vs_mainv(ptr %[[#alloca]])
// CHECK: %[[#a:]] = load %struct.S, ptr %[[#alloca]], align 4
// CHECK: %[[#b:]] = extractvalue %struct.S %[[#a]], 0
-// CHECK-DX: call void @llvm.dx.store.output.f32(i32 4, i32 0, i32 0, i8 0, i32 poison, float %[[#b]])
+// CHECK-DX: call void @llvm.dx.store.output.f32(i32 0, i32 0, i8 0, float %[[#b]])
// CHECK-VK: store float %3, ptr addrspace(8) @A4, align 4
// CHECK: ret void
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index 4dd86270f0d01..f16686186aac2 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -321,14 +321,18 @@ def int_dx_group_memory_barrier_with_group_sync
: DefaultAttrsIntrinsic<[], [], [IntrConvergent]>;
def int_dx_load_input
- : DefaultAttrsIntrinsic<[llvm_any_ty],
- [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i8_ty,
- llvm_i32_ty],
- [IntrConvergent]>;
+ : DefaultAttrsIntrinsic<
+ [llvm_any_ty],
+ [llvm_i32_ty /*SigElementId*/,
+ llvm_i32_ty /*RowIndex*/, llvm_i8_ty /*ColIndex*/,
+ llvm_i32_ty /*GsVertexOrPrimIndex*/],
+ [IntrConvergent]>;
def int_dx_store_output
- : DefaultAttrsIntrinsic<[],
- [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i8_ty,
- llvm_i32_ty, llvm_any_ty],
- [IntrConvergent]>;
+ : DefaultAttrsIntrinsic<
+ [],
+ [llvm_i32_ty /*SigElementId*/,
+ llvm_i32_ty /*RowIndex*/, llvm_i8_ty /*ColIndex*/,
+ llvm_any_ty /*Value*/],
+ [IntrConvergent]>;
}
>From 94e89e9bc8afadfa51b67c76c6479d192d561dc9 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Tue, 28 Jul 2026 20:41:35 +0000
Subject: [PATCH 2/5] assign semantic ids in parse order
---
clang/lib/CodeGen/CGHLSLRuntime.cpp | 13 ++++++-------
clang/lib/CodeGen/CGHLSLRuntime.h | 5 +++++
.../test/CodeGenHLSL/semantics/semantic.array.hlsl | 9 +++------
.../semantics/semantic.array.output.hlsl | 5 +----
4 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 03cc39de49638..12852f05e4fea 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1150,9 +1150,10 @@ CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
// DXIL packing rules etc shall be handled here.
- // FIXME: generate proper index, col, row values.
- // FIXME: also DXIL loads vectors element by element.
- SmallVector<Value *> Args{B.getInt32(0), B.getInt32(0), B.getInt8(0),
+ // FIXME: generate proper col, row values, also DXIL loads vectors element by
+ // element.
+ SmallVector<Value *> Args{B.getInt32(DXILInputSemanticIndex++), B.getInt32(0),
+ B.getInt8(0),
llvm::PoisonValue::get(B.getInt32Ty())};
llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_load_input;
@@ -1175,10 +1176,8 @@ void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B,
std::optional<unsigned> Index) {
// DXIL packing rules etc shall be handled here.
// FIXME: generate proper sigid, index, col, row values.
- SmallVector<Value *> Args{B.getInt32(0),
- B.getInt32(0),
- B.getInt8(0),
- Source};
+ SmallVector<Value *> Args{B.getInt32(DXILOutputSemanticIndex++),
+ B.getInt32(0), B.getInt8(0), Source};
llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_store_output;
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index cf47b1633fd3c..8f8bad69cae37 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -368,6 +368,11 @@ class CGHLSLRuntime {
llvm::DenseMap<const clang::RecordType *, llvm::StructType *> LayoutTypes;
unsigned SPIRVLastAssignedInputSemanticLocation = 0;
unsigned SPIRVLastAssignedOutputSemanticLocation = 0;
+ // DXIL assigns each leaf semantic an index in parse order. Inputs and outputs
+ // are counted independently. Reset in emitEntryFunction before lowering the
+ // semantics of an entry point.
+ unsigned DXILInputSemanticIndex = 0;
+ unsigned DXILOutputSemanticIndex = 0;
};
} // namespace CodeGen
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
index b50d2da9a0114..d395baaa6b335 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
@@ -12,13 +12,10 @@ struct S0 {
// CHECK-SPIRV: @A2 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_2:]]
// CHECK: define void @main0()
-// CHECK-DXIL: %A0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 0, i8 0, i32 poison)
-// CHECK-DXIL: %[[#POS0:]] = insertvalue [2 x <4 x float>] poison, <4 x float> %A0, 0
-// CHECK-DXIL: %A1 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 1, i8 0, i32 poison)
-// CHECK-DXIL: %[[#POS1:]] = insertvalue [2 x <4 x float>] %[[#POS0]], <4 x float> %A1, 1
-// CHECK-DXIL: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %[[#POS1]], 0
+// CHECK-DXIL: %A0 = call [2 x <4 x float>] @llvm.dx.load.input.a2v4f32(i32 0, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %A0, 0
// CHECK-DXIL: %A2 = call <4 x float> @llvm.dx.load.input.v4f32(i32 1, i32 0, i8 0, i32 poison)
-// CHECK-DXIL: %[[#TMP1:]] = insertvalue %struct.S0 %[[#TMP0]], <4 x float> %A2, 1
+// CHECK-DXIL: %[[#TMP1:]] = insertvalue %struct.S0 %[[#TMP0]], <4 x float> %A2, 1
// CHECK-SPIRV: %[[#A0:]] = load [2 x <4 x float>], ptr addrspace(7) @A0, align 4
// CHECK-SPIRV: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %[[#A0]], 0
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
index 9d3afc5e8fba6..32b7019b0c630 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
@@ -19,10 +19,7 @@ S0 main1(float4 input : A) : B {
// CHECK: %[[#ST:]] = load %struct.S0, ptr %[[#ARG]]
// CHECK: %[[#TMP:]] = extractvalue %struct.S0 %[[#ST]], 0
// CHECK-SPIRV: store [2 x <4 x float>] %[[#TMP]], ptr addrspace(8) @B0, align 4
- // CHECK-DXIL: %[[#POS0:]] = extractvalue [2 x <4 x float>] %[[#TMP]], 0
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#POS0]])
- // CHECK-DXIL: %[[#POS1:]] = extractvalue [2 x <4 x float>] %[[#TMP]], 1
- // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 1, i8 0, <4 x float> %[[#POS1]])
+ // CHECK-DXIL: call void @llvm.dx.store.output.a2v4f32(i32 0, i32 0, i8 0, [2 x <4 x float>] %[[#TMP]])
// CHECK: %[[#TMP:]] = extractvalue %struct.S0 %[[#ST]], 1
// CHECK-SPIRV: store <4 x float> %[[#TMP]], ptr addrspace(8) @B2, align 4
// CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 1, i32 0, i8 0, <4 x float> %[[#TMP]])
>From f71cd5396c3efbdd2b354d3e1db5faf6c3fe6ed0 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Tue, 28 Jul 2026 20:53:15 +0000
Subject: [PATCH 3/5] add test cases
---
.../CodeGenHLSL/semantics/semantic.input.hlsl | 55 ++++++++++++++++++
.../semantics/semantic.output.hlsl | 58 +++++++++++++++++++
2 files changed, 113 insertions(+)
create mode 100644 clang/test/CodeGenHLSL/semantics/semantic.input.hlsl
create mode 100644 clang/test/CodeGenHLSL/semantics/semantic.output.hlsl
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.input.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.input.hlsl
new file mode 100644
index 0000000000000..e1ea53bf16de9
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.input.hlsl
@@ -0,0 +1,55 @@
+// Per-row load.input emission for the range of semantic leaf types:
+// float - scalar -> 1 row
+// float4 - vector -> 1 row (4 columns)
+// float[5] - scalar array -> 5 rows
+// float4[2][3] - vector array -> 6 rows (multidimensional, 4 columns each)
+//
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s
+
+struct S {
+ float a : A;
+ float4 b : B;
+ float d[5] : D;
+ float4 e[2][3] : E;
+};
+
+[shader("vertex")]
+void main(S s) {}
+
+// float a : A -> 1 row, 1 column.
+// CHECK: %A0 = call float @llvm.dx.load.input.f32(i32 0, i32 0, i8 0, i32 poison)
+// CHECK: %[[#S:]] = insertvalue %struct.S poison, float %A0, 0
+
+// float4 b : B -> 1 row, 4 columns.
+// CHECK: %B0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 1, i32 0, i8 0, i32 poison)
+// CHECK: %[[#S:]] = insertvalue %struct.S %[[#S]], <4 x float> %B0, 1
+
+// float d[5] : D -> 5 rows, 1 column each.
+// CHECK: %D0 = call float @llvm.dx.load.input.f32(i32 2, i32 0, i8 0, i32 poison)
+// CHECK: %[[#D:]] = insertvalue [5 x float] poison, float %D0, 0
+// CHECK: %D1 = call float @llvm.dx.load.input.f32(i32 2, i32 1, i8 0, i32 poison)
+// CHECK: %[[#D:]] = insertvalue [5 x float] %[[#D]], float %D1, 1
+// CHECK: %D2 = call float @llvm.dx.load.input.f32(i32 2, i32 2, i8 0, i32 poison)
+// CHECK: %[[#D:]] = insertvalue [5 x float] %[[#D]], float %D2, 2
+// CHECK: %D3 = call float @llvm.dx.load.input.f32(i32 2, i32 3, i8 0, i32 poison)
+// CHECK: %[[#D:]] = insertvalue [5 x float] %[[#D]], float %D3, 3
+// CHECK: %D4 = call float @llvm.dx.load.input.f32(i32 2, i32 4, i8 0, i32 poison)
+// CHECK: %[[#D:]] = insertvalue [5 x float] %[[#D]], float %D4, 4
+// CHECK: %[[#S:]] = insertvalue %struct.S %[[#S]], [5 x float] %[[#D]], 2
+
+// float4 e[2][3] : E -> 6 rows (2 x 3), 4 columns each; row-major flattening.
+// CHECK: %E0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 3, i32 0, i8 0, i32 poison)
+// CHECK: %[[#E0:]] = insertvalue [3 x <4 x float>] poison, <4 x float> %E0, 0
+// CHECK: %E1 = call <4 x float> @llvm.dx.load.input.v4f32(i32 3, i32 1, i8 0, i32 poison)
+// CHECK: %[[#E0:]] = insertvalue [3 x <4 x float>] %[[#E0]], <4 x float> %E1, 1
+// CHECK: %E2 = call <4 x float> @llvm.dx.load.input.v4f32(i32 3, i32 2, i8 0, i32 poison)
+// CHECK: %[[#E0:]] = insertvalue [3 x <4 x float>] %[[#E0]], <4 x float> %E2, 2
+// CHECK: %[[#E:]] = insertvalue [2 x [3 x <4 x float>]] poison, [3 x <4 x float>] %[[#E0]], 0
+// CHECK: %E3 = call <4 x float> @llvm.dx.load.input.v4f32(i32 3, i32 3, i8 0, i32 poison)
+// CHECK: %[[#E1:]] = insertvalue [3 x <4 x float>] poison, <4 x float> %E3, 0
+// CHECK: %E4 = call <4 x float> @llvm.dx.load.input.v4f32(i32 3, i32 4, i8 0, i32 poison)
+// CHECK: %[[#E1:]] = insertvalue [3 x <4 x float>] %[[#E1]], <4 x float> %E4, 1
+// CHECK: %E5 = call <4 x float> @llvm.dx.load.input.v4f32(i32 3, i32 5, i8 0, i32 poison)
+// CHECK: %[[#E1:]] = insertvalue [3 x <4 x float>] %[[#E1]], <4 x float> %E5, 2
+// CHECK: %[[#E:]] = insertvalue [2 x [3 x <4 x float>]] %[[#E]], [3 x <4 x float>] %[[#E1]], 1
+// CHECK: %[[#S:]] = insertvalue %struct.S %[[#S]], [2 x [3 x <4 x float>]] %[[#E]], 3
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl
new file mode 100644
index 0000000000000..2b1d36b140838
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl
@@ -0,0 +1,58 @@
+// Per-row store.output emission for the range of semantic leaf types:
+// float - scalar -> 1 row
+// float4 - vector -> 1 row (4 columns)
+// float[5] - scalar array -> 5 rows
+// float4[2][3] - vector array -> 6 rows (multidimensional, 4 columns each)
+//
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s
+
+struct S {
+ float a : A;
+ float4 b : B;
+ float d[5] : D;
+ float4 e[2][3] : E;
+};
+
+[shader("vertex")]
+S main() {
+ S s;
+ return s;
+}
+
+// float a : A -> 1 row, 1 column.
+// CHECK: %[[#A:]] = extractvalue %struct.S %[[#S:]], 0
+// CHECK: call void @llvm.dx.store.output.f32(i32 0, i32 0, i8 0, float %[[#A]])
+
+// float4 b : B -> 1 row, 4 columns; the whole vector is stored.
+// CHECK: %[[#B:]] = extractvalue %struct.S %[[#S]], 1
+// CHECK: call void @llvm.dx.store.output.v4f32(i32 1, i32 0, i8 0, <4 x float> %[[#B]])
+
+// float d[5] : D -> 5 rows, 1 column each.
+// CHECK: %[[#D:]] = extractvalue %struct.S %[[#S]], 2
+// CHECK: %[[#D0:]] = extractvalue [5 x float] %[[#D]], 0
+// CHECK: call void @llvm.dx.store.output.f32(i32 2, i32 0, i8 0, float %[[#D0]])
+// CHECK: %[[#D1:]] = extractvalue [5 x float] %[[#D]], 1
+// CHECK: call void @llvm.dx.store.output.f32(i32 2, i32 1, i8 0, float %[[#D1]])
+// CHECK: %[[#D2:]] = extractvalue [5 x float] %[[#D]], 2
+// CHECK: call void @llvm.dx.store.output.f32(i32 2, i32 2, i8 0, float %[[#D2]])
+// CHECK: %[[#D3:]] = extractvalue [5 x float] %[[#D]], 3
+// CHECK: call void @llvm.dx.store.output.f32(i32 2, i32 3, i8 0, float %[[#D3]])
+// CHECK: %[[#D4:]] = extractvalue [5 x float] %[[#D]], 4
+// CHECK: call void @llvm.dx.store.output.f32(i32 2, i32 4, i8 0, float %[[#D4]])
+
+// float4 e[2][3] : E -> 6 rows (2 x 3), 4 columns each; row-major flattening.
+// CHECK: %[[#E:]] = extractvalue %struct.S %[[#S]], 3
+// CHECK: %[[#E0:]] = extractvalue [2 x [3 x <4 x float>]] %[[#E]], 0
+// CHECK: %[[#E00:]] = extractvalue [3 x <4 x float>] %[[#E0]], 0
+// CHECK: call void @llvm.dx.store.output.v4f32(i32 3, i32 0, i8 0, <4 x float> %[[#E00]])
+// CHECK: %[[#E01:]] = extractvalue [3 x <4 x float>] %[[#E0]], 1
+// CHECK: call void @llvm.dx.store.output.v4f32(i32 3, i32 1, i8 0, <4 x float> %[[#E01]])
+// CHECK: %[[#E02:]] = extractvalue [3 x <4 x float>] %[[#E0]], 2
+// CHECK: call void @llvm.dx.store.output.v4f32(i32 3, i32 2, i8 0, <4 x float> %[[#E02]])
+// CHECK: %[[#E1:]] = extractvalue [2 x [3 x <4 x float>]] %[[#E]], 1
+// CHECK: %[[#E10:]] = extractvalue [3 x <4 x float>] %[[#E1]], 0
+// CHECK: call void @llvm.dx.store.output.v4f32(i32 3, i32 3, i8 0, <4 x float> %[[#E10]])
+// CHECK: %[[#E11:]] = extractvalue [3 x <4 x float>] %[[#E1]], 1
+// CHECK: call void @llvm.dx.store.output.v4f32(i32 3, i32 4, i8 0, <4 x float> %[[#E11]])
+// CHECK: %[[#E12:]] = extractvalue [3 x <4 x float>] %[[#E1]], 2
+// CHECK: call void @llvm.dx.store.output.v4f32(i32 3, i32 5, i8 0, <4 x float> %[[#E12]])
>From e84d362d8e30541cef8acde230fa0a4aa7255c47 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Tue, 28 Jul 2026 23:16:48 +0000
Subject: [PATCH 4/5] correct intrin calls
---
clang/lib/CodeGen/CGHLSLRuntime.cpp | 175 +++++++++++++++---
clang/lib/CodeGen/CGHLSLRuntime.h | 3 +
.../CodeGenHLSL/semantics/semantic.array.hlsl | 11 +-
.../semantics/semantic.array.output.hlsl | 5 +-
4 files changed, 163 insertions(+), 31 deletions(-)
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 12852f05e4fea..0ef1456fec49e 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1142,21 +1142,59 @@ void CGHLSLRuntime::emitSPIRVUserSemanticStore(
VariableName.str());
}
+namespace {
+// Describes how a semantic leaf lowers to signature rows
+struct SemanticShape {
+ unsigned Rows;
+ unsigned Cols;
+ QualType RowType;
+};
+} // namespace
+
+// Returns the QualType of a semantic leaf declarator. For a function the
+// declared return type is used, otherwise the declared type.
+static QualType getSemanticLeafType(const clang::DeclaratorDecl *Decl) {
+ if (const auto *FD = dyn_cast<clang::FunctionDecl>(Decl))
+ return FD->getDeclaredReturnType();
+ return Decl->getType();
+}
+
+// Walks through the surrounding constant array types of \p Ty, accumulating the
+// number of rows, until reaching a scalar, vector, or matrix leaf. The leaf is
+// returned as the row type together with the number of rows.
+static SemanticShape getSemanticShape(ASTContext &Ctx, QualType Ty) {
+ unsigned Rows = 1;
+ while (const ConstantArrayType *CAT = Ctx.getAsConstantArrayType(Ty)) {
+ Rows *= CAT->getSize().getZExtValue();
+ Ty = CAT->getElementType();
+ }
+
+ unsigned Cols = 1;
+ if (const auto *VT = Ty->getAs<clang::VectorType>()) {
+ Cols = VT->getNumElements();
+ } else if (const auto *MT = Ty->getAs<clang::ConstantMatrixType>()) {
+ // FIXME: a matrix leaf lowers to one row per matrix row but if column_major
+ // is specified we transpose the num rows and num cols, this depends on
+ // #211977 to resolve
+ Cols = MT->getNumColumns();
+ }
+
+ return {Rows, Cols, Ty};
+}
+
llvm::Value *
CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
+ const clang::DeclaratorDecl *Decl,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
- Twine BaseName = Twine(Semantic->getAttrName()->getName());
- Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
+ StringRef Name = Semantic->getAttrName()->getName();
+ SemanticShape Shape =
+ getSemanticShape(CGM.getContext(), getSemanticLeafType(Decl));
- // DXIL packing rules etc shall be handled here.
- // FIXME: generate proper col, row values, also DXIL loads vectors element by
- // element.
- SmallVector<Value *> Args{B.getInt32(DXILInputSemanticIndex++), B.getInt32(0),
- B.getInt8(0),
- llvm::PoisonValue::get(B.getInt32Ty())};
+ llvm::Type *RowTy = CGM.getTypes().ConvertTypeForMem(Shape.RowType);
- llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_load_input;
+ llvm::Function *IntrFn = llvm::Intrinsic::getOrInsertDeclaration(
+ B.GetInsertBlock()->getModule(), llvm::Intrinsic::dx_load_input, {RowTy});
SmallVector<OperandBundleDef, 1> OB;
if (auto *Token = getConvergenceToken(*B.GetInsertBlock())) {
@@ -1164,22 +1202,82 @@ CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
OB.emplace_back("convergencectrl", bundleArgs);
}
- llvm::Function *IntrFn = llvm::Intrinsic::getOrInsertDeclaration(
- B.GetInsertBlock()->getModule(), IntrinsicID, {Type});
- llvm::Value *Value = B.CreateCall(IntrFn, Args, OB, VariableName);
- return Value;
+ unsigned SigId = DXILInputSemanticIndex++;
+ unsigned Row = 0;
+
+ // Scalar and vector leaves need no aggregate reconstruction.
+ if (!isa<llvm::ArrayType>(Type)) {
+ SmallVector<Value *> Args{
+ /*SigElementId=*/B.getInt32(SigId),
+ /*RowIndex=*/B.getInt32(Row),
+ /*ColIndex=*/B.getInt8(0),
+ /*GsVertexOrPrimIndex=*/llvm::PoisonValue::get(B.getInt32Ty())};
+ llvm::Value *Result =
+ B.CreateCall(IntrFn, Args, OB, Twine(Name).concat(Twine(Row++)));
+ assert(Row == Shape.Rows && "unexpected number of semantic rows");
+ return Result;
+ }
+
+ struct LoadItem {
+ llvm::ArrayType *Type;
+ llvm::Value *Aggregate;
+ unsigned NextIndex;
+ };
+
+ SmallVector<LoadItem> Worklist;
+ auto *RootTy = cast<llvm::ArrayType>(Type);
+ Worklist.push_back({RootTy, llvm::PoisonValue::get(RootTy), 0});
+
+ llvm::Value *Result = nullptr;
+ while (!Worklist.empty()) {
+ LoadItem &Frame = Worklist.back();
+
+ if (Frame.NextIndex == Frame.Type->getNumElements()) {
+ llvm::Value *Aggregate = Frame.Aggregate;
+ Worklist.pop_back();
+ if (Worklist.empty()) {
+ Result = Aggregate;
+ break;
+ }
+
+ LoadItem &Parent = Worklist.back();
+ Parent.Aggregate =
+ B.CreateInsertValue(Parent.Aggregate, Aggregate, Parent.NextIndex++);
+ continue;
+ }
+
+ llvm::Type *ElementTy = Frame.Type->getElementType();
+ if (auto *AT = dyn_cast<llvm::ArrayType>(ElementTy)) {
+ Worklist.push_back({AT, llvm::PoisonValue::get(AT), 0});
+ continue;
+ }
+
+ SmallVector<Value *> Args{
+ /*SigElementId=*/B.getInt32(SigId),
+ /*RowIndex=*/B.getInt32(Row),
+ /*ColIndex=*/B.getInt8(0),
+ /*GsVertexOrPrimIndex=*/llvm::PoisonValue::get(B.getInt32Ty())};
+ llvm::Value *Elt =
+ B.CreateCall(IntrFn, Args, OB, Twine(Name).concat(Twine(Row++)));
+ Frame.Aggregate =
+ B.CreateInsertValue(Frame.Aggregate, Elt, Frame.NextIndex++);
+ }
+ assert(Row == Shape.Rows && "unexpected number of semantic rows");
+ return Result;
}
void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B,
llvm::Value *Source,
+ const clang::DeclaratorDecl *Decl,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
- // DXIL packing rules etc shall be handled here.
- // FIXME: generate proper sigid, index, col, row values.
- SmallVector<Value *> Args{B.getInt32(DXILOutputSemanticIndex++),
- B.getInt32(0), B.getInt8(0), Source};
+ SemanticShape Shape =
+ getSemanticShape(CGM.getContext(), getSemanticLeafType(Decl));
+ llvm::Type *RowTy = CGM.getTypes().ConvertTypeForMem(Shape.RowType);
- llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_store_output;
+ llvm::Function *IntrFn = llvm::Intrinsic::getOrInsertDeclaration(
+ B.GetInsertBlock()->getModule(), llvm::Intrinsic::dx_store_output,
+ {RowTy});
SmallVector<OperandBundleDef, 1> OB;
if (auto *Token = getConvergenceToken(*B.GetInsertBlock())) {
@@ -1187,9 +1285,34 @@ void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B,
OB.emplace_back("convergencectrl", bundleArgs);
}
- llvm::Function *IntrFn = llvm::Intrinsic::getOrInsertDeclaration(
- B.GetInsertBlock()->getModule(), IntrinsicID, {Source->getType()});
- B.CreateCall(IntrFn, Args, OB);
+ unsigned SigId = DXILOutputSemanticIndex++;
+ unsigned Row = 0;
+
+ struct StoreItem {
+ llvm::Value *Aggregate;
+ std::optional<unsigned> Index;
+ };
+
+ SmallVector<StoreItem> Worklist{{Source, std::nullopt}};
+ while (!Worklist.empty()) {
+ StoreItem Item = Worklist.pop_back_val();
+ llvm::Value *Val = Item.Index
+ ? B.CreateExtractValue(Item.Aggregate, *Item.Index)
+ : Item.Aggregate;
+
+ if (auto *AT = dyn_cast<llvm::ArrayType>(Val->getType())) {
+ // Push elements in reverse so index 0 is visited first
+ for (unsigned I = AT->getNumElements(); I-- > 0;)
+ Worklist.push_back({Val, I});
+ continue;
+ }
+
+ SmallVector<Value *> Args{/*SigElementId=*/B.getInt32(SigId),
+ /*RowIndex=*/B.getInt32(Row++),
+ /*ColIndex=*/B.getInt8(0), /*Value=*/Val};
+ B.CreateCall(IntrFn, Args, OB);
+ }
+ assert(Row == Shape.Rows && "unexpected number of semantic rows");
}
llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
@@ -1199,7 +1322,7 @@ llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
return emitSPIRVUserSemanticLoad(B, Type, Decl, Semantic, Index);
if (CGM.getTarget().getTriple().isDXIL())
- return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
+ return emitDXILUserSemanticLoad(B, Type, Decl, Semantic, Index);
llvm_unreachable("Unsupported target for user-semantic load.");
}
@@ -1212,7 +1335,7 @@ void CGHLSLRuntime::emitUserSemanticStore(IRBuilder<> &B, llvm::Value *Source,
return emitSPIRVUserSemanticStore(B, Source, Decl, Semantic, Index);
if (CGM.getTarget().getTriple().isDXIL())
- return emitDXILUserSemanticStore(B, Source, Semantic, Index);
+ return emitDXILUserSemanticStore(B, Source, Decl, Semantic, Index);
llvm_unreachable("Unsupported target for user-semantic load.");
}
@@ -1267,7 +1390,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
Semantic->getAttrName()->getName(),
/* BuiltIn::FragCoord */ 15);
if (CGM.getTarget().getTriple().isDXIL())
- return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
+ return emitDXILUserSemanticLoad(B, Type, Decl, Semantic, Index);
}
if (ST == Triple::EnvironmentType::Vertex) {
@@ -1282,7 +1405,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
Semantic->getAttrName()->getName(),
/* BuiltIn::VertexIndex */ 42);
else
- return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
+ return emitDXILUserSemanticLoad(B, Type, Decl, Semantic, Index);
}
}
@@ -1312,7 +1435,7 @@ void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, llvm::Value *Source,
std::string SemanticName = Semantic->getAttrName()->getName().upper();
if (SemanticName == "SV_POSITION") {
if (CGM.getTarget().getTriple().isDXIL()) {
- emitDXILUserSemanticStore(B, Source, Semantic, Index);
+ emitDXILUserSemanticStore(B, Source, Decl, Semantic, Index);
return;
}
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 8f8bad69cae37..bbd9d59a4de4c 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -340,6 +340,7 @@ class CGHLSLRuntime {
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index);
llvm::Value *emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
+ const clang::DeclaratorDecl *Decl,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index);
llvm::Value *emitUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
@@ -352,6 +353,7 @@ class CGHLSLRuntime {
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index);
void emitDXILUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+ const clang::DeclaratorDecl *Decl,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index);
void emitUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
@@ -368,6 +370,7 @@ class CGHLSLRuntime {
llvm::DenseMap<const clang::RecordType *, llvm::StructType *> LayoutTypes;
unsigned SPIRVLastAssignedInputSemanticLocation = 0;
unsigned SPIRVLastAssignedOutputSemanticLocation = 0;
+
// DXIL assigns each leaf semantic an index in parse order. Inputs and outputs
// are counted independently. Reset in emitEntryFunction before lowering the
// semantics of an entry point.
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
index d395baaa6b335..5466948bd32bc 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl
@@ -12,10 +12,13 @@ struct S0 {
// CHECK-SPIRV: @A2 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_2:]]
// CHECK: define void @main0()
-// CHECK-DXIL: %A0 = call [2 x <4 x float>] @llvm.dx.load.input.a2v4f32(i32 0, i32 0, i8 0, i32 poison)
-// CHECK-DXIL: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %A0, 0
-// CHECK-DXIL: %A2 = call <4 x float> @llvm.dx.load.input.v4f32(i32 1, i32 0, i8 0, i32 poison)
-// CHECK-DXIL: %[[#TMP1:]] = insertvalue %struct.S0 %[[#TMP0]], <4 x float> %A2, 1
+// CHECK-DXIL: %A0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %[[#POS0:]] = insertvalue [2 x <4 x float>] poison, <4 x float> %A0, 0
+// CHECK-DXIL: %A1 = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 1, i8 0, i32 poison)
+// CHECK-DXIL: %[[#POS1:]] = insertvalue [2 x <4 x float>] %[[#POS0]], <4 x float> %A1, 1
+// CHECK-DXIL: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %[[#POS1]], 0
+// CHECK-DXIL: %A01 = call <4 x float> @llvm.dx.load.input.v4f32(i32 1, i32 0, i8 0, i32 poison)
+// CHECK-DXIL: %[[#TMP1:]] = insertvalue %struct.S0 %[[#TMP0]], <4 x float> %A01, 1
// CHECK-SPIRV: %[[#A0:]] = load [2 x <4 x float>], ptr addrspace(7) @A0, align 4
// CHECK-SPIRV: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %[[#A0]], 0
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
index 32b7019b0c630..9d3afc5e8fba6 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
@@ -19,7 +19,10 @@ S0 main1(float4 input : A) : B {
// CHECK: %[[#ST:]] = load %struct.S0, ptr %[[#ARG]]
// CHECK: %[[#TMP:]] = extractvalue %struct.S0 %[[#ST]], 0
// CHECK-SPIRV: store [2 x <4 x float>] %[[#TMP]], ptr addrspace(8) @B0, align 4
- // CHECK-DXIL: call void @llvm.dx.store.output.a2v4f32(i32 0, i32 0, i8 0, [2 x <4 x float>] %[[#TMP]])
+ // CHECK-DXIL: %[[#POS0:]] = extractvalue [2 x <4 x float>] %[[#TMP]], 0
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 0, i8 0, <4 x float> %[[#POS0]])
+ // CHECK-DXIL: %[[#POS1:]] = extractvalue [2 x <4 x float>] %[[#TMP]], 1
+ // CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 0, i32 1, i8 0, <4 x float> %[[#POS1]])
// CHECK: %[[#TMP:]] = extractvalue %struct.S0 %[[#ST]], 1
// CHECK-SPIRV: store <4 x float> %[[#TMP]], ptr addrspace(8) @B2, align 4
// CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 1, i32 0, i8 0, <4 x float> %[[#TMP]])
>From 12f99419a3a1fe9c94d76cf2f7bfcb569ff6a51b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 29 Jul 2026 15:52:34 +0000
Subject: [PATCH 5/5] clang format
---
clang/lib/CodeGen/CGHLSLRuntime.cpp | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 0ef1456fec49e..df6cb42540a75 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1182,11 +1182,9 @@ static SemanticShape getSemanticShape(ASTContext &Ctx, QualType Ty) {
return {Rows, Cols, Ty};
}
-llvm::Value *
-CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
- const clang::DeclaratorDecl *Decl,
- HLSLAppliedSemanticAttr *Semantic,
- std::optional<unsigned> Index) {
+llvm::Value *CGHLSLRuntime::emitDXILUserSemanticLoad(
+ llvm::IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
+ HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
StringRef Name = Semantic->getAttrName()->getName();
SemanticShape Shape =
getSemanticShape(CGM.getContext(), getSemanticLeafType(Decl));
More information about the cfe-commits
mailing list