[clang] [llvm] [HLSL][SPIR-V] Implements SV_Position for VS/PS I/O (PR #168735)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Nov 19 08:36:02 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-codegen
Author: Nathan Gauër (Keenuts)
<details>
<summary>Changes</summary>
Current implementation for SV_Position was very basic to allow implementing/testing some semantics. Now that semantic support is more robust, I can move forward and implement the whole semantic logic.
DX part is still a bit placeholder.
---
Full diff: https://github.com/llvm/llvm-project/pull/168735.diff
9 Files Affected:
- (modified) clang/include/clang/Sema/SemaHLSL.h (+8-3)
- (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+20-9)
- (modified) clang/lib/Sema/SemaHLSL.cpp (+23-14)
- (modified) clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl (+15-5)
- (added) clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl (+26)
- (modified) clang/test/SemaHLSL/Semantics/position.ps.hlsl (+4-10)
- (removed) clang/test/SemaHLSL/Semantics/position.vs.hlsl (-6)
- (added) llvm/test/CodeGen/SPIRV/semantics/position.ps.ll (+32)
- (added) llvm/test/CodeGen/SPIRV/semantics/position.vs.ll (+31)
``````````diff
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 86da323892f98..15edb7e77a22b 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -250,15 +250,20 @@ class SemaHLSL : public SemaBase {
const RecordType *RT);
void checkSemanticAnnotation(FunctionDecl *EntryPoint, const Decl *Param,
- const HLSLAppliedSemanticAttr *SemanticAttr);
+ const HLSLAppliedSemanticAttr *SemanticAttr,
+ bool IsInput);
+
bool determineActiveSemanticOnScalar(FunctionDecl *FD,
DeclaratorDecl *OutputDecl,
DeclaratorDecl *D,
SemanticInfo &ActiveSemantic,
- llvm::StringSet<> &ActiveInputSemantics);
+ llvm::StringSet<> &ActiveSemantics,
+ bool IsInput);
+
bool determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *OutputDecl,
DeclaratorDecl *D, SemanticInfo &ActiveSemantic,
- llvm::StringSet<> &ActiveInputSemantics);
+ llvm::StringSet<> &ActiveSemantics,
+ bool IsInput);
void processExplicitBindingsOnDecl(VarDecl *D);
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 0371af417bdab..b9bdce521c989 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -730,10 +730,18 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
}
if (SemanticName == "SV_POSITION") {
- if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Pixel)
- return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
- Semantic->getAttrName()->getName(),
- /* BuiltIn::FragCoord */ 15);
+ if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Pixel) {
+ if (CGM.getTarget().getTriple().isSPIRV())
+ return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
+ Semantic->getAttrName()->getName(),
+ /* BuiltIn::FragCoord */ 15);
+ if (CGM.getTarget().getTriple().isDXIL())
+ return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
+ }
+
+ if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Vertex) {
+ return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
+ }
}
llvm_unreachable("non-handled system semantic. FIXME.");
@@ -759,11 +767,14 @@ void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, llvm::Value *Source,
std::optional<unsigned> Index) {
std::string SemanticName = Semantic->getAttrName()->getName().upper();
- if (SemanticName == "SV_POSITION")
- createSPIRVBuiltinStore(B, CGM.getModule(), Source,
- Semantic->getAttrName()->getName(),
- /* BuiltIn::Position */ 0);
- else
+ if (SemanticName == "SV_POSITION") {
+ if (CGM.getTarget().getTriple().isDXIL())
+ emitDXILUserSemanticStore(B, Source, Semantic, Index);
+ else if (CGM.getTarget().getTriple().isSPIRV())
+ createSPIRVBuiltinStore(B, CGM.getModule(), Source,
+ Semantic->getAttrName()->getName(),
+ /* BuiltIn::Position */ 0);
+ } else
llvm_unreachable("non-handled system semantic. FIXME.");
}
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index e7ee3b1adf941..5beb4e7fe8546 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -771,9 +771,12 @@ void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
}
}
-bool SemaHLSL::determineActiveSemanticOnScalar(
- FunctionDecl *FD, DeclaratorDecl *OutputDecl, DeclaratorDecl *D,
- SemanticInfo &ActiveSemantic, llvm::StringSet<> &UsedSemantics) {
+bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD,
+ DeclaratorDecl *OutputDecl,
+ DeclaratorDecl *D,
+ SemanticInfo &ActiveSemantic,
+ llvm::StringSet<> &UsedSemantics,
+ bool IsInput) {
if (ActiveSemantic.Semantic == nullptr) {
ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
if (ActiveSemantic.Semantic)
@@ -792,7 +795,7 @@ bool SemaHLSL::determineActiveSemanticOnScalar(
if (!A)
return false;
- checkSemanticAnnotation(FD, D, A);
+ checkSemanticAnnotation(FD, D, A, IsInput);
OutputDecl->addAttr(A);
unsigned Location = ActiveSemantic.Index.value_or(0);
@@ -820,7 +823,8 @@ bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD,
DeclaratorDecl *OutputDecl,
DeclaratorDecl *D,
SemanticInfo &ActiveSemantic,
- llvm::StringSet<> &UsedSemantics) {
+ llvm::StringSet<> &UsedSemantics,
+ bool IsInput) {
if (ActiveSemantic.Semantic == nullptr) {
ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
if (ActiveSemantic.Semantic)
@@ -833,12 +837,13 @@ bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD,
const RecordType *RT = dyn_cast<RecordType>(T);
if (!RT)
return determineActiveSemanticOnScalar(FD, OutputDecl, D, ActiveSemantic,
- UsedSemantics);
+ UsedSemantics, IsInput);
const RecordDecl *RD = RT->getDecl();
for (FieldDecl *Field : RD->fields()) {
SemanticInfo Info = ActiveSemantic;
- if (!determineActiveSemantic(FD, OutputDecl, Field, Info, UsedSemantics)) {
+ if (!determineActiveSemantic(FD, OutputDecl, Field, Info, UsedSemantics,
+ IsInput)) {
Diag(Field->getLocation(), diag::note_hlsl_semantic_used_here) << Field;
return false;
}
@@ -920,7 +925,7 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
// FIXME: Verify output semantics in parameters.
if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic,
- ActiveInputSemantics)) {
+ ActiveInputSemantics, /* IsInput= */ true)) {
Diag(Param->getLocation(), diag::note_previous_decl) << Param;
FD->setInvalidDecl();
}
@@ -932,12 +937,13 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
if (ActiveSemantic.Semantic)
ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
if (!FD->getReturnType()->isVoidType())
- determineActiveSemantic(FD, FD, FD, ActiveSemantic, ActiveOutputSemantics);
+ determineActiveSemantic(FD, FD, FD, ActiveSemantic, ActiveOutputSemantics,
+ /* IsInput= */ false);
}
void SemaHLSL::checkSemanticAnnotation(
FunctionDecl *EntryPoint, const Decl *Param,
- const HLSLAppliedSemanticAttr *SemanticAttr) {
+ const HLSLAppliedSemanticAttr *SemanticAttr, bool IsInput) {
auto *ShaderAttr = EntryPoint->getAttr<HLSLShaderAttr>();
assert(ShaderAttr && "Entry point has no shader attribute");
llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
@@ -961,11 +967,14 @@ void SemaHLSL::checkSemanticAnnotation(
}
if (SemanticName == "SV_POSITION") {
- // TODO(#143523): allow use on other shader types & output once the overall
- // semantic logic is implemented.
- if (ST == llvm::Triple::Pixel)
+ // SV_Position can is I/O for vertex shaders.
+ // For pixel shaders, only valid as input.
+ // Note: for SPIR-V, not backed by a builtin when used as input in a vertex
+ // shaders.
+ if (ST == llvm::Triple::Vertex || (ST == llvm::Triple::Pixel && IsInput))
return;
- DiagnoseAttrStageMismatch(SemanticAttr, ST, {llvm::Triple::Pixel});
+ DiagnoseAttrStageMismatch(SemanticAttr, ST,
+ {llvm::Triple::Pixel, llvm::Triple::Vertex});
return;
}
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
index be30e79438831..b7d2283ea7766 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
@@ -1,11 +1,21 @@
-// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=CHECK-SPIRV
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=CHECK-DXIL
-// CHECK: @SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations !0
+// CHECK-SPIRV: @SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
// CHECK: define void @main() {{.*}} {
float4 main(float4 p : SV_Position) : A {
- // CHECK: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
- // CHECK: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
- // CHECK: store <4 x float> %[[#R]], ptr addrspace(8) @A0, align 16
+ // CHECK-SPIRV: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
+ // 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 16
+
+ // 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: %[[#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]])
return p;
}
+
+// CHECK-SPIRV-DAG: ![[#MD_0]] = !{![[#MD_1:]]}
+// CHECK-SPIRV-DAG: ![[#MD_1]] = !{i32 11, i32 15}
+// | `-> BuiltIn Position
+// `-> SPIR-V decoration 'FragCoord'
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl
new file mode 100644
index 0000000000000..0156c0bb816c1
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -triple dxil-unknown-shadermodel6.8-vertex -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck --check-prefix=CHECK-DXIL %s
+// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck --check-prefix=CHECK-SPIRV %s
+
+// CHECK-SPIRV: @SV_Position0 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
+// CHECK-SPIRV: @SV_Position = external hidden thread_local addrspace(8) global <4 x float>, !spirv.Decorations ![[#MD_2:]]
+
+// CHECK: define void @main() {{.*}} {
+float4 main(float4 p : SV_Position) : SV_Position {
+ // CHECK-SPIRV: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position0, align 16
+ // 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 16
+
+ // 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: %[[#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]])
+ return p;
+}
+
+// CHECK-SPIRV-DAG: ![[#MD_0]] = !{![[#MD_1:]]}
+// CHECK-SPIRV-DAG: ![[#MD_2]] = !{![[#MD_3:]]}
+// CHECK-SPIRV-DAG: ![[#MD_1]] = !{i32 30, i32 0}
+// | `-> Location 0
+// `-> SPIR-V decoration 'Location'
+// CHECK-SPIRV-DAG: ![[#MD_3]] = !{i32 11, i32 0}
+// | `-> BuiltIn Position
+// `-> SPIR-V decoration 'BuiltIn'
diff --git a/clang/test/SemaHLSL/Semantics/position.ps.hlsl b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
index 2d02384821d90..47d07887911d6 100644
--- a/clang/test/SemaHLSL/Semantics/position.ps.hlsl
+++ b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
@@ -1,13 +1,7 @@
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-pixel -x hlsl -finclude-default-header -o - %s -ast-dump | FileCheck %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -finclude-default-header -x hlsl -verify -o - %s
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header -x hlsl -verify -o - %s
-// FIXME(Keenuts): change output semantic to something valid for pixels shaders
-float4 main(float4 a : SV_Position2) : A {
-// CHECK: FunctionDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> line:[[@LINE-1]]:8 main 'float4 (float4)'
-// CHECK-NEXT: ParmVarDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> col:20 used a 'float4':'vector<float, 4>'
-// CHECK-NEXT: HLSLParsedSemanticAttr 0x{{[0-9a-f]+}} <col:24> "SV_Position" 2
-// CHECK-NEXT: HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:24> "SV_Position" 2
-
-// CHECK: HLSLParsedSemanticAttr 0x{{[0-9a-f]+}} <line:4:40> "A" 0
-// CHECK: HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:40> "A" 0
+float4 main(float4 a : A) : SV_Position {
+// expected-error at -1 {{attribute 'SV_Position' is unsupported in 'pixel' shaders, requires one of the following: pixel, vertex}}
return a;
}
diff --git a/clang/test/SemaHLSL/Semantics/position.vs.hlsl b/clang/test/SemaHLSL/Semantics/position.vs.hlsl
deleted file mode 100644
index 9d0ff285ce055..0000000000000
--- a/clang/test/SemaHLSL/Semantics/position.vs.hlsl
+++ /dev/null
@@ -1,6 +0,0 @@
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-vertex -x hlsl -finclude-default-header -o - %s -verify
-
-// expected-error at +1 {{attribute 'SV_Position' is unsupported in 'vertex' shaders, requires pixel}}
-float4 main(float4 a : SV_Position) : A {
- return a;
-}
diff --git a/llvm/test/CodeGen/SPIRV/semantics/position.ps.ll b/llvm/test/CodeGen/SPIRV/semantics/position.ps.ll
new file mode 100644
index 0000000000000..2c02987f73928
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/semantics/position.ps.ll
@@ -0,0 +1,32 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-vulkan-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: OpDecorate %[[#INPUT:]] BuiltIn FragCoord
+; CHECK-DAG: OpDecorate %[[#OUTPUT:]] Location 0
+
+; CHECK-DAG: %[[#float:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#v4:]] = OpTypeVector %[[#float]] 4
+; CHECK-DAG: %[[#ptr_i:]] = OpTypePointer Input %[[#v4]]
+; CHECK-DAG: %[[#ptr_o:]] = OpTypePointer Output %[[#v4]]
+
+; CHECK-DAG: %[[#INPUT]] = OpVariable %[[#ptr_i]] Input
+; CHECK-DAG: %[[#OUTPUT]] = OpVariable %[[#ptr_o]] Output
+
+ at SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations !0
+ at A0 = external hidden thread_local addrspace(8) global <4 x float>, !spirv.Decorations !2
+
+define void @main() #1 {
+entry:
+ %0 = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
+ store <4 x float> %0, ptr addrspace(8) @A0, align 16
+ ret void
+
+; CHECK: %[[#TMP:]] = OpLoad %[[#v4]] %[[#INPUT]] Aligned 16
+; CHECK: OpStore %[[#OUTPUT]] %[[#TMP]] Aligned 16
+}
+
+!0 = !{!1}
+!1 = !{i32 11, i32 15}
+!2 = !{!3}
+!3 = !{i32 30, i32 0}
+
diff --git a/llvm/test/CodeGen/SPIRV/semantics/position.vs.ll b/llvm/test/CodeGen/SPIRV/semantics/position.vs.ll
new file mode 100644
index 0000000000000..73165f3719a97
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/semantics/position.vs.ll
@@ -0,0 +1,31 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-vulkan-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: OpDecorate %[[#INPUT:]] Location 0
+; CHECK-DAG: OpDecorate %[[#OUTPUT:]] BuiltIn Position
+
+; CHECK-DAG: %[[#float:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#v4:]] = OpTypeVector %[[#float]] 4
+; CHECK-DAG: %[[#ptr_i:]] = OpTypePointer Input %[[#v4]]
+; CHECK-DAG: %[[#ptr_o:]] = OpTypePointer Output %[[#v4]]
+
+; CHECK-DAG: %[[#INPUT]] = OpVariable %[[#ptr_i]] Input
+; CHECK-DAG: %[[#OUTPUT]] = OpVariable %[[#ptr_o]] Output
+
+ at SV_Position0 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations !0
+ at SV_Position = external hidden thread_local addrspace(8) global <4 x float>, !spirv.Decorations !2
+
+define void @main() #1 {
+entry:
+ %0 = load <4 x float>, ptr addrspace(7) @SV_Position0, align 16
+ store <4 x float> %0, ptr addrspace(8) @SV_Position, align 16
+ ret void
+
+; CHECK: %[[#TMP:]] = OpLoad %[[#v4]] %[[#INPUT]] Aligned 16
+; CHECK: OpStore %[[#OUTPUT]] %[[#TMP]] Aligned 16
+}
+
+!0 = !{!1}
+!1 = !{i32 30, i32 0}
+!2 = !{!3}
+!3 = !{i32 11, i32 0}
``````````
</details>
https://github.com/llvm/llvm-project/pull/168735
More information about the llvm-commits
mailing list