Skip to content

Conversation

s-perron
Copy link
Contributor

This option allows users to control the image format used for HLSL resources
when targeting SPIR-V. When the option is enabled, the unknown image format
is used. Otherwise, the image format is guessed based on the input type.

Fixes #148270

This option allows users to control the image format used for HLSL resources
when targeting SPIR-V. When the option is enabled, the unknown image format
is used. Otherwise, the image format is guessed based on the input type.

Fixes llvm#148270
@s-perron s-perron requested a review from Keenuts August 27, 2025 17:39
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. HLSL HLSL Language Support labels Aug 27, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 27, 2025

@llvm/pr-subscribers-hlsl
@llvm/pr-subscribers-clang-driver
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Steven Perron (s-perron)

Changes

This option allows users to control the image format used for HLSL resources
when targeting SPIR-V. When the option is enabled, the unknown image format
is used. Otherwise, the image format is guessed based on the input type.

Fixes #148270


Full diff: https://github.com/llvm/llvm-project/pull/155664.diff

8 Files Affected:

  • (modified) clang/include/clang/Basic/LangOptions.def (+1)
  • (modified) clang/include/clang/Driver/Options.td (+10)
  • (modified) clang/lib/CodeGen/Targets/SPIR.cpp (+57-6)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+2-1)
  • (modified) clang/test/CodeGenHLSL/resources/RWBuffer-elementtype.hlsl (+6-6)
  • (added) clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl (+74)
  • (modified) clang/test/CodeGenHLSL/resources/RWBuffer-subscript.hlsl (+1-1)
  • (modified) clang/test/CodeGenHLSL/vk-features/SpirvType.hlsl (+1-1)
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index f094ba112988f..e0a5351143dfd 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -241,6 +241,7 @@ LANGOPT(HLSL, 1, 0, NotCompatible, "HLSL")
 ENUM_LANGOPT(HLSLVersion, HLSLLangStd, 16, HLSL_Unset, NotCompatible, "HLSL Version")
 LANGOPT(HLSLStrictAvailability, 1, 0, NotCompatible,
         "Strict availability diagnostic mode for HLSL built-in functions.")
+LANGOPT(HLSLSpvUseUnknownImageFormat, 1, 0, NotCompatible, "For storage images and texel buffers, sets the default format to 'Unknown' when not specified via the `vk::image_format` attribute. If this option is not used, the format is inferred from the resource's data type.")
 
 LANGOPT(CUDAIsDevice      , 1, 0, NotCompatible, "compiling for CUDA device")
 LANGOPT(CUDAAllowVariadicFunctions, 1, 0, NotCompatible, "allowing variadic functions in CUDA device code")
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 9cfb1bbcac5c3..4e7864bbc59f7 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -9480,6 +9480,16 @@ def fvk_use_scalar_layout
     : DXCFlag<"fvk-use-scalar-layout">,
       HelpText<"Use scalar memory layout for Vulkan resources.">;
 
+def fhlsl_spv_use_unknown_image_format
+    : Flag<["-"], "fspv-use-unknown-image-format">,
+      Group<dxc_Group>,
+      Visibility<[CC1Option, DXCOption]>,
+      HelpText<"For storage images and texel buffers, sets the default format "
+               "to 'Unknown' when not specified via the `vk::image_format` "
+               "attribute. If this option is not used, the format is inferred "
+               "fron the resource's data type.">,
+      MarshallingInfoFlag<LangOpts<"HLSLSpvUseUnknownImageFormat">>;
+
 def no_wasm_opt : Flag<["--"], "no-wasm-opt">,
   Group<m_Group>,
   HelpText<"Disable the wasm-opt optimizer">,
diff --git a/clang/lib/CodeGen/Targets/SPIR.cpp b/clang/lib/CodeGen/Targets/SPIR.cpp
index 237aea755fa29..442d71555ee59 100644
--- a/clang/lib/CodeGen/Targets/SPIR.cpp
+++ b/clang/lib/CodeGen/Targets/SPIR.cpp
@@ -517,14 +517,65 @@ llvm::Type *CommonSPIRTargetCodeGenInfo::getHLSLType(
   return nullptr;
 }
 
+static unsigned
+getImageFormat(const LangOptions &LangOpts,
+               const HLSLAttributedResourceType::Attributes &attributes,
+               llvm::Type *SampledType, QualType Ty, unsigned NumChannels) {
+  // For images with `Sampled` operand equal to 2, there are restrictions on
+  // using the Unknown image format. To avoid these restrictions in common
+  // cases, we guess an image format for them based on the sampled type and the
+  // number of channels. This is intended to match the behaviour of DXC.
+  if (LangOpts.HLSLSpvUseUnknownImageFormat ||
+      attributes.ResourceClass != llvm::dxil::ResourceClass::UAV) {
+    return 0; // Unknown
+  }
+
+  if (SampledType->isIntegerTy(32)) {
+    if (Ty->isSignedIntegerType()) {
+      if (NumChannels == 1)
+        return 24; // R32i
+      if (NumChannels == 2)
+        return 25; // Rg32i
+      if (NumChannels == 4)
+        return 21; // Rgba32i
+    } else {
+      if (NumChannels == 1)
+        return 33; // R32ui
+      if (NumChannels == 2)
+        return 35; // Rg32ui
+      if (NumChannels == 4)
+        return 30; // Rgba32ui
+    }
+  } else if (SampledType->isIntegerTy(64)) {
+    if (NumChannels == 1) {
+      if (Ty->isSignedIntegerType()) {
+        return 41; // R64i
+      }
+      return 40; // R64ui
+    }
+  } else if (SampledType->isFloatTy()) {
+    if (NumChannels == 1)
+      return 3; // R32f
+    if (NumChannels == 2)
+      return 6; // Rg32f
+    if (NumChannels == 4)
+      return 1; // Rgba32f
+  }
+
+  return 0; // Unknown
+}
+
 llvm::Type *CommonSPIRTargetCodeGenInfo::getSPIRVImageTypeFromHLSLResource(
-    const HLSLAttributedResourceType::Attributes &attributes, QualType Ty,
-    CodeGenModule &CGM) const {
+    const HLSLAttributedResourceType::Attributes &attributes,
+    QualType OriginalTy, CodeGenModule &CGM) const {
   llvm::LLVMContext &Ctx = CGM.getLLVMContext();
 
-  Ty = Ty->getCanonicalTypeUnqualified();
-  if (const VectorType *V = dyn_cast<VectorType>(Ty))
+  unsigned NumChannels = 1;
+  QualType Ty = OriginalTy->getCanonicalTypeUnqualified();
+  if (const VectorType *V = dyn_cast<VectorType>(Ty)) {
+    NumChannels = V->getNumElements();
     Ty = V->getElementType();
+  }
   assert(!Ty->isVectorType() && "We still have a vector type.");
 
   llvm::Type *SampledType = CGM.getTypes().ConvertTypeForMem(Ty);
@@ -560,8 +611,8 @@ llvm::Type *CommonSPIRTargetCodeGenInfo::getSPIRVImageTypeFromHLSLResource(
       attributes.ResourceClass == llvm::dxil::ResourceClass::UAV ? 2 : 1;
 
   // Image format.
-  // Setting to unknown for now.
-  IntParams[5] = 0;
+  IntParams[5] = getImageFormat(CGM.getLangOpts(), attributes, SampledType, Ty,
+                                NumChannels);
 
   llvm::TargetExtType *ImageType =
       llvm::TargetExtType::get(Ctx, Name, {SampledType}, IntParams);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 29b7180df5cb5..747633898da6e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -3801,7 +3801,8 @@ static void RenderHLSLOptions(const ArgList &Args, ArgStringList &CmdArgs,
       options::OPT_disable_llvm_passes,
       options::OPT_fnative_half_type,
       options::OPT_hlsl_entrypoint,
-      options::OPT_fdx_rootsignature_version};
+      options::OPT_fdx_rootsignature_version,
+      options::OPT_fhlsl_spv_use_unknown_image_format};
   if (!types::isHLSL(InputType))
     return;
   for (const auto &Arg : ForwardedArguments)
diff --git a/clang/test/CodeGenHLSL/resources/RWBuffer-elementtype.hlsl b/clang/test/CodeGenHLSL/resources/RWBuffer-elementtype.hlsl
index 5512a657bc5f0..f48521b0f1764 100644
--- a/clang/test/CodeGenHLSL/resources/RWBuffer-elementtype.hlsl
+++ b/clang/test/CodeGenHLSL/resources/RWBuffer-elementtype.hlsl
@@ -18,18 +18,18 @@
 
 // SPIRV: %"class.hlsl::RWBuffer" = type { target("spirv.SignedImage", i16, 5, 2, 0, 0, 2, 0) }
 // SPIRV: %"class.hlsl::RWBuffer.0" = type { target("spirv.Image", i16, 5, 2, 0, 0, 2, 0) }
-// SPIRV: %"class.hlsl::RWBuffer.1" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) }
-// SPIRV: %"class.hlsl::RWBuffer.2" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) }
-// SPIRV: %"class.hlsl::RWBuffer.3" = type { target("spirv.SignedImage", i64, 5, 2, 0, 0, 2, 0) }
-// SPIRV: %"class.hlsl::RWBuffer.4" = type { target("spirv.Image", i64, 5, 2, 0, 0, 2, 0) }
+// SPIRV: %"class.hlsl::RWBuffer.1" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 24) }
+// SPIRV: %"class.hlsl::RWBuffer.2" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 33) }
+// SPIRV: %"class.hlsl::RWBuffer.3" = type { target("spirv.SignedImage", i64, 5, 2, 0, 0, 2, 41) }
+// SPIRV: %"class.hlsl::RWBuffer.4" = type { target("spirv.Image", i64, 5, 2, 0, 0, 2, 40) }
 // SPIRV: %"class.hlsl::RWBuffer.5" = type { target("spirv.Image", half, 5, 2, 0, 0, 2, 0) }
-// SPIRV: %"class.hlsl::RWBuffer.6" = type { target("spirv.Image", float, 5, 2, 0, 0, 2, 0) }
+// SPIRV: %"class.hlsl::RWBuffer.6" = type { target("spirv.Image", float, 5, 2, 0, 0, 2, 3) }
 // SPIRV: %"class.hlsl::RWBuffer.7" = type { target("spirv.Image", double, 5, 2, 0, 0, 2, 0) }
 // SPIRV: %"class.hlsl::RWBuffer.8" = type { target("spirv.SignedImage", i16, 5, 2, 0, 0, 2, 0) }
 // SPIRV: %"class.hlsl::RWBuffer.9" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) }
 // SPIRV: %"class.hlsl::RWBuffer.10" = type { target("spirv.Image", half, 5, 2, 0, 0, 2, 0) }
 // SPIRV: %"class.hlsl::RWBuffer.11" = type { target("spirv.Image", float, 5, 2, 0, 0, 2, 0) }
-// SPIRV: %"class.hlsl::RWBuffer.12" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) }
+// SPIRV: %"class.hlsl::RWBuffer.12" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 21) }
 
 RWBuffer<int16_t> BufI16;
 RWBuffer<uint16_t> BufU16;
diff --git a/clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl b/clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl
new file mode 100644
index 0000000000000..aebee894f79d5
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/RWBuffer-imageformat.hlsl
@@ -0,0 +1,74 @@
+// RUN: %clang_cc1 -triple spirv-pc-vulkan-compute -finclude-default-header -fnative-half-type -emit-llvm -o - %s | FileCheck %s
+
+// Signed integers
+// CHECK: %"class.hlsl::RWBuffer" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 24) }
+RWBuffer<int> rwb_int;
+// CHECK: %"class.hlsl::RWBuffer.0" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 25) }
+RWBuffer<int2> rwb_int2;
+// CHECK: %"class.hlsl::RWBuffer.1" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) }
+RWBuffer<int3> rwb_int3;
+// CHECK: %"class.hlsl::RWBuffer.2" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 21) }
+RWBuffer<int4> rwb_int4;
+
+// Unsigned integers
+// CHECK: %"class.hlsl::RWBuffer.3" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 33) }
+RWBuffer<uint> rwb_uint;
+// CHECK: %"class.hlsl::RWBuffer.4" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 35) }
+RWBuffer<uint2> rwb_uint2;
+// CHECK: %"class.hlsl::RWBuffer.5" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) }
+RWBuffer<uint3> rwb_uint3;
+// CHECK: %"class.hlsl::RWBuffer.6" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 30) }
+RWBuffer<uint4> rwb_uint4;
+
+// 64-bit integers
+// CHECK: %"class.hlsl::RWBuffer.7" = type { target("spirv.SignedImage", i64, 5, 2, 0, 0, 2, 41) }
+RWBuffer<int64_t> rwb_i64;
+// CHECK: %"class.hlsl::RWBuffer.8" = type { target("spirv.SignedImage", i64, 5, 2, 0, 0, 2, 0) }
+RWBuffer<int64_t2> rwb_i64_2;
+// CHECK: %"class.hlsl::RWBuffer.9" = type { target("spirv.Image", i64, 5, 2, 0, 0, 2, 40) }
+RWBuffer<uint64_t> rwb_u64;
+// CHECK: %"class.hlsl::RWBuffer.10" = type { target("spirv.Image", i64, 5, 2, 0, 0, 2, 0) }
+RWBuffer<uint64_t2> rwb_u64_2;
+
+// Floats
+// CHECK: %"class.hlsl::RWBuffer.11" = type { target("spirv.Image", float, 5, 2, 0, 0, 2, 3) }
+RWBuffer<float> rwb_float;
+// CHECK: %"class.hlsl::RWBuffer.12" = type { target("spirv.Image", float, 5, 2, 0, 0, 2, 6) }
+RWBuffer<float2> rwb_float2;
+// CHECK: %"class.hlsl::RWBuffer.13" = type { target("spirv.Image", float, 5, 2, 0, 0, 2, 0) }
+RWBuffer<float3> rwb_float3;
+// CHECK: %"class.hlsl::RWBuffer.14" = type { target("spirv.Image", float, 5, 2, 0, 0, 2, 1) }
+RWBuffer<float4> rwb_float4;
+
+// Other types that should get Unknown format
+// CHECK: %"class.hlsl::RWBuffer.15" = type { target("spirv.Image", half, 5, 2, 0, 0, 2, 0) }
+RWBuffer<half> rwb_half;
+// CHECK: %"class.hlsl::RWBuffer.16" = type { target("spirv.Image", double, 5, 2, 0, 0, 2, 0) }
+RWBuffer<double> rwb_double;
+
+// Non-UAV resource
+// CHECK: %"class.hlsl::Buffer" = type { target("spirv.SignedImage", i32, 5, 2, 0, 0, 1, 0) }
+Buffer<int> b_int;
+
+[numthreads(1,1,1)]
+void main(int GI : SV_GroupIndex) {
+    rwb_int[GI] = 0;
+    rwb_int2[GI] = 0;
+    rwb_int3[GI] = 0;
+    rwb_int4[GI] = 0;
+    rwb_uint[GI] = 0;
+    rwb_uint2[GI] = 0;
+    rwb_uint3[GI] = 0;
+    rwb_uint4[GI] = 0;
+    rwb_i64[GI] = 0;
+    rwb_i64_2[GI] = 0;
+    rwb_u64[GI] = 0;
+    rwb_u64_2[GI] = 0;
+    rwb_float[GI] = 0;
+    rwb_float2[GI] = 0;
+    rwb_float3[GI] = 0;
+    rwb_float4[GI] = 0;
+    rwb_half[GI] = 0;
+    rwb_double[GI] = 0;
+    int val = b_int[GI];
+}
diff --git a/clang/test/CodeGenHLSL/resources/RWBuffer-subscript.hlsl b/clang/test/CodeGenHLSL/resources/RWBuffer-subscript.hlsl
index 63e3552b680b6..0de171cb452d8 100644
--- a/clang/test/CodeGenHLSL/resources/RWBuffer-subscript.hlsl
+++ b/clang/test/CodeGenHLSL/resources/RWBuffer-subscript.hlsl
@@ -1,5 +1,5 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-llvm -o - -O0 %s | FileCheck %s --check-prefixes=DXC,CHECK
-// RUN: %clang_cc1 -triple spirv1.6-pc-vulkan1.3-compute -emit-llvm -o - -O0 %s | FileCheck %s --check-prefixes=SPIRV,CHECK
+// RUN: %clang_cc1 -triple spirv1.6-pc-vulkan1.3-compute -fspv-use-unknown-image-format -emit-llvm -o - -O0 %s | FileCheck %s --check-prefixes=SPIRV,CHECK
 
 RWBuffer<int> In;
 RWBuffer<int> Out;
diff --git a/clang/test/CodeGenHLSL/vk-features/SpirvType.hlsl b/clang/test/CodeGenHLSL/vk-features/SpirvType.hlsl
index 7149be0122f4d..0cebac1e9d864 100644
--- a/clang/test/CodeGenHLSL/vk-features/SpirvType.hlsl
+++ b/clang/test/CodeGenHLSL/vk-features/SpirvType.hlsl
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
 // RUN:   spirv-unknown-vulkan-compute %s -emit-llvm -disable-llvm-passes \
-// RUN:   -o - | FileCheck %s
+// RUN:   -fspv-use-unknown-image-format -o - | FileCheck %s
 
 template<class T, uint64_t Size>
 using Array = vk::SpirvOpaqueType</* OpTypeArray */ 28, T, vk::integral_constant<uint64_t, Size>>;

@s-perron s-perron changed the title [HLSL][SPIRV] Add -fhlsl-spv-use-unknown-image-format option [HLSL][SPIRV] Add -fspv-use-unknown-image-format option Aug 28, 2025
s-perron added a commit to s-perron/offload-test-suite that referenced this pull request Aug 28, 2025
Enable vulkan tests that are now able to pass using clang. Some of the
tests require small updates in the options used.

FYI: Requires llvm/llvm-project#155664 to avoid
validation problems on Intel.
s-perron added a commit to s-perron/offload-test-suite that referenced this pull request Aug 28, 2025
Enable vulkan tests that are now able to pass using clang. Some of the
tests require small updates in the options used.

FYI: Requires llvm/llvm-project#155664 to avoid
validation problems on Intel.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HLSL][SPIRV] Guess at the image format for storage images
3 participants