Skip to content

[DirectX] Add static sampler support to root signature #143422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: users/joaosaffran/142492
Choose a base branch
from

Conversation

joaosaffran
Copy link
Contributor

@joaosaffran joaosaffran commented Jun 9, 2025

Implements static samplers parsing from root signature metadata representation. This is required to support Root Signatures in HLSL.
Closes: #126641

@joaosaffran joaosaffran requested a review from inbelic June 9, 2025 18:57
@llvmbot llvmbot added mc Machine (object) code backend:DirectX labels Jun 9, 2025
@joaosaffran joaosaffran requested a review from bogner June 9, 2025 18:57
@llvmbot
Copy link
Member

llvmbot commented Jun 9, 2025

@llvm/pr-subscribers-backend-directx

Author: None (joaosaffran)

Changes

This patch introduces the following changes to the DirectX container implementation:

  1. Add static sampler support to root signatures:
    • Implement parsing and validation of static sampler parameters
    • Add verification for sampler attributes (filter, addressing, LOD, etc.)
    • Update root signature writing to handle static samplers
    • Add test coverage for static sampler validation

Testing:

  • Added new test files for static sampler validation
  • Added tests for all possible invalid sampler parameters
  • Existing root signature tests continue to pass

Patch is 32.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143422.diff

17 Files Affected:

  • (modified) llvm/lib/MC/DXContainerRootSignature.cpp (+24-17)
  • (modified) llvm/lib/Target/DirectX/DXILRootSignature.cpp (+253)
  • (modified) llvm/lib/Target/DirectX/DXILRootSignature.h (+1)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-BorderColor.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-ComparisonFunc.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-Filter.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MaxAnisotropy.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MaxLod.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MinLod.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MinLopBias.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-RegisterSpace.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-ShaderRegister.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-ShaderVisibility.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers.ll (+42)
diff --git a/llvm/lib/MC/DXContainerRootSignature.cpp b/llvm/lib/MC/DXContainerRootSignature.cpp
index 6c71823a51f85..77d648a739b87 100644
--- a/llvm/lib/MC/DXContainerRootSignature.cpp
+++ b/llvm/lib/MC/DXContainerRootSignature.cpp
@@ -71,12 +71,16 @@ void RootSignatureDesc::write(raw_ostream &OS) const {
   BOS.reserveExtraSpace(getSize());
 
   const uint32_t NumParameters = ParametersContainer.size();
-
+  const uint32_t NumSamplers = StaticSamplers.size();
   support::endian::write(BOS, Version, llvm::endianness::little);
   support::endian::write(BOS, NumParameters, llvm::endianness::little);
   support::endian::write(BOS, RootParameterOffset, llvm::endianness::little);
-  support::endian::write(BOS, NumStaticSamplers, llvm::endianness::little);
-  support::endian::write(BOS, StaticSamplersOffset, llvm::endianness::little);
+  support::endian::write(BOS, NumSamplers, llvm::endianness::little);
+  uint32_t SSO = StaticSamplersOffset;
+  if (NumSamplers > 0)
+    SSO = writePlaceholder(BOS);
+  else
+    support::endian::write(BOS, SSO, llvm::endianness::little);
   support::endian::write(BOS, Flags, llvm::endianness::little);
 
   SmallVector<uint32_t> ParamsOffsets;
@@ -142,20 +146,23 @@ void RootSignatureDesc::write(raw_ostream &OS) const {
     }
     }
   }
-  for (const auto &S : StaticSamplers) {
-    support::endian::write(BOS, S.Filter, llvm::endianness::little);
-    support::endian::write(BOS, S.AddressU, llvm::endianness::little);
-    support::endian::write(BOS, S.AddressV, llvm::endianness::little);
-    support::endian::write(BOS, S.AddressW, llvm::endianness::little);
-    support::endian::write(BOS, S.MipLODBias, llvm::endianness::little);
-    support::endian::write(BOS, S.MaxAnisotropy, llvm::endianness::little);
-    support::endian::write(BOS, S.ComparisonFunc, llvm::endianness::little);
-    support::endian::write(BOS, S.BorderColor, llvm::endianness::little);
-    support::endian::write(BOS, S.MinLOD, llvm::endianness::little);
-    support::endian::write(BOS, S.MaxLOD, llvm::endianness::little);
-    support::endian::write(BOS, S.ShaderRegister, llvm::endianness::little);
-    support::endian::write(BOS, S.RegisterSpace, llvm::endianness::little);
-    support::endian::write(BOS, S.ShaderVisibility, llvm::endianness::little);
+  if (NumSamplers > 0) {
+    rewriteOffsetToCurrentByte(BOS, SSO);
+    for (const auto &S : StaticSamplers) {
+      support::endian::write(BOS, S.Filter, llvm::endianness::little);
+      support::endian::write(BOS, S.AddressU, llvm::endianness::little);
+      support::endian::write(BOS, S.AddressV, llvm::endianness::little);
+      support::endian::write(BOS, S.AddressW, llvm::endianness::little);
+      support::endian::write(BOS, S.MipLODBias, llvm::endianness::little);
+      support::endian::write(BOS, S.MaxAnisotropy, llvm::endianness::little);
+      support::endian::write(BOS, S.ComparisonFunc, llvm::endianness::little);
+      support::endian::write(BOS, S.BorderColor, llvm::endianness::little);
+      support::endian::write(BOS, S.MinLOD, llvm::endianness::little);
+      support::endian::write(BOS, S.MaxLOD, llvm::endianness::little);
+      support::endian::write(BOS, S.ShaderRegister, llvm::endianness::little);
+      support::endian::write(BOS, S.RegisterSpace, llvm::endianness::little);
+      support::endian::write(BOS, S.ShaderVisibility, llvm::endianness::little);
+    }
   }
   assert(Storage.size() == getSize());
   OS.write(Storage.data(), Storage.size());
diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.cpp b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
index b38ab516a3bb5..0cd3dc72505ef 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.cpp
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
@@ -27,6 +27,7 @@
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
+#include <cmath>
 #include <cstdint>
 #include <optional>
 #include <utility>
@@ -55,6 +56,13 @@ static std::optional<uint32_t> extractMdIntValue(MDNode *Node,
   return std::nullopt;
 }
 
+static std::optional<APFloat> extractMdFloatValue(MDNode *Node,
+                                                  unsigned int OpId) {
+  if (auto *CI = mdconst::dyn_extract<ConstantFP>(Node->getOperand(OpId).get()))
+    return CI->getValue();
+  return std::nullopt;
+}
+
 static std::optional<StringRef> extractMdStringValue(MDNode *Node,
                                                      unsigned int OpId) {
   MDString *NodeText = dyn_cast<MDString>(Node->getOperand(OpId));
@@ -258,6 +266,81 @@ static bool parseDescriptorTable(LLVMContext *Ctx,
   return false;
 }
 
+static bool parseStaticSampler(LLVMContext *Ctx, mcdxbc::RootSignatureDesc &RSD,
+                               MDNode *StaticSamplerNode) {
+  if (StaticSamplerNode->getNumOperands() != 14)
+    return reportError(Ctx, "Invalid format for Static Sampler");
+
+  dxbc::RTS0::v1::StaticSampler Sampler;
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 1))
+    Sampler.Filter = *Val;
+  else
+    return reportError(Ctx, "Invalid value for Filter");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 2))
+    Sampler.AddressU = *Val;
+  else
+    return reportError(Ctx, "Invalid value for AddressU");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 3))
+    Sampler.AddressV = *Val;
+  else
+    return reportError(Ctx, "Invalid value for AddressV");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 4))
+    Sampler.AddressW = *Val;
+  else
+    return reportError(Ctx, "Invalid value for AddressW");
+
+  if (std::optional<APFloat> Val = extractMdFloatValue(StaticSamplerNode, 5))
+    Sampler.MipLODBias = Val->convertToFloat();
+  else
+    return reportError(Ctx, "Invalid value for MipLODBias");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 6))
+    Sampler.MaxAnisotropy = *Val;
+  else
+    return reportError(Ctx, "Invalid value for MaxAnisotropy");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 7))
+    Sampler.ComparisonFunc = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ComparisonFunc ");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 8))
+    Sampler.BorderColor = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ComparisonFunc ");
+
+  if (std::optional<APFloat> Val = extractMdFloatValue(StaticSamplerNode, 9))
+    Sampler.MinLOD = Val->convertToFloat();
+  else
+    return reportError(Ctx, "Invalid value for MinLOD");
+
+  if (std::optional<APFloat> Val = extractMdFloatValue(StaticSamplerNode, 10))
+    Sampler.MaxLOD = Val->convertToFloat();
+  else
+    return reportError(Ctx, "Invalid value for MaxLOD");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 11))
+    Sampler.ShaderRegister = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ShaderRegister");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 12))
+    Sampler.RegisterSpace = *Val;
+  else
+    return reportError(Ctx, "Invalid value for RegisterSpace");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 13))
+    Sampler.ShaderVisibility = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ShaderVisibility");
+
+  RSD.StaticSamplers.push_back(Sampler);
+  return false;
+}
+
 static bool parseRootSignatureElement(LLVMContext *Ctx,
                                       mcdxbc::RootSignatureDesc &RSD,
                                       MDNode *Element) {
@@ -273,6 +356,7 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
           .Case("RootSRV", RootSignatureElementKind::SRV)
           .Case("RootUAV", RootSignatureElementKind::UAV)
           .Case("DescriptorTable", RootSignatureElementKind::DescriptorTable)
+          .Case("StaticSampler", RootSignatureElementKind::StaticSamplers)
           .Default(RootSignatureElementKind::Error);
 
   switch (ElementKind) {
@@ -287,6 +371,8 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
     return parseRootDescriptors(Ctx, RSD, Element, ElementKind);
   case RootSignatureElementKind::DescriptorTable:
     return parseDescriptorTable(Ctx, RSD, Element);
+  case RootSignatureElementKind::StaticSamplers:
+    return parseStaticSampler(Ctx, RSD, Element);
   case RootSignatureElementKind::Error:
     return reportError(Ctx, "Invalid Root Signature Element: " + *ElementText);
   }
@@ -347,6 +433,7 @@ static bool isFlagSet(uint32_t Flags, FlagTypes... FlagsToCheck) {
 
 static bool verifyDescriptorRangeFlag(uint32_t Version, uint32_t Type,
                                       uint32_t Flags) {
+
   bool IsSampler =
       (Type == llvm::to_underlying(dxbc::DescriptorRangeType::Sampler));
 
@@ -399,6 +486,127 @@ static bool verifyDescriptorRangeFlag(uint32_t Version, uint32_t Type,
   return false;
 }
 
+static bool verifySamplerFilter(uint32_t Filter) {
+  switch (Filter) {
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::ANISOTROPIC):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::COMPARISON_ANISOTROPIC):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MINIMUM_ANISOTROPIC):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MAXIMUM_ANISOTROPIC):
+    return true;
+  }
+  return false;
+}
+
+// Values allowed here:
+// https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_texture_address_mode#syntax
+static bool verifyAddress(uint32_t Address) {
+  switch (Address) {
+  case llvm::to_underlying(dxbc::TextureAddressMode::Border):
+  case llvm::to_underlying(dxbc::TextureAddressMode::Clamp):
+  case llvm::to_underlying(dxbc::TextureAddressMode::Mirror):
+  case llvm::to_underlying(dxbc::TextureAddressMode::MirrorOnce):
+  case llvm::to_underlying(dxbc::TextureAddressMode::Wrap):
+    return true;
+  }
+
+  return false;
+}
+
+static bool verifyMipLODBias(float MipLODBias) {
+  return MipLODBias >= -16.f && MipLODBias <= 16.f;
+}
+
+static bool verifyMaxAnisotropy(uint32_t MaxAnisotropy) {
+  return MaxAnisotropy <= 16u;
+}
+
+static bool verifyComparisonFunc(uint32_t ComparisonFunc) {
+  switch (ComparisonFunc) {
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Never):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Less):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Equal):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::LessEqual):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Greater):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::NotEqual):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::GreaterEqual):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Always):
+    return true;
+  }
+  return false;
+}
+
+static bool verifyBorderColor(uint32_t BorderColor) {
+  switch (BorderColor) {
+  case llvm::to_underlying(dxbc::SamplersBorderColor::TransparentBlack):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueBlack):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueWhite):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueBlackUint):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueWhiteUint):
+    return true;
+  }
+  return false;
+}
+
+static bool verifyLOD(float LOD) { return !std::isnan(LOD); }
+
 static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {
 
   if (!verifyVersion(RSD.Version)) {
@@ -456,6 +664,48 @@ static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {
     }
   }
 
+  for (const dxbc::RTS0::v1::StaticSampler &Sampler : RSD.StaticSamplers) {
+    if (!verifySamplerFilter(Sampler.Filter))
+      return reportValueError(Ctx, "Filter", Sampler.Filter);
+
+    if (!verifyAddress(Sampler.AddressU))
+      return reportValueError(Ctx, "AddressU", Sampler.AddressU);
+
+    if (!verifyAddress(Sampler.AddressV))
+      return reportValueError(Ctx, "AddressV", Sampler.AddressV);
+
+    if (!verifyAddress(Sampler.AddressW))
+      return reportValueError(Ctx, "AddressW", Sampler.AddressW);
+
+    if (!verifyMipLODBias(Sampler.MipLODBias))
+      return reportValueError(Ctx, "MipLODBias", Sampler.MipLODBias);
+
+    if (!verifyMaxAnisotropy(Sampler.MaxAnisotropy))
+      return reportValueError(Ctx, "MaxAnisotropy", Sampler.MaxAnisotropy);
+
+    if (!verifyComparisonFunc(Sampler.ComparisonFunc))
+      return reportValueError(Ctx, "ComparisonFunc", Sampler.ComparisonFunc);
+
+    if (!verifyBorderColor(Sampler.BorderColor))
+      return reportValueError(Ctx, "BorderColor", Sampler.BorderColor);
+
+    if (!verifyLOD(Sampler.MinLOD))
+      return reportValueError(Ctx, "MinLOD", Sampler.MinLOD);
+
+    if (!verifyLOD(Sampler.MaxLOD))
+      return reportValueError(Ctx, "MaxLOD", Sampler.MaxLOD);
+
+    if (!verifyRegisterValue(Sampler.ShaderRegister))
+      return reportValueError(Ctx, "ShaderRegister", Sampler.ShaderRegister);
+
+    if (!verifyRegisterSpace(Sampler.RegisterSpace))
+      return reportValueError(Ctx, "RegisterSpace", Sampler.RegisterSpace);
+
+    if (!dxbc::isValidShaderVisibility(Sampler.ShaderVisibility))
+      return reportValueError(Ctx, "ShaderVisibility",
+                              Sampler.ShaderVisibility);
+  }
+
   return false;
 }
 
@@ -529,6 +779,9 @@ analyzeModule(Module &M) {
     // offset will always equal to the header size.
     RSD.RootParameterOffset = sizeof(dxbc::RTS0::v1::RootSignatureHeader);
 
+    // static sampler offset is calculated when writting dxcontainer.
+    RSD.StaticSamplersOffset = 0u;
+
     if (parse(Ctx, RSD, RootElementListNode) || validate(Ctx, RSD)) {
       return RSDMap;
     }
diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.h b/llvm/lib/Target/DirectX/DXILRootSignature.h
index b45cebc15fd39..be5cc78bc6bdf 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.h
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.h
@@ -32,6 +32,7 @@ enum class RootSignatureElementKind {
   UAV = 4,
   CBV = 5,
   DescriptorTable = 6,
+  StaticSamplers = 7
 };
 class RootSignatureAnalysis : public AnalysisInfoMixin<RootSignatureAnalysis> {
   friend AnalysisInfoMixin<RootSignatureAnalysis>;
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll
new file mode 100644
index 0000000000000..124803e36a450
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll
@@ -0,0 +1,19 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+; CHECK: error: Invalid value for AddressU: 666
+; CHECK-NOT: Root Signature Definitions
+
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"StaticSampler", i32 4, i32 666, i32 3, i32 5, float 0x3FF6CCCCC0000000, i32 9, i32 3, i32 2, float -1.280000e+02, float 1.280000e+02, i32 42, i32 0, i32 0 }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll
new file mode 100644
index 0000000000000..e2c5de1c36a0c
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll
@@ -0,0 +1,19 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+; CHECK: error: Invalid value for AddressV: 666
+; CHECK-NOT: Root Signature Definitions
+
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"StaticSampler", i32 4, i32 2, i32 666, i32 5, float 0x3FF6CCCCC0000000, i32 9, i32 3, i32 2, float -1.280000e+02, float 1.280000e+02, i32 42, i32 0, i32 0 }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll
new file mode 100644
index 0000000000000..08fc8fa9e5093
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll
@@ -0,0 +1,19 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+; CHECK: error: Invalid value for AddressW: 666
+; CHECK-NOT: Root Signature Definitions
+
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"StaticSampler", i32 4, i32 2, i32 3, i32 666, float 0x3FF6CCCCC0000000, i32 9, i32 3, i32 2, float...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jun 9, 2025

@llvm/pr-subscribers-mc

Author: None (joaosaffran)

Changes

This patch introduces the following changes to the DirectX container implementation:

  1. Add static sampler support to root signatures:
    • Implement parsing and validation of static sampler parameters
    • Add verification for sampler attributes (filter, addressing, LOD, etc.)
    • Update root signature writing to handle static samplers
    • Add test coverage for static sampler validation

Testing:

  • Added new test files for static sampler validation
  • Added tests for all possible invalid sampler parameters
  • Existing root signature tests continue to pass

Patch is 32.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143422.diff

17 Files Affected:

  • (modified) llvm/lib/MC/DXContainerRootSignature.cpp (+24-17)
  • (modified) llvm/lib/Target/DirectX/DXILRootSignature.cpp (+253)
  • (modified) llvm/lib/Target/DirectX/DXILRootSignature.h (+1)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-BorderColor.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-ComparisonFunc.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-Filter.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MaxAnisotropy.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MaxLod.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MinLod.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-MinLopBias.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-RegisterSpace.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-ShaderRegister.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-ShaderVisibility.ll (+19)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers.ll (+42)
diff --git a/llvm/lib/MC/DXContainerRootSignature.cpp b/llvm/lib/MC/DXContainerRootSignature.cpp
index 6c71823a51f85..77d648a739b87 100644
--- a/llvm/lib/MC/DXContainerRootSignature.cpp
+++ b/llvm/lib/MC/DXContainerRootSignature.cpp
@@ -71,12 +71,16 @@ void RootSignatureDesc::write(raw_ostream &OS) const {
   BOS.reserveExtraSpace(getSize());
 
   const uint32_t NumParameters = ParametersContainer.size();
-
+  const uint32_t NumSamplers = StaticSamplers.size();
   support::endian::write(BOS, Version, llvm::endianness::little);
   support::endian::write(BOS, NumParameters, llvm::endianness::little);
   support::endian::write(BOS, RootParameterOffset, llvm::endianness::little);
-  support::endian::write(BOS, NumStaticSamplers, llvm::endianness::little);
-  support::endian::write(BOS, StaticSamplersOffset, llvm::endianness::little);
+  support::endian::write(BOS, NumSamplers, llvm::endianness::little);
+  uint32_t SSO = StaticSamplersOffset;
+  if (NumSamplers > 0)
+    SSO = writePlaceholder(BOS);
+  else
+    support::endian::write(BOS, SSO, llvm::endianness::little);
   support::endian::write(BOS, Flags, llvm::endianness::little);
 
   SmallVector<uint32_t> ParamsOffsets;
@@ -142,20 +146,23 @@ void RootSignatureDesc::write(raw_ostream &OS) const {
     }
     }
   }
-  for (const auto &S : StaticSamplers) {
-    support::endian::write(BOS, S.Filter, llvm::endianness::little);
-    support::endian::write(BOS, S.AddressU, llvm::endianness::little);
-    support::endian::write(BOS, S.AddressV, llvm::endianness::little);
-    support::endian::write(BOS, S.AddressW, llvm::endianness::little);
-    support::endian::write(BOS, S.MipLODBias, llvm::endianness::little);
-    support::endian::write(BOS, S.MaxAnisotropy, llvm::endianness::little);
-    support::endian::write(BOS, S.ComparisonFunc, llvm::endianness::little);
-    support::endian::write(BOS, S.BorderColor, llvm::endianness::little);
-    support::endian::write(BOS, S.MinLOD, llvm::endianness::little);
-    support::endian::write(BOS, S.MaxLOD, llvm::endianness::little);
-    support::endian::write(BOS, S.ShaderRegister, llvm::endianness::little);
-    support::endian::write(BOS, S.RegisterSpace, llvm::endianness::little);
-    support::endian::write(BOS, S.ShaderVisibility, llvm::endianness::little);
+  if (NumSamplers > 0) {
+    rewriteOffsetToCurrentByte(BOS, SSO);
+    for (const auto &S : StaticSamplers) {
+      support::endian::write(BOS, S.Filter, llvm::endianness::little);
+      support::endian::write(BOS, S.AddressU, llvm::endianness::little);
+      support::endian::write(BOS, S.AddressV, llvm::endianness::little);
+      support::endian::write(BOS, S.AddressW, llvm::endianness::little);
+      support::endian::write(BOS, S.MipLODBias, llvm::endianness::little);
+      support::endian::write(BOS, S.MaxAnisotropy, llvm::endianness::little);
+      support::endian::write(BOS, S.ComparisonFunc, llvm::endianness::little);
+      support::endian::write(BOS, S.BorderColor, llvm::endianness::little);
+      support::endian::write(BOS, S.MinLOD, llvm::endianness::little);
+      support::endian::write(BOS, S.MaxLOD, llvm::endianness::little);
+      support::endian::write(BOS, S.ShaderRegister, llvm::endianness::little);
+      support::endian::write(BOS, S.RegisterSpace, llvm::endianness::little);
+      support::endian::write(BOS, S.ShaderVisibility, llvm::endianness::little);
+    }
   }
   assert(Storage.size() == getSize());
   OS.write(Storage.data(), Storage.size());
diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.cpp b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
index b38ab516a3bb5..0cd3dc72505ef 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.cpp
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
@@ -27,6 +27,7 @@
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
+#include <cmath>
 #include <cstdint>
 #include <optional>
 #include <utility>
@@ -55,6 +56,13 @@ static std::optional<uint32_t> extractMdIntValue(MDNode *Node,
   return std::nullopt;
 }
 
+static std::optional<APFloat> extractMdFloatValue(MDNode *Node,
+                                                  unsigned int OpId) {
+  if (auto *CI = mdconst::dyn_extract<ConstantFP>(Node->getOperand(OpId).get()))
+    return CI->getValue();
+  return std::nullopt;
+}
+
 static std::optional<StringRef> extractMdStringValue(MDNode *Node,
                                                      unsigned int OpId) {
   MDString *NodeText = dyn_cast<MDString>(Node->getOperand(OpId));
@@ -258,6 +266,81 @@ static bool parseDescriptorTable(LLVMContext *Ctx,
   return false;
 }
 
+static bool parseStaticSampler(LLVMContext *Ctx, mcdxbc::RootSignatureDesc &RSD,
+                               MDNode *StaticSamplerNode) {
+  if (StaticSamplerNode->getNumOperands() != 14)
+    return reportError(Ctx, "Invalid format for Static Sampler");
+
+  dxbc::RTS0::v1::StaticSampler Sampler;
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 1))
+    Sampler.Filter = *Val;
+  else
+    return reportError(Ctx, "Invalid value for Filter");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 2))
+    Sampler.AddressU = *Val;
+  else
+    return reportError(Ctx, "Invalid value for AddressU");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 3))
+    Sampler.AddressV = *Val;
+  else
+    return reportError(Ctx, "Invalid value for AddressV");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 4))
+    Sampler.AddressW = *Val;
+  else
+    return reportError(Ctx, "Invalid value for AddressW");
+
+  if (std::optional<APFloat> Val = extractMdFloatValue(StaticSamplerNode, 5))
+    Sampler.MipLODBias = Val->convertToFloat();
+  else
+    return reportError(Ctx, "Invalid value for MipLODBias");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 6))
+    Sampler.MaxAnisotropy = *Val;
+  else
+    return reportError(Ctx, "Invalid value for MaxAnisotropy");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 7))
+    Sampler.ComparisonFunc = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ComparisonFunc ");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 8))
+    Sampler.BorderColor = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ComparisonFunc ");
+
+  if (std::optional<APFloat> Val = extractMdFloatValue(StaticSamplerNode, 9))
+    Sampler.MinLOD = Val->convertToFloat();
+  else
+    return reportError(Ctx, "Invalid value for MinLOD");
+
+  if (std::optional<APFloat> Val = extractMdFloatValue(StaticSamplerNode, 10))
+    Sampler.MaxLOD = Val->convertToFloat();
+  else
+    return reportError(Ctx, "Invalid value for MaxLOD");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 11))
+    Sampler.ShaderRegister = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ShaderRegister");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 12))
+    Sampler.RegisterSpace = *Val;
+  else
+    return reportError(Ctx, "Invalid value for RegisterSpace");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(StaticSamplerNode, 13))
+    Sampler.ShaderVisibility = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ShaderVisibility");
+
+  RSD.StaticSamplers.push_back(Sampler);
+  return false;
+}
+
 static bool parseRootSignatureElement(LLVMContext *Ctx,
                                       mcdxbc::RootSignatureDesc &RSD,
                                       MDNode *Element) {
@@ -273,6 +356,7 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
           .Case("RootSRV", RootSignatureElementKind::SRV)
           .Case("RootUAV", RootSignatureElementKind::UAV)
           .Case("DescriptorTable", RootSignatureElementKind::DescriptorTable)
+          .Case("StaticSampler", RootSignatureElementKind::StaticSamplers)
           .Default(RootSignatureElementKind::Error);
 
   switch (ElementKind) {
@@ -287,6 +371,8 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
     return parseRootDescriptors(Ctx, RSD, Element, ElementKind);
   case RootSignatureElementKind::DescriptorTable:
     return parseDescriptorTable(Ctx, RSD, Element);
+  case RootSignatureElementKind::StaticSamplers:
+    return parseStaticSampler(Ctx, RSD, Element);
   case RootSignatureElementKind::Error:
     return reportError(Ctx, "Invalid Root Signature Element: " + *ElementText);
   }
@@ -347,6 +433,7 @@ static bool isFlagSet(uint32_t Flags, FlagTypes... FlagsToCheck) {
 
 static bool verifyDescriptorRangeFlag(uint32_t Version, uint32_t Type,
                                       uint32_t Flags) {
+
   bool IsSampler =
       (Type == llvm::to_underlying(dxbc::DescriptorRangeType::Sampler));
 
@@ -399,6 +486,127 @@ static bool verifyDescriptorRangeFlag(uint32_t Version, uint32_t Type,
   return false;
 }
 
+static bool verifySamplerFilter(uint32_t Filter) {
+  switch (Filter) {
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::ANISOTROPIC):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::COMPARISON_MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::COMPARISON_ANISOTROPIC):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MINIMUM_MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MINIMUM_ANISOTROPIC):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_POINT_MAG_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_LINEAR_MAG_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_LINEAR_MIP_POINT):
+  case llvm::to_underlying(
+      dxbc::StaticSamplerFilter::MAXIMUM_MIN_MAG_MIP_LINEAR):
+  case llvm::to_underlying(dxbc::StaticSamplerFilter::MAXIMUM_ANISOTROPIC):
+    return true;
+  }
+  return false;
+}
+
+// Values allowed here:
+// https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_texture_address_mode#syntax
+static bool verifyAddress(uint32_t Address) {
+  switch (Address) {
+  case llvm::to_underlying(dxbc::TextureAddressMode::Border):
+  case llvm::to_underlying(dxbc::TextureAddressMode::Clamp):
+  case llvm::to_underlying(dxbc::TextureAddressMode::Mirror):
+  case llvm::to_underlying(dxbc::TextureAddressMode::MirrorOnce):
+  case llvm::to_underlying(dxbc::TextureAddressMode::Wrap):
+    return true;
+  }
+
+  return false;
+}
+
+static bool verifyMipLODBias(float MipLODBias) {
+  return MipLODBias >= -16.f && MipLODBias <= 16.f;
+}
+
+static bool verifyMaxAnisotropy(uint32_t MaxAnisotropy) {
+  return MaxAnisotropy <= 16u;
+}
+
+static bool verifyComparisonFunc(uint32_t ComparisonFunc) {
+  switch (ComparisonFunc) {
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Never):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Less):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Equal):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::LessEqual):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Greater):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::NotEqual):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::GreaterEqual):
+  case llvm::to_underlying(dxbc::SamplersComparisonFunction::Always):
+    return true;
+  }
+  return false;
+}
+
+static bool verifyBorderColor(uint32_t BorderColor) {
+  switch (BorderColor) {
+  case llvm::to_underlying(dxbc::SamplersBorderColor::TransparentBlack):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueBlack):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueWhite):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueBlackUint):
+  case llvm::to_underlying(dxbc::SamplersBorderColor::OpaqueWhiteUint):
+    return true;
+  }
+  return false;
+}
+
+static bool verifyLOD(float LOD) { return !std::isnan(LOD); }
+
 static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {
 
   if (!verifyVersion(RSD.Version)) {
@@ -456,6 +664,48 @@ static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {
     }
   }
 
+  for (const dxbc::RTS0::v1::StaticSampler &Sampler : RSD.StaticSamplers) {
+    if (!verifySamplerFilter(Sampler.Filter))
+      return reportValueError(Ctx, "Filter", Sampler.Filter);
+
+    if (!verifyAddress(Sampler.AddressU))
+      return reportValueError(Ctx, "AddressU", Sampler.AddressU);
+
+    if (!verifyAddress(Sampler.AddressV))
+      return reportValueError(Ctx, "AddressV", Sampler.AddressV);
+
+    if (!verifyAddress(Sampler.AddressW))
+      return reportValueError(Ctx, "AddressW", Sampler.AddressW);
+
+    if (!verifyMipLODBias(Sampler.MipLODBias))
+      return reportValueError(Ctx, "MipLODBias", Sampler.MipLODBias);
+
+    if (!verifyMaxAnisotropy(Sampler.MaxAnisotropy))
+      return reportValueError(Ctx, "MaxAnisotropy", Sampler.MaxAnisotropy);
+
+    if (!verifyComparisonFunc(Sampler.ComparisonFunc))
+      return reportValueError(Ctx, "ComparisonFunc", Sampler.ComparisonFunc);
+
+    if (!verifyBorderColor(Sampler.BorderColor))
+      return reportValueError(Ctx, "BorderColor", Sampler.BorderColor);
+
+    if (!verifyLOD(Sampler.MinLOD))
+      return reportValueError(Ctx, "MinLOD", Sampler.MinLOD);
+
+    if (!verifyLOD(Sampler.MaxLOD))
+      return reportValueError(Ctx, "MaxLOD", Sampler.MaxLOD);
+
+    if (!verifyRegisterValue(Sampler.ShaderRegister))
+      return reportValueError(Ctx, "ShaderRegister", Sampler.ShaderRegister);
+
+    if (!verifyRegisterSpace(Sampler.RegisterSpace))
+      return reportValueError(Ctx, "RegisterSpace", Sampler.RegisterSpace);
+
+    if (!dxbc::isValidShaderVisibility(Sampler.ShaderVisibility))
+      return reportValueError(Ctx, "ShaderVisibility",
+                              Sampler.ShaderVisibility);
+  }
+
   return false;
 }
 
@@ -529,6 +779,9 @@ analyzeModule(Module &M) {
     // offset will always equal to the header size.
     RSD.RootParameterOffset = sizeof(dxbc::RTS0::v1::RootSignatureHeader);
 
+    // static sampler offset is calculated when writting dxcontainer.
+    RSD.StaticSamplersOffset = 0u;
+
     if (parse(Ctx, RSD, RootElementListNode) || validate(Ctx, RSD)) {
       return RSDMap;
     }
diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.h b/llvm/lib/Target/DirectX/DXILRootSignature.h
index b45cebc15fd39..be5cc78bc6bdf 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.h
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.h
@@ -32,6 +32,7 @@ enum class RootSignatureElementKind {
   UAV = 4,
   CBV = 5,
   DescriptorTable = 6,
+  StaticSamplers = 7
 };
 class RootSignatureAnalysis : public AnalysisInfoMixin<RootSignatureAnalysis> {
   friend AnalysisInfoMixin<RootSignatureAnalysis>;
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll
new file mode 100644
index 0000000000000..124803e36a450
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressU.ll
@@ -0,0 +1,19 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+; CHECK: error: Invalid value for AddressU: 666
+; CHECK-NOT: Root Signature Definitions
+
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"StaticSampler", i32 4, i32 666, i32 3, i32 5, float 0x3FF6CCCCC0000000, i32 9, i32 3, i32 2, float -1.280000e+02, float 1.280000e+02, i32 42, i32 0, i32 0 }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll
new file mode 100644
index 0000000000000..e2c5de1c36a0c
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressV.ll
@@ -0,0 +1,19 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+; CHECK: error: Invalid value for AddressV: 666
+; CHECK-NOT: Root Signature Definitions
+
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"StaticSampler", i32 4, i32 2, i32 666, i32 5, float 0x3FF6CCCCC0000000, i32 9, i32 3, i32 2, float -1.280000e+02, float 1.280000e+02, i32 42, i32 0, i32 0 }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll
new file mode 100644
index 0000000000000..08fc8fa9e5093
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-StaticSamplers-Invalid-AddressW.ll
@@ -0,0 +1,19 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+; CHECK: error: Invalid value for AddressW: 666
+; CHECK-NOT: Root Signature Definitions
+
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"StaticSampler", i32 4, i32 2, i32 3, i32 666, float 0x3FF6CCCCC0000000, i32 9, i32 3, i32 2, float...
[truncated]

@@ -399,6 +486,127 @@ static bool verifyDescriptorRangeFlag(uint32_t Version, uint32_t Type,
return false;
}

static bool verifySamplerFilter(uint32_t Filter) {
switch (Filter) {
case llvm::to_underlying(dxbc::StaticSamplerFilter::MIN_MAG_MIP_POINT):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might be able to use a similar BitEnumMask technique here?

Or just a <= comparison?

if (!verifySamplerFilter(Sampler.Filter))
return reportValueError(Ctx, "Filter", Sampler.Filter);

if (!verifyAddress(Sampler.AddressU))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thought that just came up. I don't think it is required for us to early exit on all these verifications.

Maybe we can report all invalid errors in one go rather than exiting on the first one? Can you provide an argument for why or why not?

I would assume reporting all errors (so long as they aren't too verbose) reduces the number of compile cycles for the user to correct their input.

@joaosaffran joaosaffran changed the title [DirectX] Add static sampler support to root signature and fix descriptor range flags [DirectX] Add static sampler support to root signature Jun 12, 2025
@joaosaffran joaosaffran changed the base branch from users/joaosaffran/142492 to main June 12, 2025 19:50
@joaosaffran joaosaffran changed the base branch from main to users/joaosaffran/143201 June 12, 2025 19:51
@joaosaffran joaosaffran changed the base branch from users/joaosaffran/143201 to users/joaosaffran/142492 June 12, 2025 19:51
@joaosaffran joaosaffran changed the base branch from users/joaosaffran/142492 to main June 12, 2025 19:54
@joaosaffran joaosaffran changed the base branch from main to users/joaosaffran/142492 June 12, 2025 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:DirectX mc Machine (object) code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants