https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/236252
>From 96bce6ef59dc8efb2220624aa82fa13c3f20e514 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Tue, 27 Oct 2026 22:01:12 +0000 Subject: [PATCH 1/7] [HLSL] Add support for SV_SampleIndex --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 49 ++++++++++-- clang/lib/Sema/SemaHLSL.cpp | 8 +- .../semantics/SV_SampleIndex.ps.hlsl | 51 ++++++++++++ .../SemaHLSL/Semantics/sampleindex.ps.hlsl | 57 ++++++++++++++ .../Semantics/sampleindex.stages.hlsl | 23 ++++++ .../llvm/Frontend/HLSL/SemanticSignatures.h | 2 + llvm/include/llvm/IR/IntrinsicsDirectX.td | 3 + llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 5 ++ llvm/lib/Target/DirectX/DXIL.td | 9 +++ llvm/lib/Target/DirectX/DXILShaderFlags.cpp | 8 +- .../Target/DirectX/DXILSignatureAnalysis.cpp | 23 +++++- .../SemanticSignatures-sample-index.ll | 62 +++++++++++++++ ...semantic-signatures-sample-index-errors.ll | 78 +++++++++++++++++++ .../ShaderFlags/unpacked-low-precision.ll | 40 ++++++++++ llvm/test/CodeGen/DirectX/sample_index.ll | 28 +++++++ .../SPIRV/semantics/sample-index.ps.ll | 32 ++++++++ .../HLSLSemanticSignaturePackingTest.cpp | 15 ++++ .../Frontend/HLSLSemanticStagesTest.cpp | 13 +++- 18 files changed, 492 insertions(+), 14 deletions(-) create mode 100644 clang/test/CodeGenHLSL/semantics/SV_SampleIndex.ps.hlsl create mode 100644 clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl create mode 100644 clang/test/SemaHLSL/Semantics/sampleindex.stages.hlsl create mode 100644 llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll create mode 100644 llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index-errors.ll create mode 100644 llvm/test/CodeGen/DirectX/ShaderFlags/unpacked-low-precision.ll create mode 100644 llvm/test/CodeGen/DirectX/sample_index.ll create mode 100644 llvm/test/CodeGen/SPIRV/semantics/sample-index.ps.ll diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 6dfec8cd934580..fac0f7b850f71a 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1183,14 +1183,19 @@ static llvm::Value *createSPIRVInputLoad( IRBuilder<> &B, llvm::Module &M, llvm::Type *Ty, const Twine &Name, llvm::hlsl::SPIRVInterfaceDecoration Decoration, unsigned Operand, llvm::hlsl::InterpolationMode InterpMode) { + llvm::Type *LoadTy = Ty; + if (Decoration == llvm::hlsl::SPIRVInterfaceDecoration::BuiltIn && + Ty->isIntegerTy() && Ty->getIntegerBitWidth() < 32) + LoadTy = B.getInt32Ty(); auto *GV = new llvm::GlobalVariable( - M, Ty, /* isConstant= */ true, llvm::GlobalValue::ExternalLinkage, + M, LoadTy, /* isConstant= */ true, llvm::GlobalValue::ExternalLinkage, /* Initializer= */ nullptr, Name, /* insertBefore= */ nullptr, llvm::GlobalVariable::GeneralDynamicTLSModel, /* AddressSpace */ 7, /* isExternallyInitialized= */ true); GV->setVisibility(llvm::GlobalValue::HiddenVisibility); addSPIRVInterfaceDecorations(GV, Decoration, Operand, InterpMode); - return B.CreateLoad(Ty, GV); + llvm::Value *Value = B.CreateLoad(LoadTy, GV); + return LoadTy == Ty ? Value : B.CreateTrunc(Value, Ty); } llvm::Value *CGHLSLRuntime::emitSPIRVUserSemanticLoad( @@ -1345,13 +1350,24 @@ static llvm::hlsl::SemanticSignatureElement createSemanticSignatureElement( for (uint32_t I = 0, E = Shape.getNumRows(); I < E; ++I) SemanticIndices.push_back(FirstSemanticIndex + I); + llvm::dxbc::PSV::SemanticKind Kind = llvm::hlsl::getSemanticKind(Name); + llvm::dxil::ElementType CompType = + getSignatureComponentType(CGM, Shape.RowType); + // These system values may be declared as int or uint, but DXIL signatures + // require an unsigned component type. + if (Kind == llvm::dxbc::PSV::SemanticKind::SampleIndex) { + if (CompType == llvm::dxil::ElementType::I16) + CompType = llvm::dxil::ElementType::U16; + else if (CompType == llvm::dxil::ElementType::I32) + CompType = llvm::dxil::ElementType::U32; + } + // The remaining members keep their default value and will be filled at a // later stage, either during packing or analysis of usage. Interpolation // is set by the load/store traversal after visiting each leaf. - return llvm::hlsl::SemanticSignatureElement( - SigId, Name, getSignatureComponentType(CGM, Shape.RowType), - llvm::hlsl::getSemanticKind(Name), SemanticIndices, - static_cast<uint8_t>(Shape.Cols)); + return llvm::hlsl::SemanticSignatureElement(SigId, Name, CompType, Kind, + SemanticIndices, + static_cast<uint8_t>(Shape.Cols)); } llvm::Value *CGHLSLRuntime::emitDXILUserSemanticLoad( @@ -1687,6 +1703,27 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad( return B.CreateCall( CGM.getIntrinsic(llvm::Intrinsic::dx_start_vertex_location)); break; + case llvm::dxbc::PSV::SemanticKind::SampleIndex: + assert(Stage == llvm::Triple::Pixel && + "SV_SampleIndex is in an unavailable stage and should have been " + "diagnosed by Sema"); + if (CGM.getTarget().getTriple().isSPIRV()) + return createSPIRVInputLoad(B, CGM.getModule(), Type, + Semantic->getAttrName()->getName(), + llvm::hlsl::SPIRVInterfaceDecoration::BuiltIn, + /* SampleId */ 18, InterpMode); + if (CGM.getTarget().getTriple().isDXIL()) { + // The sample index is read with a dedicated operation, but an unpacked + // element is still added to the input signature for compatibility. + SemanticShape Shape = + getSemanticShape(CGM.getContext(), getSemanticLeafType(Decl)); + Signature.push_back(createSemanticSignatureElement( + CGM, Signature.size(), Semantic, Index, Shape)); + llvm::Value *SampleIndex = + B.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::dx_sample_index)); + return B.CreateTrunc(SampleIndex, Type); + } + break; case llvm::dxbc::PSV::SemanticKind::StartInstanceLocation: assert(Stage == llvm::Triple::Vertex && "SV_StartInstanceLocation is in an unavailable stage and should " diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index e42f9bb21ff70f..2ce99418b2f8e1 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -1015,6 +1015,7 @@ static bool isVkPipelineBuiltin(const ASTContext &AstContext, FunctionDecl *FD, return (ST == llvm::Triple::Pixel && IsInput) || (ST == llvm::Triple::Vertex && !IsInput); case SemanticKind::Barycentrics: + case SemanticKind::SampleIndex: return ST == llvm::Triple::Pixel && IsInput; case SemanticKind::StartVertexLocation: case SemanticKind::StartInstanceLocation: @@ -1436,6 +1437,12 @@ void SemaHLSL::diagnoseSemanticType(const Decl *D, << A->getAttrName() << /* scalar or vector of up to */ 1 << 4 << /* 16 or 32 bit floating-point */ 2 << DeclaredTy; return; + case SemanticKind::SampleIndex: + if (DeclaredTy != T || !T->isScalarType() || !isIntUpTo32Element(Ctx, T)) + Diag(A->getLoc(), diag::err_hlsl_semantic_invalid_type) + << A->getAttrName() << /* scalar */ 0 << 1 + << /* 16 or 32 bit integer */ 0 << DeclaredTy; + return; case SemanticKind::InstanceID: case SemanticKind::ShadingRate: // DXIL permits U32 or U16. SPIR-V requires a 32-bit scalar per: @@ -2253,7 +2260,6 @@ void SemaHLSL::handleSemanticAttr(Decl *D, const ParsedAttr &AL) { case SemanticKind::DomainLocation: case SemanticKind::PrimitiveID: case SemanticKind::GSInstanceID: - case SemanticKind::SampleIndex: case SemanticKind::IsFrontFace: case SemanticKind::Coverage: case SemanticKind::InnerCoverage: diff --git a/clang/test/CodeGenHLSL/semantics/SV_SampleIndex.ps.hlsl b/clang/test/CodeGenHLSL/semantics/SV_SampleIndex.ps.hlsl new file mode 100644 index 00000000000000..ebbaf33f79e218 --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/SV_SampleIndex.ps.hlsl @@ -0,0 +1,51 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.2-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck --check-prefixes=CHECK-DXIL,DXIL32 %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.2-pixel -x hlsl -emit-llvm -finclude-default-header -fnative-half-type -fnative-int16-type -disable-llvm-passes -DINT16 -o - %s | FileCheck --check-prefixes=CHECK-DXIL,DXIL16 %s +// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck --check-prefix=CHECK-SPIRV %s +// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -x hlsl -emit-llvm -finclude-default-header -fnative-half-type -fnative-int16-type -disable-llvm-passes -DINT16 -o - %s | FileCheck --check-prefix=SPIRV16 %s + +#ifdef INT16 +#define TYPE int16_t +#else +#define TYPE int +#endif + +// CHECK-SPIRV: @SV_SampleIndex = external hidden thread_local addrspace(7) externally_initialized constant i32, !spirv.Decorations ![[#MD_0:]] + +// SV_SampleIndex is read with a dedicated operation. It still adds an unpacked +// element to the input signature that does not consume a row, so the user +// input that follows it is packed at row 0. + +// CHECK-DXIL-LABEL: define void @main() +// CHECK-DXIL: %[[SI:.*]] = call i32 @llvm.dx.sample.index() +// DXIL32-NOT: trunc +// DXIL16: %[[SI16:.*]] = trunc i32 %[[SI]] to i16 +// CHECK-DXIL: %[[A:.*]] = call float @llvm.dx.load.input.f32(i32 1, i32 0, i8 0, i32 poison) +// DXIL32: call float @_Z4mainif(i32 %[[SI]], float %[[A]]) +// DXIL16: call float @_Z4mainsf(i16 %[[SI16]], float %[[A]]) + +// Signed declarations use an unsigned DXIL component type, with nointerpolation. +// DXIL32-DAG: !{i32 0, !"SV_SampleIndex", i32 5, i32 12, ![[#ZERO:]], i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +// DXIL16-DAG: !{i32 0, !"SV_SampleIndex", i32 3, i32 12, ![[#ZERO:]], i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +// CHECK-DXIL-DAG: !{i32 1, !"A", i32 9, i32 0, ![[#ZERO]], i32 2, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} +// CHECK-DXIL-DAG: ![[#ZERO]] = !{i32 0} + +// CHECK-SPIRV-LABEL: define void @main() +// CHECK-SPIRV: %[[SI:.*]] = load i32, ptr addrspace(7) @SV_SampleIndex, align 4 +// CHECK-SPIRV: call spir_func float @_Z4mainif(i32 %[[SI]], +float main(TYPE s : SV_SampleIndex, float a : A) : SV_Target { + return a * s; +} + +// CHECK-SPIRV-DAG: ![[#MD_0]] = !{![[#BUILTIN:]], ![[#FLAT:]]} +// The metadata below contains SPIR-V decoration 'BuiltIn' followed by +// BuiltIn SampleId. +// CHECK-SPIRV-DAG: ![[#BUILTIN]] = !{i32 11, i32 18} +// CHECK-SPIRV-DAG: ![[#FLAT]] = !{i32 14} + +// The SPIR-V built-in is a 32-bit integer, so a 16-bit declaration is +// truncated. +// SPIRV16: @SV_SampleIndex = {{.*}} addrspace(7) externally_initialized constant i32, +// SPIRV16-LABEL: define void @main() +// SPIRV16: %[[V32:.*]] = load i32, ptr addrspace(7) @SV_SampleIndex +// SPIRV16: %[[V16:.*]] = trunc i32 %[[V32]] to i16 +// SPIRV16: call spir_func float @_Z4mainsf(i16 %[[V16]], diff --git a/clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl b/clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl new file mode 100644 index 00000000000000..520893b5c4d9e1 --- /dev/null +++ b/clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl @@ -0,0 +1,57 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.2-library -fnative-half-type -fnative-int16-type -finclude-default-header -fsyntax-only -verify %s +// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-library -fnative-half-type -fnative-int16-type -finclude-default-header -fsyntax-only -verify %s + +// Type, index and interpolation checks for SV_SampleIndex. These are +// independent of the shader stage and IO direction, which are covered by +// sampleindex.stages.hlsl. + +[shader("pixel")] +void valid_uint(uint s : SV_SampleIndex) {} +[shader("pixel")] +void valid_int(int s : sv_sampleindex0) {} +[shader("pixel")] +void valid_flat(nointerpolation uint s : SV_SampleIndex) {} + +// 16-bit integers are allowed. SPIR-V converts them to and from the 32-bit +// built-in. +[shader("pixel")] +void uint16(uint16_t s : SV_SampleIndex) {} +[shader("pixel")] +void int16(int16_t s : SV_SampleIndex) {} + +struct Input { + float4 p : SV_Position; + uint s : SV_SampleIndex; +}; +[shader("pixel")] +void valid_struct(Input i) {} + +[shader("pixel")] +void bad_float(float s : SV_SampleIndex) {} +// expected-error@-1 {{semantic 'SV_SampleIndex' must be a scalar of 16 or 32 bit integer type (was 'float')}} +[shader("pixel")] +void bad_bool(bool s : SV_SampleIndex) {} +// expected-error@-1 {{semantic 'SV_SampleIndex' must be a scalar of 16 or 32 bit integer type (was 'bool')}} +[shader("pixel")] +void bad_int64(uint64_t s : SV_SampleIndex) {} +// expected-error@-1 {{semantic 'SV_SampleIndex' must be a scalar of 16 or 32 bit integer type (was 'uint64_t' (aka 'unsigned long'))}} +[shader("pixel")] +void bad_vector(uint1 s : SV_SampleIndex) {} +// expected-error@-1 {{semantic 'SV_SampleIndex' must be a scalar of 16 or 32 bit integer type (was 'uint1' (aka 'vector<uint, 1>'))}} +[shader("pixel")] +void bad_array(uint s[1] : SV_SampleIndex) {} +// expected-error@-1 {{semantic 'SV_SampleIndex' must be a scalar of 16 or 32 bit integer type (was 'uint[1]'}} + +[shader("pixel")] +void bad_index(uint s : SV_SampleIndex1) {} +// expected-error@-1 {{semantic 'SV_SampleIndex' does not allow indexing}} + +[shader("pixel")] +void same_index(uint s : SV_SampleIndex, // expected-note {{previous use is here}} + uint t : SV_SampleIndex0) {} // expected-note {{'t' declared here}} +// expected-error@-1 {{semantic index overlap SV_SampleIndex0}} + +// Integer inputs can only use nointerpolation. +[shader("pixel")] +void bad_interpolation(sample uint s : SV_SampleIndex) {} +// expected-error@-1 {{interpolation modifiers other than 'nointerpolation' cannot be used with type 'uint' (aka 'unsigned int')}} diff --git a/clang/test/SemaHLSL/Semantics/sampleindex.stages.hlsl b/clang/test/SemaHLSL/Semantics/sampleindex.stages.hlsl new file mode 100644 index 00000000000000..059328ccaf14ed --- /dev/null +++ b/clang/test/SemaHLSL/Semantics/sampleindex.stages.hlsl @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.2-library -finclude-default-header -x hlsl -verify -o - %s +// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-library -finclude-default-header -x hlsl -verify -o - %s + +// Stage and IO direction checks for SV_SampleIndex: only a pixel input. +// Type, index and interpolation checks are covered by sampleindex.ps.hlsl. + +[shader("pixel")] +float4 ok_ps_in(uint s : SV_SampleIndex) : SV_Target { + return float4((float)s, 0, 0, 1); +} + +[shader("pixel")] +uint bad_ps_out() : SV_SampleIndex { return 0; } +// expected-error@-1 {{semantic 'SV_SampleIndex' is not supported in pixel shader outputs}} + +[shader("vertex")] +float4 bad_vs_in(uint s : SV_SampleIndex) : SV_Position { return 0; } +// expected-error@-1 {{semantic 'SV_SampleIndex' is not supported in vertex shader inputs}} + +[shader("compute")] +[numthreads(1, 1, 1)] +void bad_cs_in(uint s : SV_SampleIndex) {} +// expected-error@-1 {{semantic 'SV_SampleIndex' is not supported in compute shader inputs}} diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h index 90b7241d4d21d4..971b493ab7ee36 100644 --- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h +++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h @@ -110,6 +110,8 @@ LLVM_ABI dxbc::PSV::InterpolationMode normalizeInterpolationMode( enum class SemanticInterpretation { Invalid, NotAllocated, + // In the signature, but not packed. This includes DXC's Shadow + // interpretation, whose value is read with a dedicated intrinsic. NotPacked, Arbitrary, SV, diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index b9b02aeb081916..6bde73d87f356a 100644 --- a/llvm/include/llvm/IR/IntrinsicsDirectX.td +++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td @@ -607,4 +607,7 @@ let TargetPrefix = "dx" in { def int_dx_start_instance_location : DefaultAttrsIntrinsic<[llvm_i32_ty], [], [IntrNoMem]>; + + def int_dx_sample_index + : DefaultAttrsIntrinsic<[llvm_i32_ty], [], [IntrNoMem]>; } diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp index 114d6309b12ddb..85c016b261ecd4 100644 --- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp +++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp @@ -180,6 +180,11 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) { {Triple::Vertex, IOType::In, SemanticInterpretation::NotAllocated}}; return Stages; } + case dxbc::PSV::SemanticKind::SampleIndex: { + static constexpr SemanticStageInfo Stages[] = { + {Triple::Pixel, IOType::In, SemanticInterpretation::NotPacked}}; + return Stages; + } case dxbc::PSV::SemanticKind::Barycentrics: { static constexpr SemanticStageInfo Stages[] = { {Triple::Pixel, IOType::In, SemanticInterpretation::NotPacked}}; diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td index 3f81e83b01ecba..ca8dd5450bff6d 100644 --- a/llvm/lib/Target/DirectX/DXIL.td +++ b/llvm/lib/Target/DirectX/DXIL.td @@ -1153,6 +1153,15 @@ def DerivFineY : DXILOp<86, unary> { let stages = [Stages<DXIL1_0, [library, pixel]>]; } +def SampleIndex : DXILOp<90, sampleIndex> { + let Doc = "returns the sample index in a sample-frequency pixel shader"; + let intrinsics = [IntrinSelect<int_dx_sample_index>]; + let result = OverloadTy; + let overloads = [Overloads<DXIL1_0, [Int32Ty]>]; + let stages = [Stages<DXIL1_0, [pixel]>]; + let attributes = [Attributes<DXIL1_0, [ReadNone]>]; +} + def ThreadId : DXILOp<93, threadId> { let Doc = "Reads the thread ID"; let intrinsics = [IntrinSelect<int_dx_thread_id>]; diff --git a/llvm/lib/Target/DirectX/DXILShaderFlags.cpp b/llvm/lib/Target/DirectX/DXILShaderFlags.cpp index 59953c191497be..b61f0aca42773b 100644 --- a/llvm/lib/Target/DirectX/DXILShaderFlags.cpp +++ b/llvm/lib/Target/DirectX/DXILShaderFlags.cpp @@ -456,11 +456,13 @@ void ModuleShaderFlags::initialize(Module &M, DXILResourceTypeMap &DRTM, CSF.Barycentrics |= llvm::any_of(Sig->Inputs, [](const auto &E) { return E.SemanticKind == dxbc::PSV::SemanticKind::Barycentrics; }); + // Unallocated elements do not occupy signature registers. Their type + // requirements come from the instructions that access them. auto HasLowPrecision = [](const auto &Elements) { return llvm::any_of(Elements, [](const auto &E) { - return E.CompType == ElementType::F16 || - E.CompType == ElementType::I16 || - E.CompType == ElementType::U16; + return E.isAllocated() && (E.CompType == ElementType::F16 || + E.CompType == ElementType::I16 || + E.CompType == ElementType::U16); }); }; if (HasLowPrecision(Sig->Inputs) || HasLowPrecision(Sig->Outputs)) { diff --git a/llvm/lib/Target/DirectX/DXILSignatureAnalysis.cpp b/llvm/lib/Target/DirectX/DXILSignatureAnalysis.cpp index d652ac24321fb7..7fa66a249ba8d5 100644 --- a/llvm/lib/Target/DirectX/DXILSignatureAnalysis.cpp +++ b/llvm/lib/Target/DirectX/DXILSignatureAnalysis.cpp @@ -96,6 +96,12 @@ static Error diagnoseSemanticType(const SemanticSignatureElement &E) { return signatureError( "SV_ShadingRate requires a scalar 16- or 32-bit unsigned integer"); break; + case dxbc::PSV::SemanticKind::SampleIndex: + if (E.Rows != 1 || E.Cols != 1 || + (E.CompType != ElementType::U16 && E.CompType != ElementType::U32)) + return signatureError( + "SV_SampleIndex requires a scalar 16- or 32-bit unsigned integer"); + break; case dxbc::PSV::SemanticKind::Barycentrics: if (E.Rows != 1 || E.Cols != 3 || (E.CompType != ElementType::F16 && E.CompType != ElementType::F32)) @@ -123,6 +129,10 @@ static Error diagnoseSemanticIndex(const SemanticSignatureElement &E) { if (E.SemanticIndices.front() != 0) return signatureError("SV_Barycentrics requires semantic index 0"); break; + case dxbc::PSV::SemanticKind::SampleIndex: + if (E.SemanticIndices.front() != 0) + return signatureError("SV_SampleIndex requires semantic index 0"); + break; default: break; } @@ -199,8 +209,8 @@ static Error parseSignature(Metadata *MD, "point"); if (Interpretation == SemanticInterpretation::NotPacked) { if (E.isAllocated()) - return signatureError( - "SV_Barycentrics must not have an allocated location"); + return signatureError(E.SemanticName + + " must not have an allocated location"); } else { if (!E.isAllocated()) return signatureError("signature element " + Twine(E.SigId) + " ('" + @@ -301,6 +311,8 @@ static Error analyzeAccess(const IntrinsicInst &I, EntrySignature &Sig) { return signatureError(Category + " signature element " + Twine(E.SigId) + " ('" + E.SemanticName + "'): " + Message); }; + if (E.SemanticKind == dxbc::PSV::SemanticKind::SampleIndex) + return AccessError("must not be read with dx.op.load/store"); Type *Ty = Input ? I.getType() : I.getArgOperand(3)->getType(); unsigned Width = 1; if (auto *VT = dyn_cast<FixedVectorType>(Ty)) { @@ -532,6 +544,8 @@ static dxbc::D3DSystemValue getSystemValue(dxbc::PSV::SemanticKind Kind) { return dxbc::D3DSystemValue::ShadingRate; case KindTy::Barycentrics: return dxbc::D3DSystemValue::Barycentrics; + case KindTy::SampleIndex: + return dxbc::D3DSystemValue::SampleIndex; default: llvm_unreachable("validated vertex/pixel signature semantic"); } @@ -564,6 +578,8 @@ static StringRef getContainerSemanticName(const SemanticSignatureElement &E) { return "SV_ShadingRate"; case Kind::Barycentrics: return "SV_Barycentrics"; + case Kind::SampleIndex: + return "SV_SampleIndex"; default: llvm_unreachable("validated vertex/pixel signature semantic"); } @@ -643,7 +659,8 @@ void EntrySignature::updatePSV(mcdxbc::PSVRuntimeInfo &PSV, llvm::any_of(Inputs, [](const auto &E) { return E.InterpMode == dxbc::PSV::InterpolationMode::LinearSample || E.InterpMode == - dxbc::PSV::InterpolationMode::LinearNoperspectiveSample; + dxbc::PSV::InterpolationMode::LinearNoperspectiveSample || + E.SemanticKind == dxbc::PSV::SemanticKind::SampleIndex; }); } diff --git a/llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll b/llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll new file mode 100644 index 00000000000000..72a23895e039ff --- /dev/null +++ b/llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll @@ -0,0 +1,62 @@ +; RUN: opt -disable-output -passes='print<dxil-signature>' %s 2>&1 | FileCheck %s --check-prefix=ANALYSIS +; RUN: llc -filetype=obj %s -o %t.dxbc +; RUN: obj2yaml %t.dxbc | FileCheck %s --check-prefix=PARTS +; RUN: llvm-objcopy --dump-section=DXIL=%t.bc %t.dxbc +; RUN: llvm-dis %t.bc -o - | FileCheck %s --check-prefixes=MD,DXIL + +; SV_SampleIndex is an unpacked element: it keeps a signature ID, but is read with +; dx.op.sampleIndex, does not consume a row and marks the pixel shader as +; sample-frequency. +target triple = "dxil-pc-shadermodel6.0-pixel" + +define void @main() #0 { + %p = call float @llvm.dx.load.input.f32(i32 0, i32 0, i8 0, i32 poison) + %si = call i32 @llvm.dx.sample.index() + %f = uitofp i32 %si to float + %result = fadd float %p, %f + call void @llvm.dx.store.output.f32(i32 0, i32 0, i8 0, float %result) + ret void +} +attributes #0 = { "hlsl.shader"="pixel" } + +!dx.valver = !{!10} +!10 = !{i32 1, i32 8} +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, !2} +!1 = !{!3, !4} +!2 = !{!6} +!3 = !{i32 0, !"A", i32 9, i32 0, !7, i32 2, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} +!4 = !{i32 1, !"SV_SampleIndex", i32 5, i32 12, !7, i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!6 = !{i32 0, !"SV_Target", i32 9, i32 16, !7, i32 0, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} +!7 = !{i32 0} + +; ANALYSIS: Inputs: 2 elements, 1 vectors +; ANALYSIS-NEXT: 0: A rows=1 cols=1 at 0:0 usage=1 dynamic=0 +; ANALYSIS-NEXT: 1: SV_SampleIndex rows=1 cols=1 at 4294967295:255 usage=0 dynamic=0 +; ANALYSIS-NEXT: Outputs: 1 elements, 1 vectors + +; DXIL: call float @dx.op.loadInput.f32(i32 4, i32 0, i32 0, i8 0, i32 {{.*}}) +; DXIL: call i32 @dx.op.sampleIndex.i32(i32 90) +; MD-DAG: !{i32 1, !"SV_SampleIndex", i8 5, i8 12, !{{[0-9]+}}, i8 1, i32 1, i8 1, i32 -1, i8 -1, null} + +; PARTS: Name: ISG1 +; PARTS: Name: SV_SampleIndex +; PARTS-NEXT: Index: 0 +; PARTS-NEXT: SystemValue: SampleIndex +; PARTS-NEXT: CompType: UInt32 +; PARTS-NEXT: Register: 4294967295 +; PARTS-NEXT: Mask: 1 +; PARTS-NEXT: ExclusiveMask: 0 +; PARTS: Name: PSV0 +; PARTS: SampleFrequency: 1 +; PARTS: SigInputVectors: 1 +; PARTS: SigInputElements: +; PARTS: Kind: Arbitrary +; PARTS: Indices: [ 0 ] +; PARTS-NEXT: StartRow: 0 +; PARTS-NEXT: Cols: 1 +; PARTS-NEXT: StartCol: 0 +; PARTS-NEXT: Allocated: false +; PARTS-NEXT: Kind: SampleIndex +; PARTS-NEXT: ComponentType: UInt32 +; PARTS-NEXT: Interpolation: Constant diff --git a/llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index-errors.ll b/llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index-errors.ll new file mode 100644 index 00000000000000..9d4d63bbc97289 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index-errors.ll @@ -0,0 +1,78 @@ +; RUN: split-file %s %t +; RUN: not opt -disable-output -passes=dxil-translate-metadata %t/signed.ll 2>&1 | FileCheck %s --check-prefix=CHECK-SIGNED +; RUN: not opt -disable-output -passes=dxil-translate-metadata %t/cols.ll 2>&1 | FileCheck %s --check-prefix=CHECK-COLS +; RUN: not opt -disable-output -passes=dxil-translate-metadata %t/index.ll 2>&1 | FileCheck %s --check-prefix=CHECK-INDEX +; RUN: not opt -disable-output -passes=dxil-translate-metadata %t/allocated.ll 2>&1 | FileCheck %s --check-prefix=CHECK-ALLOCATED +; RUN: not opt -disable-output -passes=dxil-translate-metadata %t/load.ll 2>&1 | FileCheck %s --check-prefix=CHECK-LOAD +; RUN: not opt -disable-output -passes=dxil-translate-metadata %t/vertex.ll 2>&1 | FileCheck %s --check-prefix=CHECK-VERTEX + +; Each case is a complete module with one invalid SV_SampleIndex declaration. + +;--- signed.ll +; CHECK-SIGNED: SV_SampleIndex requires a scalar 16- or 32-bit unsigned integer +target triple = "dxil-pc-shadermodel6.0-pixel" +define void @main() #0 { ret void } +attributes #0 = { "hlsl.shader"="pixel" } +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, null} +!1 = !{!2} +!2 = !{i32 0, !"SV_SampleIndex", i32 4, i32 12, !3, i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!3 = !{i32 0} + +;--- cols.ll +; CHECK-COLS: SV_SampleIndex requires a scalar 16- or 32-bit unsigned integer +target triple = "dxil-pc-shadermodel6.0-pixel" +define void @main() #0 { ret void } +attributes #0 = { "hlsl.shader"="pixel" } +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, null} +!1 = !{!2} +!2 = !{i32 0, !"SV_SampleIndex", i32 5, i32 12, !3, i32 1, i32 1, i8 2, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!3 = !{i32 0} + +;--- index.ll +; CHECK-INDEX: SV_SampleIndex requires semantic index 0 +target triple = "dxil-pc-shadermodel6.0-pixel" +define void @main() #0 { ret void } +attributes #0 = { "hlsl.shader"="pixel" } +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, null} +!1 = !{!2} +!2 = !{i32 0, !"SV_SampleIndex", i32 5, i32 12, !3, i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!3 = !{i32 1} + +;--- allocated.ll +; CHECK-ALLOCATED: SV_SampleIndex must not have an allocated location +target triple = "dxil-pc-shadermodel6.0-pixel" +define void @main() #0 { ret void } +attributes #0 = { "hlsl.shader"="pixel" } +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, null} +!1 = !{!2} +!2 = !{i32 0, !"SV_SampleIndex", i32 5, i32 12, !3, i32 1, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} +!3 = !{i32 0} + +;--- load.ll +; CHECK-LOAD: input signature element 0 ('SV_SampleIndex'): must not be read with dx.op.load/store +target triple = "dxil-pc-shadermodel6.0-pixel" +define void @main() #0 { + %si = call i32 @llvm.dx.load.input.i32(i32 0, i32 0, i8 0, i32 poison) + ret void +} +attributes #0 = { "hlsl.shader"="pixel" } +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, null} +!1 = !{!2} +!2 = !{i32 0, !"SV_SampleIndex", i32 5, i32 12, !3, i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!3 = !{i32 0} + +;--- vertex.ll +; CHECK-VERTEX: invalid or unsupported semantic for this stage and signature category +target triple = "dxil-pc-shadermodel6.0-vertex" +define void @main() #0 { ret void } +attributes #0 = { "hlsl.shader"="vertex" } +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, null} +!1 = !{!2} +!2 = !{i32 0, !"SV_SampleIndex", i32 5, i32 12, !3, i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!3 = !{i32 0} diff --git a/llvm/test/CodeGen/DirectX/ShaderFlags/unpacked-low-precision.ll b/llvm/test/CodeGen/DirectX/ShaderFlags/unpacked-low-precision.ll new file mode 100644 index 00000000000000..3e7eebefbb40bb --- /dev/null +++ b/llvm/test/CodeGen/DirectX/ShaderFlags/unpacked-low-precision.ll @@ -0,0 +1,40 @@ +; RUN: opt -disable-output -passes=print-dx-shader-flags %s 2>&1 | FileCheck %s + +; Unallocated (NotPacked) elements do not occupy signature registers, so only +; the instructions that read them contribute type requirements, as in DXC. A +; 16-bit SV_SampleIndex is read with the 32-bit dx.op.sampleIndex, and an +; unused 16-bit SV_Barycentrics has no loadInput. Allocated 16-bit signature +; elements still require low-precision support. + +target triple = "dxil-pc-shadermodel6.2-library" + +; CHECK: ; Shader Flags Value: 0x00400020 +; CHECK: Function sample_index : 0x00000000 +; CHECK: Function barycentrics : 0x00400000 +; CHECK: Function arbitrary : 0x00000020 +define void @sample_index() #0 { +entry: + %0 = call i32 @llvm.dx.sample.index() + ret void +} +define void @barycentrics() #0 { +entry: + ret void +} +define void @arbitrary() #0 { +entry: + ret void +} +attributes #0 = { "hlsl.shader"="pixel" } + +!dx.semantic.signatures = !{!0, !4, !7} +!0 = !{ptr @sample_index, !1, null} +!1 = !{!2} +!2 = !{i32 0, !"SV_SampleIndex", i32 3, i32 12, !3, i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!3 = !{i32 0} +!4 = !{ptr @barycentrics, !5, null} +!5 = !{!6} +!6 = !{i32 0, !"SV_Barycentrics", i32 8, i32 28, !3, i32 2, i32 1, i8 3, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!7 = !{ptr @arbitrary, !8, null} +!8 = !{!9} +!9 = !{i32 0, !"A", i32 3, i32 0, !3, i32 1, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} diff --git a/llvm/test/CodeGen/DirectX/sample_index.ll b/llvm/test/CodeGen/DirectX/sample_index.ll new file mode 100644 index 00000000000000..0119d427ff3878 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/sample_index.ll @@ -0,0 +1,28 @@ +; RUN: split-file %s %t +; RUN: opt -S -dxil-op-lower %t/pixel.ll | FileCheck %s +; RUN: not opt -S -dxil-op-lower %t/vertex.ll 2>&1 | FileCheck %s --check-prefix=STAGE + +; CHECK-LABEL: define i32 @main( +; CHECK: %[[SI:.*]] = call i32 @dx.op.sampleIndex.i32(i32 90) +; CHECK: ret i32 %[[SI]] +; CHECK: declare i32 @dx.op.sampleIndex.i32(i32) #[[#ATTR:]] +; CHECK: attributes #[[#ATTR]] = { nounwind memory(none) } + +; SampleIndex is only available to pixel shaders. +; STAGE: Cannot create SampleIndex operation: Invalid stage + +;--- pixel.ll +target triple = "dxil-pc-shadermodel6.0-pixel" +define i32 @main() { +entry: + %0 = call i32 @llvm.dx.sample.index() + ret i32 %0 +} + +;--- vertex.ll +target triple = "dxil-pc-shadermodel6.0-vertex" +define i32 @main() { +entry: + %0 = call i32 @llvm.dx.sample.index() + ret i32 %0 +} diff --git a/llvm/test/CodeGen/SPIRV/semantics/sample-index.ps.ll b/llvm/test/CodeGen/SPIRV/semantics/sample-index.ps.ll new file mode 100644 index 00000000000000..ecc6bbce22c0df --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/semantics/sample-index.ps.ll @@ -0,0 +1,32 @@ +; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan1.3-pixel %s -o - | FileCheck %s --implicit-check-not=OpExtension +; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan1.3-pixel %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %} + +; SV_SampleIndex is lowered to a flat load of the SampleId built-in, which +; requires SampleRateShading. + +; CHECK-DAG: OpCapability SampleRateShading +; CHECK-DAG: OpDecorate %[[#SAMPLE:]] BuiltIn SampleId +; CHECK-DAG: OpDecorate %[[#SAMPLE]] Flat +; CHECK-DAG: %[[#INT:]] = OpTypeInt 32 0 +; CHECK-DAG: %[[#PTR:]] = OpTypePointer Input %[[#INT]] +; CHECK-DAG: %[[#SAMPLE]] = OpVariable %[[#PTR]] Input + +@SV_SampleIndex = external hidden thread_local addrspace(7) externally_initialized constant i32, !spirv.Decorations !0 +@SV_Target0 = external hidden thread_local addrspace(8) global float, !spirv.Decorations !3 + +define void @main() #0 { +entry: +; CHECK: OpLoad %[[#INT]] %[[#SAMPLE]] + %0 = load i32, ptr addrspace(7) @SV_SampleIndex, align 4 + %1 = uitofp i32 %0 to float + store float %1, ptr addrspace(8) @SV_Target0, align 4 + ret void +} + +attributes #0 = { "hlsl.shader"="pixel" } + +!0 = !{!1, !2} +!1 = !{i32 11, i32 18} +!2 = !{i32 14} +!3 = !{!4} +!4 = !{i32 30, i32 0} diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp index 080a675c6e763a..94af5531592c95 100644 --- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp +++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp @@ -258,6 +258,21 @@ TEST_F(HLSLSemanticSignaturePackingTest, BarycentricsDoNotReserveRegisters) { {Unallocated, {/*Row=*/0, /*Col=*/0}}); } +TEST_F(HLSLSemanticSignaturePackingTest, SampleIndexDoesNotReserveRegisters) { + // SV_SampleIndex stays in the signature but is read with a dedicated + // intrinsic and never occupies a register. + TestConfig Config( + Triple::Pixel, IOType::In, + {{dxbc::PSV::SemanticKind::SampleIndex, /*Rows=*/1, /*Cols=*/1, + dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Constant}, + {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/32, /*Cols=*/4, + dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}}); + for (PackingMethod Method : + {PackingMethod::PrefixStable, PackingMethod::Optimized}) + verifyPacking(Method, Config, /*ExpectedRows=*/32, + {Unallocated, {/*Row=*/0, /*Col=*/0}}); +} + TEST_F(HLSLSemanticSignaturePackingTest, StacksInDeclarationOrder) { // Elements are assigned whole rows in declaration order, regardless of their // semantic interpretation. diff --git a/llvm/unittests/Frontend/HLSLSemanticStagesTest.cpp b/llvm/unittests/Frontend/HLSLSemanticStagesTest.cpp index cd4a57abb68a6a..399e6630a189e1 100644 --- a/llvm/unittests/Frontend/HLSLSemanticStagesTest.cpp +++ b/llvm/unittests/Frontend/HLSLSemanticStagesTest.cpp @@ -76,7 +76,8 @@ constexpr SemanticKind SupportedKinds[] = {SemanticKind::Arbitrary, SemanticKind::ShadingRate, SemanticKind::Barycentrics, SemanticKind::StartVertexLocation, - SemanticKind::StartInstanceLocation}; + SemanticKind::StartInstanceLocation, + SemanticKind::SampleIndex}; // Ensure all stages return a valid SemanticInterpretation TEST(HLSLSemanticStagesTest, StagesAreValid) { @@ -191,4 +192,14 @@ TEST(HLSLSemanticStagesTest, StartInstanceLocationSignaturePoints) { expectOnlySignaturePoints(SemanticKind::StartInstanceLocation, Valid); } +// SV_SampleIndex is read with a dedicated operation, but is still added to the +// pixel input signature for compatibility. Valid only on pixel inputs, where it +// is in the signature but not packed. +TEST(HLSLSemanticStagesTest, SampleIndexSignaturePoints) { + const SignaturePoint Valid[] = { + {Triple::Pixel, IOType::In, SemanticInterpretation::NotPacked}, + }; + expectOnlySignaturePoints(SemanticKind::SampleIndex, Valid); +} + } // namespace >From df5731068e66f7bc6d25a83d4a26a7bf09bfec74 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Wed, 28 Oct 2026 17:30:03 +0000 Subject: [PATCH 2/7] review: simplify the SV_SampleIndex scalar type check --- clang/lib/Sema/SemaHLSL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 2ce99418b2f8e1..785ffea50d875e 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -1438,7 +1438,7 @@ void SemaHLSL::diagnoseSemanticType(const Decl *D, << /* 16 or 32 bit floating-point */ 2 << DeclaredTy; return; case SemanticKind::SampleIndex: - if (DeclaredTy != T || !T->isScalarType() || !isIntUpTo32Element(Ctx, T)) + if (!DeclaredTy->isScalarType() || !isIntUpTo32Element(Ctx, DeclaredTy)) Diag(A->getLoc(), diag::err_hlsl_semantic_invalid_type) << A->getAttrName() << /* scalar */ 0 << 1 << /* 16 or 32 bit integer */ 0 << DeclaredTy; >From 8f51c419f8d964bebe45443db3e6894eb9a2735b Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Wed, 28 Oct 2026 17:30:03 +0000 Subject: [PATCH 3/7] review: drop the unneeded SV_Position from the SV_SampleIndex struct test --- clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl b/clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl index 520893b5c4d9e1..63bb531c15e767 100644 --- a/clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl +++ b/clang/test/SemaHLSL/Semantics/sampleindex.ps.hlsl @@ -20,7 +20,6 @@ void uint16(uint16_t s : SV_SampleIndex) {} void int16(int16_t s : SV_SampleIndex) {} struct Input { - float4 p : SV_Position; uint s : SV_SampleIndex; }; [shader("pixel")] >From c27905f1e7b5af3a2c7e3826d8a9c475f5104d0e Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Wed, 28 Oct 2026 17:30:03 +0000 Subject: [PATCH 4/7] review: remove the DXC-specific NotPacked comment --- llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h index 971b493ab7ee36..90b7241d4d21d4 100644 --- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h +++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h @@ -110,8 +110,6 @@ LLVM_ABI dxbc::PSV::InterpolationMode normalizeInterpolationMode( enum class SemanticInterpretation { Invalid, NotAllocated, - // In the signature, but not packed. This includes DXC's Shadow - // interpretation, whose value is read with a dedicated intrinsic. NotPacked, Arbitrary, SV, >From 90e84055c60da2acb4a762b18397893fcf5c56f7 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Wed, 28 Oct 2026 21:30:57 +0000 Subject: [PATCH 5/7] review: split the SV_SampleIndex container test into packing and container part tests --- .../SemanticSignatures-sample-index.ll | 63 ++++++++----------- .../semantic-signatures-sample-index.ll | 37 +++++++++++ 2 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index.ll diff --git a/llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll b/llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll index 72a23895e039ff..f91673596445ee 100644 --- a/llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll +++ b/llvm/test/CodeGen/DirectX/ContainerData/SemanticSignatures-sample-index.ll @@ -1,12 +1,10 @@ -; RUN: opt -disable-output -passes='print<dxil-signature>' %s 2>&1 | FileCheck %s --check-prefix=ANALYSIS ; RUN: llc -filetype=obj %s -o %t.dxbc -; RUN: obj2yaml %t.dxbc | FileCheck %s --check-prefix=PARTS -; RUN: llvm-objcopy --dump-section=DXIL=%t.bc %t.dxbc -; RUN: llvm-dis %t.bc -o - | FileCheck %s --check-prefixes=MD,DXIL +; RUN: obj2yaml %t.dxbc | FileCheck %s -; SV_SampleIndex is an unpacked element: it keeps a signature ID, but is read with -; dx.op.sampleIndex, does not consume a row and marks the pixel shader as -; sample-frequency. +; SV_SampleIndex is written to the ISG1 and PSV0 parts as an unallocated input +; element, and marks the pixel shader as sample-frequency. Packing is covered by +; Metadata/semantic-signatures-sample-index.ll and the dx.op.sampleIndex +; lowering by sample_index.ll. target triple = "dxil-pc-shadermodel6.0-pixel" define void @main() #0 { @@ -30,33 +28,24 @@ attributes #0 = { "hlsl.shader"="pixel" } !6 = !{i32 0, !"SV_Target", i32 9, i32 16, !7, i32 0, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} !7 = !{i32 0} -; ANALYSIS: Inputs: 2 elements, 1 vectors -; ANALYSIS-NEXT: 0: A rows=1 cols=1 at 0:0 usage=1 dynamic=0 -; ANALYSIS-NEXT: 1: SV_SampleIndex rows=1 cols=1 at 4294967295:255 usage=0 dynamic=0 -; ANALYSIS-NEXT: Outputs: 1 elements, 1 vectors - -; DXIL: call float @dx.op.loadInput.f32(i32 4, i32 0, i32 0, i8 0, i32 {{.*}}) -; DXIL: call i32 @dx.op.sampleIndex.i32(i32 90) -; MD-DAG: !{i32 1, !"SV_SampleIndex", i8 5, i8 12, !{{[0-9]+}}, i8 1, i32 1, i8 1, i32 -1, i8 -1, null} - -; PARTS: Name: ISG1 -; PARTS: Name: SV_SampleIndex -; PARTS-NEXT: Index: 0 -; PARTS-NEXT: SystemValue: SampleIndex -; PARTS-NEXT: CompType: UInt32 -; PARTS-NEXT: Register: 4294967295 -; PARTS-NEXT: Mask: 1 -; PARTS-NEXT: ExclusiveMask: 0 -; PARTS: Name: PSV0 -; PARTS: SampleFrequency: 1 -; PARTS: SigInputVectors: 1 -; PARTS: SigInputElements: -; PARTS: Kind: Arbitrary -; PARTS: Indices: [ 0 ] -; PARTS-NEXT: StartRow: 0 -; PARTS-NEXT: Cols: 1 -; PARTS-NEXT: StartCol: 0 -; PARTS-NEXT: Allocated: false -; PARTS-NEXT: Kind: SampleIndex -; PARTS-NEXT: ComponentType: UInt32 -; PARTS-NEXT: Interpolation: Constant +; CHECK: Name: ISG1 +; CHECK: Name: SV_SampleIndex +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: SystemValue: SampleIndex +; CHECK-NEXT: CompType: UInt32 +; CHECK-NEXT: Register: 4294967295 +; CHECK-NEXT: Mask: 1 +; CHECK-NEXT: ExclusiveMask: 0 +; CHECK: Name: PSV0 +; CHECK: SampleFrequency: 1 +; CHECK: SigInputVectors: 1 +; CHECK: SigInputElements: +; CHECK: Kind: Arbitrary +; CHECK: Indices: [ 0 ] +; CHECK-NEXT: StartRow: 0 +; CHECK-NEXT: Cols: 1 +; CHECK-NEXT: StartCol: 0 +; CHECK-NEXT: Allocated: false +; CHECK-NEXT: Kind: SampleIndex +; CHECK-NEXT: ComponentType: UInt32 +; CHECK-NEXT: Interpolation: Constant diff --git a/llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index.ll b/llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index.ll new file mode 100644 index 00000000000000..f67eefd656662e --- /dev/null +++ b/llvm/test/CodeGen/DirectX/Metadata/semantic-signatures-sample-index.ll @@ -0,0 +1,37 @@ +; RUN: opt -disable-output -passes='print<dxil-signature>' %s 2>&1 | FileCheck %s --check-prefix=ANALYSIS +; RUN: opt -S -dxil-translate-metadata %s | FileCheck %s --check-prefix=MD + +; SV_SampleIndex is an unpacked input element: it keeps signature ID 1, but is +; not allocated a row or column, so the arbitrary input A is still packed at +; row 0. +target triple = "dxil-pc-shadermodel6.0-pixel" + +define void @main() #0 { + %p = call float @llvm.dx.load.input.f32(i32 0, i32 0, i8 0, i32 poison) + %si = call i32 @llvm.dx.sample.index() + %f = uitofp i32 %si to float + %result = fadd float %p, %f + call void @llvm.dx.store.output.f32(i32 0, i32 0, i8 0, float %result) + ret void +} +attributes #0 = { "hlsl.shader"="pixel" } + +!dx.valver = !{!10} +!10 = !{i32 1, i32 8} +!dx.semantic.signatures = !{!0} +!0 = !{ptr @main, !1, !2} +!1 = !{!3, !4} +!2 = !{!6} +!3 = !{i32 0, !"A", i32 9, i32 0, !7, i32 2, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} +!4 = !{i32 1, !"SV_SampleIndex", i32 5, i32 12, !7, i32 1, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +!6 = !{i32 0, !"SV_Target", i32 9, i32 16, !7, i32 0, i32 1, i8 1, i32 0, i8 0, i8 0, i8 0, i32 0} +!7 = !{i32 0} + +; ANALYSIS: Inputs: 2 elements, 1 vectors +; ANALYSIS-NEXT: 0: A rows=1 cols=1 at 0:0 usage=1 dynamic=0 +; ANALYSIS-NEXT: 1: SV_SampleIndex rows=1 cols=1 at 4294967295:255 usage=0 dynamic=0 +; ANALYSIS-NEXT: Outputs: 1 elements, 1 vectors + +; The DXIL signature element has a start row of -1 and start column of -1. +; MD-DAG: !{i32 0, !"A", i8 9, i8 0, !{{[0-9]+}}, i8 2, i32 1, i8 1, i32 0, i8 0, !{{[0-9]+}}} +; MD-DAG: !{i32 1, !"SV_SampleIndex", i8 5, i8 12, !{{[0-9]+}}, i8 1, i32 1, i8 1, i32 -1, i8 -1, null} >From d7989f414ed971547a48627e6a9f7035c877b39f Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Wed, 28 Oct 2026 21:30:58 +0000 Subject: [PATCH 6/7] review: move the SampleIndex stage error to sample_index_error.ll --- llvm/test/CodeGen/DirectX/sample_index.ll | 23 ++++--------------- .../CodeGen/DirectX/sample_index_error.ll | 11 +++++++++ 2 files changed, 16 insertions(+), 18 deletions(-) create mode 100644 llvm/test/CodeGen/DirectX/sample_index_error.ll diff --git a/llvm/test/CodeGen/DirectX/sample_index.ll b/llvm/test/CodeGen/DirectX/sample_index.ll index 0119d427ff3878..5265e00f17b372 100644 --- a/llvm/test/CodeGen/DirectX/sample_index.ll +++ b/llvm/test/CodeGen/DirectX/sample_index.ll @@ -1,28 +1,15 @@ -; RUN: split-file %s %t -; RUN: opt -S -dxil-op-lower %t/pixel.ll | FileCheck %s -; RUN: not opt -S -dxil-op-lower %t/vertex.ll 2>&1 | FileCheck %s --check-prefix=STAGE +; RUN: opt -S -dxil-op-lower %s | FileCheck %s + +target triple = "dxil-pc-shadermodel6.0-pixel" ; CHECK-LABEL: define i32 @main( ; CHECK: %[[SI:.*]] = call i32 @dx.op.sampleIndex.i32(i32 90) ; CHECK: ret i32 %[[SI]] -; CHECK: declare i32 @dx.op.sampleIndex.i32(i32) #[[#ATTR:]] -; CHECK: attributes #[[#ATTR]] = { nounwind memory(none) } - -; SampleIndex is only available to pixel shaders. -; STAGE: Cannot create SampleIndex operation: Invalid stage - -;--- pixel.ll -target triple = "dxil-pc-shadermodel6.0-pixel" define i32 @main() { entry: %0 = call i32 @llvm.dx.sample.index() ret i32 %0 } -;--- vertex.ll -target triple = "dxil-pc-shadermodel6.0-vertex" -define i32 @main() { -entry: - %0 = call i32 @llvm.dx.sample.index() - ret i32 %0 -} +; CHECK: declare i32 @dx.op.sampleIndex.i32(i32) #[[#ATTR:]] +; CHECK: attributes #[[#ATTR]] = { nounwind memory(none) } diff --git a/llvm/test/CodeGen/DirectX/sample_index_error.ll b/llvm/test/CodeGen/DirectX/sample_index_error.ll new file mode 100644 index 00000000000000..eb6707480a8467 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/sample_index_error.ll @@ -0,0 +1,11 @@ +; RUN: not opt -S -dxil-op-lower %s 2>&1 | FileCheck %s + +; SampleIndex is only available to pixel shaders. +; CHECK: Cannot create SampleIndex operation: Invalid stage + +target triple = "dxil-pc-shadermodel6.0-vertex" +define i32 @main() { +entry: + %0 = call i32 @llvm.dx.sample.index() + ret i32 %0 +} >From 58487139716924f47fb2ef4383711b2028df161d Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Wed, 28 Oct 2026 21:30:58 +0000 Subject: [PATCH 7/7] review: document the integer type assumptions when loading SV_SampleIndex --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index fac0f7b850f71a..0438ddc5de448a 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1183,10 +1183,11 @@ static llvm::Value *createSPIRVInputLoad( IRBuilder<> &B, llvm::Module &M, llvm::Type *Ty, const Twine &Name, llvm::hlsl::SPIRVInterfaceDecoration Decoration, unsigned Operand, llvm::hlsl::InterpolationMode InterpMode) { - llvm::Type *LoadTy = Ty; - if (Decoration == llvm::hlsl::SPIRVInterfaceDecoration::BuiltIn && - Ty->isIntegerTy() && Ty->getIntegerBitWidth() < 32) - LoadTy = B.getInt32Ty(); + // SPIR-V integer built-ins are 32-bit, so a narrower integer declaration + // loads the 32-bit built-in and truncates it. Other types are loaded as is. + bool Truncate = Decoration == llvm::hlsl::SPIRVInterfaceDecoration::BuiltIn && + Ty->isIntegerTy() && Ty->getIntegerBitWidth() < 32; + llvm::Type *LoadTy = Truncate ? B.getInt32Ty() : Ty; auto *GV = new llvm::GlobalVariable( M, LoadTy, /* isConstant= */ true, llvm::GlobalValue::ExternalLinkage, /* Initializer= */ nullptr, Name, /* insertBefore= */ nullptr, @@ -1195,7 +1196,7 @@ static llvm::Value *createSPIRVInputLoad( GV->setVisibility(llvm::GlobalValue::HiddenVisibility); addSPIRVInterfaceDecorations(GV, Decoration, Operand, InterpMode); llvm::Value *Value = B.CreateLoad(LoadTy, GV); - return LoadTy == Ty ? Value : B.CreateTrunc(Value, Ty); + return Truncate ? B.CreateTrunc(Value, Ty) : Value; } llvm::Value *CGHLSLRuntime::emitSPIRVUserSemanticLoad( @@ -1713,6 +1714,9 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad( llvm::hlsl::SPIRVInterfaceDecoration::BuiltIn, /* SampleId */ 18, InterpMode); if (CGM.getTarget().getTriple().isDXIL()) { + assert(Type->isIntegerTy() && Type->getIntegerBitWidth() <= 32 && + "SV_SampleIndex must be a 16 or 32 bit scalar integer, as " + "diagnosed by Sema"); // The sample index is read with a dedicated operation, but an unpacked // element is still added to the input signature for compatibility. SemanticShape Shape = _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
