[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