From 754ca1f91969e61151be7e415f6a26b49a96da2d Mon Sep 17 00:00:00 2001 From: Steven Perron Date: Fri, 7 Nov 2025 13:53:02 -0500 Subject: [PATCH 1/5] [VULKAN] Add support for specialization constants This commit introduces support for specialization constants in the Vulkan backend. Key changes: - Added struct to to represent a specialization constant with its ID, type, and value. - Updated YAML mapping in to parse specialization constants from the test configuration. - Modified to create and use when creating the compute pipeline, allowing specialization constants to be passed to the shader. - Added a new test case in to verify the functionality of specialization constants with various data types (bool, int, uint, float). Fixes https://github.com/llvm/llvm-project/issues/142992 --- include/Support/Pipeline.h | 12 ++ lib/API/VK/Device.cpp | 116 ++++++++++++++++-- lib/Support/Pipeline.cpp | 10 ++ .../spec_const_32_bits.test | 87 +++++++++++++ .../spec_const_other_sizes.test | 64 ++++++++++ 5 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 test/Feature/SpecializationConstant/spec_const_32_bits.test create mode 100644 test/Feature/SpecializationConstant/spec_const_other_sizes.test diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index 15b4fc8ea..fa37a96f8 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -282,11 +282,18 @@ struct IOBindings { } }; +struct SpecializationConstant { + uint32_t ConstantID; + DataFormat Type; + std::string Value; +}; + struct Shader { Stages Stage; std::string Entry; std::unique_ptr Shader; int DispatchSize[3]; + llvm::SmallVector SpecializationConstants; }; struct Pipeline { @@ -335,6 +342,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::Shader) LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::dx::RootParameter) LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::Result) LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::VertexAttribute) +LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::SpecializationConstant) namespace llvm { namespace yaml { @@ -399,6 +407,10 @@ template <> struct MappingTraits { static void mapping(IO &I, offloadtest::RuntimeSettings &S); }; +template <> struct MappingTraits { + static void mapping(IO &I, offloadtest::SpecializationConstant &C); +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &I, offloadtest::Rule &V) { #define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::Rule::Val) diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index e852c19f0..c614533a5 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -20,22 +20,34 @@ using namespace offloadtest; -#define VKFormats(FMT) \ +#define VKFormats(FMT, BITS) \ if (Channels == 1) \ - return VK_FORMAT_R32_##FMT; \ + return VK_FORMAT_R##BITS##_##FMT; \ if (Channels == 2) \ - return VK_FORMAT_R32G32_##FMT; \ + return VK_FORMAT_R##BITS##G##BITS##_##FMT; \ if (Channels == 3) \ - return VK_FORMAT_R32G32B32_##FMT; \ + return VK_FORMAT_R##BITS##G##BITS##B##BITS##_##FMT; \ if (Channels == 4) \ - return VK_FORMAT_R32G32B32A32_##FMT; + return VK_FORMAT_R##BITS##G##BITS##B##BITS##A##BITS##_##FMT; static VkFormat getVKFormat(DataFormat Format, int Channels) { switch (Format) { + case DataFormat::Int16: + VKFormats(SINT, 16) break; + case DataFormat::UInt16: + VKFormats(UINT, 16) break; case DataFormat::Int32: - VKFormats(SINT) break; + VKFormats(SINT, 32) break; + case DataFormat::UInt32: + VKFormats(UINT, 32) break; case DataFormat::Float32: - VKFormats(SFLOAT) break; + VKFormats(SFLOAT, 32) break; + case DataFormat::Int64: + VKFormats(SINT, 64) break; + case DataFormat::UInt64: + VKFormats(UINT, 64) break; + case DataFormat::Float64: + VKFormats(SFLOAT, 64) break; default: llvm_unreachable("Unsupported Resource format specified"); } @@ -1273,6 +1285,76 @@ class VKDevice : public offloadtest::Device { return llvm::Error::success(); } + static void + parseSpecializationConstant(const SpecializationConstant &SpecConst, + VkSpecializationMapEntry &Entry, + llvm::SmallVector &SpecData) { + Entry.constantID = SpecConst.ConstantID; + Entry.offset = SpecData.size(); + switch (SpecConst.Type) { + case DataFormat::Float32: { + float Value = 0.0f; + double Tmp = 0.0; + llvm::StringRef(SpecConst.Value).getAsDouble(Tmp); + Value = static_cast(Tmp); + Entry.size = sizeof(float); + SpecData.resize(SpecData.size() + sizeof(float)); + memcpy(SpecData.data() + Entry.offset, &Value, sizeof(float)); + break; + } + case DataFormat::Float64: { + double Value = 0.0; + llvm::StringRef(SpecConst.Value).getAsDouble(Value); + Entry.size = sizeof(double); + SpecData.resize(SpecData.size() + sizeof(double)); + memcpy(SpecData.data() + Entry.offset, &Value, sizeof(double)); + break; + } + case DataFormat::Int16: { + int16_t Value = 0; + llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + Entry.size = sizeof(int16_t); + SpecData.resize(SpecData.size() + sizeof(int16_t)); + memcpy(SpecData.data() + Entry.offset, &Value, sizeof(int16_t)); + break; + } + case DataFormat::UInt16: { + uint16_t Value = 0; + llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + Entry.size = sizeof(uint16_t); + SpecData.resize(SpecData.size() + sizeof(uint16_t)); + memcpy(SpecData.data() + Entry.offset, &Value, sizeof(uint16_t)); + break; + } + case DataFormat::Int32: { + int32_t Value = 0; + llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + Entry.size = sizeof(int32_t); + SpecData.resize(SpecData.size() + sizeof(int32_t)); + memcpy(SpecData.data() + Entry.offset, &Value, sizeof(int32_t)); + break; + } + case DataFormat::UInt32: { + uint32_t Value = 0; + llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + Entry.size = sizeof(uint32_t); + SpecData.resize(SpecData.size() + sizeof(uint32_t)); + memcpy(SpecData.data() + Entry.offset, &Value, sizeof(uint32_t)); + break; + } + case DataFormat::Bool: { + bool Value = false; + llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + Entry.size = sizeof(bool); + SpecData.resize(SpecData.size() + sizeof(bool)); + memcpy(SpecData.data() + Entry.offset, &Value, sizeof(bool)); + break; + } + default: + llvm_unreachable("Unsupported specialization constant type"); + } + } + llvm::Error createPipeline(Pipeline &P, InvocationState &IS) { VkPipelineCacheCreateInfo CacheCreateInfo = {}; CacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; @@ -1282,15 +1364,33 @@ class VKDevice : public offloadtest::Device { "Failed to create pipeline cache."); if (P.isCompute()) { - const CompiledShader &S = IS.Shaders[0]; + const offloadtest::Shader &Shader = P.Shaders[0]; assert(IS.Shaders.size() == 1 && "Currently only support one compute shader"); + const CompiledShader &S = IS.Shaders[0]; VkPipelineShaderStageCreateInfo StageInfo = {}; StageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; StageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; StageInfo.module = S.Shader; StageInfo.pName = S.Entry.c_str(); + llvm::SmallVector SpecEntries; + llvm::SmallVector SpecData; + VkSpecializationInfo SpecInfo = {}; + if (!Shader.SpecializationConstants.empty()) { + for (const auto &SpecConst : Shader.SpecializationConstants) { + VkSpecializationMapEntry Entry; + parseSpecializationConstant(SpecConst, Entry, SpecData); + SpecEntries.push_back(Entry); + } + + SpecInfo.mapEntryCount = SpecEntries.size(); + SpecInfo.pMapEntries = SpecEntries.data(); + SpecInfo.dataSize = SpecData.size(); + SpecInfo.pData = SpecData.data(); + StageInfo.pSpecializationInfo = &SpecInfo; + } + VkComputePipelineCreateInfo PipelineCreateInfo = {}; PipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; PipelineCreateInfo.stage = StageInfo; diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 74494f0d0..5a8b9745d 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -372,6 +372,7 @@ void MappingTraits::mapping(IO &I, offloadtest::Shader &S) { I.mapRequired("Stage", S.Stage); I.mapRequired("Entry", S.Entry); + I.mapOptional("SpecializationConstants", S.SpecializationConstants); if (S.Stage == Stages::Compute) { // Stage-specific data, not sure if this should be optional @@ -380,6 +381,7 @@ void MappingTraits::mapping(IO &I, I.mapRequired("DispatchSize", MutableDispatchSize); } } + void MappingTraits::mapping(IO &I, offloadtest::Result &R) { I.mapRequired("Result", R.Name); @@ -402,5 +404,13 @@ void MappingTraits::mapping(IO &I, break; } } + +void MappingTraits::mapping( + IO &I, offloadtest::SpecializationConstant &C) { + I.mapRequired("ConstantID", C.ConstantID); + I.mapRequired("Type", C.Type); + I.mapRequired("Value", C.Value); +} + } // namespace yaml } // namespace llvm diff --git a/test/Feature/SpecializationConstant/spec_const_32_bits.test b/test/Feature/SpecializationConstant/spec_const_32_bits.test new file mode 100644 index 000000000..129caf5c9 --- /dev/null +++ b/test/Feature/SpecializationConstant/spec_const_32_bits.test @@ -0,0 +1,87 @@ +#--- simple.hlsl +// bool +[[vk::constant_id(0)]] +const bool spec_bool = false; +RWBuffer OutBool : register(u0, space0); + +// int +[[vk::constant_id(1)]] +const int spec_int = 0; +RWBuffer OutInt : register(u1, space0); + +// unsigned int +[[vk::constant_id(2)]] +const uint spec_uint = 0; +RWBuffer OutUInt : register(u2, space0); + +// float +[[vk::constant_id(3)]] +const float spec_float = 0.0; +RWBuffer OutFloat : register(u3, space0); + +// int with default value +[[vk::constant_id(4)]] +const int spec_int_default = 1234; +RWBuffer OutIntDefault : register(u4, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutBool[GI] = (uint)spec_bool; + OutInt[GI] = spec_int; + OutUInt[GI] = spec_uint; + OutFloat[GI] = spec_float; + OutIntDefault[GI] = spec_int_default; +} +#--- simple.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: 1, Type: Bool } + - { ConstantID: 1, Value: 42, Type: Int32 } + - { ConstantID: 2, Value: 0xDEADBEEF, Type: UInt32 } + - { ConstantID: 3, Value: 3.14, Type: Float32 } +Buffers: + - { Name: OutBool, Format: Int32, FillSize: 4 } + - { Name: OutInt, Format: Int32, FillSize: 4 } + - { Name: OutUInt, Format: UInt32, FillSize: 4 } + - { Name: OutFloat, Format: Float32, FillSize: 4 } + - { Name: OutIntDefault, Format: Int32, FillSize: 4 } +DescriptorSets: + - Resources: + - Name: OutBool + Kind: RWBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } + - Name: OutInt + Kind: RWBuffer + DirectXBinding: { Register: 1, Space: 0 } + VulkanBinding: { Binding: 1 } + - Name: OutUInt + Kind: RWBuffer + DirectXBinding: { Register: 2, Space: 0 } + VulkanBinding: { Binding: 2 } + - Name: OutFloat + Kind: RWBuffer + DirectXBinding: { Register: 3, Space: 0 } + VulkanBinding: { Binding: 3 } + - Name: OutIntDefault + Kind: RWBuffer + DirectXBinding: { Register: 4, Space: 0 } + VulkanBinding: { Binding: 4 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_0 -Fo %t.o %t/simple.hlsl +# RUN: %offloader %t/simple.yaml %t.o | FileCheck %s + +# CHECK: Data: [ 1 ] +# CHECK: Data: [ 42 ] +# CHECK: Data: [ 3735928559 ] +# CHECK: Data: [ {{3.14.*}} ] +# CHECK: Data: [ 1234 ] diff --git a/test/Feature/SpecializationConstant/spec_const_other_sizes.test b/test/Feature/SpecializationConstant/spec_const_other_sizes.test new file mode 100644 index 000000000..1edaa3b48 --- /dev/null +++ b/test/Feature/SpecializationConstant/spec_const_other_sizes.test @@ -0,0 +1,64 @@ +#--- simple_64bit.hlsl +// double +[[vk::constant_id(0)]] +const double spec_double = 0.0; +RWStructuredBuffer OutDouble : register(u0, space0); + +// short +[[vk::constant_id(1)]] +const int16_t spec_short = 0; +RWStructuredBuffer OutShort : register(u1, space0); + +// ushort +[[vk::constant_id(2)]] +const uint16_t spec_ushort = 0; +RWStructuredBuffer OutUShort : register(u2, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutDouble[GI] = spec_double; + OutShort[GI] = spec_short; + OutUShort[GI] = spec_ushort; +} +#--- simple_64bit.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: 2.718, Type: Float64 } + - { ConstantID: 1, Value: 123, Type: Int16 } + - { ConstantID: 2, Value: 456, Type: UInt16 } +Buffers: + - { Name: OutDouble, Format: Float64, Stride: 8, FillSize: 8 } + - { Name: OutShort, Format: Int16, Stride: 2, FillSize: 2 } + - { Name: OutUShort, Format: UInt16, Stride: 2, FillSize: 2 } +DescriptorSets: + - Resources: + - Name: OutDouble + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } + - Name: OutShort + Kind: RWStructuredBuffer + DirectXBinding: { Register: 1, Space: 0 } + VulkanBinding: { Binding: 1 } + - Name: OutUShort + Kind: RWStructuredBuffer + DirectXBinding: { Register: 2, Space: 0 } + VulkanBinding: { Binding: 2 } +... +#--- end + +# REQUIRES: Vulkan + +# XFAIL: DXC + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -enable-16bit-types -Fo %t.o %t/simple_64bit.hlsl +# RUN: %offloader %t/simple_64bit.yaml %t.o | FileCheck %s + +# CHECK: Data: [ {{2.718}} ] +# CHECK: Data: [ 123 ] +# CHECK: Data: [ 456 ] \ No newline at end of file From daae43bb4a49deae61b7da04a82889dee281acc0 Mon Sep 17 00:00:00 2001 From: Steven Perron Date: Thu, 13 Nov 2025 08:43:52 -0500 Subject: [PATCH 2/5] Add error for invalid strings --- lib/API/VK/Device.cpp | 49 +++++++++++++++---- .../SpecializationConstant/invalid_bool.test | 35 +++++++++++++ .../invalid_double.test | 35 +++++++++++++ .../SpecializationConstant/invalid_float.test | 35 +++++++++++++ .../SpecializationConstant/invalid_int16.test | 35 +++++++++++++ .../SpecializationConstant/invalid_int32.test | 35 +++++++++++++ .../invalid_uint16.test | 35 +++++++++++++ .../invalid_uint32.test | 35 +++++++++++++ 8 files changed, 285 insertions(+), 9 deletions(-) create mode 100644 test/Feature/SpecializationConstant/invalid_bool.test create mode 100644 test/Feature/SpecializationConstant/invalid_double.test create mode 100644 test/Feature/SpecializationConstant/invalid_float.test create mode 100644 test/Feature/SpecializationConstant/invalid_int16.test create mode 100644 test/Feature/SpecializationConstant/invalid_int32.test create mode 100644 test/Feature/SpecializationConstant/invalid_uint16.test create mode 100644 test/Feature/SpecializationConstant/invalid_uint32.test diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index c614533a5..3a0471149 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -1285,7 +1285,7 @@ class VKDevice : public offloadtest::Device { return llvm::Error::success(); } - static void + static llvm::Error parseSpecializationConstant(const SpecializationConstant &SpecConst, VkSpecializationMapEntry &Entry, llvm::SmallVector &SpecData) { @@ -1295,7 +1295,11 @@ class VKDevice : public offloadtest::Device { case DataFormat::Float32: { float Value = 0.0f; double Tmp = 0.0; - llvm::StringRef(SpecConst.Value).getAsDouble(Tmp); + if (llvm::StringRef(SpecConst.Value).getAsDouble(Tmp)) + return llvm::createStringError( + std::errc::invalid_argument, + "Invalid float value for specialization constant '%s'", + SpecConst.Value.c_str()); Value = static_cast(Tmp); Entry.size = sizeof(float); SpecData.resize(SpecData.size() + sizeof(float)); @@ -1304,7 +1308,11 @@ class VKDevice : public offloadtest::Device { } case DataFormat::Float64: { double Value = 0.0; - llvm::StringRef(SpecConst.Value).getAsDouble(Value); + if (llvm::StringRef(SpecConst.Value).getAsDouble(Value)) + return llvm::createStringError( + std::errc::invalid_argument, + "Invalid double value for specialization constant '%s'", + SpecConst.Value.c_str()); Entry.size = sizeof(double); SpecData.resize(SpecData.size() + sizeof(double)); memcpy(SpecData.data() + Entry.offset, &Value, sizeof(double)); @@ -1312,7 +1320,11 @@ class VKDevice : public offloadtest::Device { } case DataFormat::Int16: { int16_t Value = 0; - llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + if (llvm::StringRef(SpecConst.Value).getAsInteger(0, Value)) + return llvm::createStringError( + std::errc::invalid_argument, + "Invalid int16 value for specialization constant '%s'", + SpecConst.Value.c_str()); Entry.size = sizeof(int16_t); SpecData.resize(SpecData.size() + sizeof(int16_t)); memcpy(SpecData.data() + Entry.offset, &Value, sizeof(int16_t)); @@ -1320,7 +1332,11 @@ class VKDevice : public offloadtest::Device { } case DataFormat::UInt16: { uint16_t Value = 0; - llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + if (llvm::StringRef(SpecConst.Value).getAsInteger(0, Value)) + return llvm::createStringError( + std::errc::invalid_argument, + "Invalid uint16 value for specialization constant '%s'", + SpecConst.Value.c_str()); Entry.size = sizeof(uint16_t); SpecData.resize(SpecData.size() + sizeof(uint16_t)); memcpy(SpecData.data() + Entry.offset, &Value, sizeof(uint16_t)); @@ -1328,7 +1344,11 @@ class VKDevice : public offloadtest::Device { } case DataFormat::Int32: { int32_t Value = 0; - llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + if (llvm::StringRef(SpecConst.Value).getAsInteger(0, Value)) + return llvm::createStringError( + std::errc::invalid_argument, + "Invalid int32 value for specialization constant '%s'", + SpecConst.Value.c_str()); Entry.size = sizeof(int32_t); SpecData.resize(SpecData.size() + sizeof(int32_t)); memcpy(SpecData.data() + Entry.offset, &Value, sizeof(int32_t)); @@ -1336,7 +1356,11 @@ class VKDevice : public offloadtest::Device { } case DataFormat::UInt32: { uint32_t Value = 0; - llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + if (llvm::StringRef(SpecConst.Value).getAsInteger(0, Value)) + return llvm::createStringError( + std::errc::invalid_argument, + "Invalid uint32 value for specialization constant '%s'", + SpecConst.Value.c_str()); Entry.size = sizeof(uint32_t); SpecData.resize(SpecData.size() + sizeof(uint32_t)); memcpy(SpecData.data() + Entry.offset, &Value, sizeof(uint32_t)); @@ -1344,7 +1368,11 @@ class VKDevice : public offloadtest::Device { } case DataFormat::Bool: { bool Value = false; - llvm::StringRef(SpecConst.Value).getAsInteger(0, Value); + if (llvm::StringRef(SpecConst.Value).getAsInteger(0, Value)) + return llvm::createStringError( + std::errc::invalid_argument, + "Invalid bool value for specialization constant '%s'", + SpecConst.Value.c_str()); Entry.size = sizeof(bool); SpecData.resize(SpecData.size() + sizeof(bool)); memcpy(SpecData.data() + Entry.offset, &Value, sizeof(bool)); @@ -1353,6 +1381,7 @@ class VKDevice : public offloadtest::Device { default: llvm_unreachable("Unsupported specialization constant type"); } + return llvm::Error::success(); } llvm::Error createPipeline(Pipeline &P, InvocationState &IS) { @@ -1380,7 +1409,9 @@ class VKDevice : public offloadtest::Device { if (!Shader.SpecializationConstants.empty()) { for (const auto &SpecConst : Shader.SpecializationConstants) { VkSpecializationMapEntry Entry; - parseSpecializationConstant(SpecConst, Entry, SpecData); + if (auto Err = + parseSpecializationConstant(SpecConst, Entry, SpecData)) + return Err; SpecEntries.push_back(Entry); } diff --git a/test/Feature/SpecializationConstant/invalid_bool.test b/test/Feature/SpecializationConstant/invalid_bool.test new file mode 100644 index 000000000..cb71f72d5 --- /dev/null +++ b/test/Feature/SpecializationConstant/invalid_bool.test @@ -0,0 +1,35 @@ +#--- invalid_bool.hlsl +[[vk::constant_id(0)]] +const bool spec_bool = false; +RWStructuredBuffer OutBool : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutBool[GI] = (uint)spec_bool; +} +#--- invalid_bool.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: "not a number", Type: Bool } +Buffers: + - { Name: OutBool, Format: Int32, Stride: 4, FillSize: 4 } +DescriptorSets: + - Resources: + - Name: OutBool + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/invalid_bool.hlsl +# RUN: not %offloader %t/invalid_bool.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid bool value for specialization constant 'not a number' diff --git a/test/Feature/SpecializationConstant/invalid_double.test b/test/Feature/SpecializationConstant/invalid_double.test new file mode 100644 index 000000000..2245991bf --- /dev/null +++ b/test/Feature/SpecializationConstant/invalid_double.test @@ -0,0 +1,35 @@ +#--- invalid_double.hlsl +[[vk::constant_id(0)]] +const double spec_double = 0.0; +RWStructuredBuffer OutDouble : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutDouble[GI] = spec_double; +} +#--- invalid_double.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: "not a number", Type: Float64 } +Buffers: + - { Name: OutDouble, Format: Float64, Stride: 8, FillSize: 8 } +DescriptorSets: + - Resources: + - Name: OutDouble + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/invalid_double.hlsl +# RUN: not %offloader %t/invalid_double.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid double value for specialization constant 'not a number' \ No newline at end of file diff --git a/test/Feature/SpecializationConstant/invalid_float.test b/test/Feature/SpecializationConstant/invalid_float.test new file mode 100644 index 000000000..da5dbbd90 --- /dev/null +++ b/test/Feature/SpecializationConstant/invalid_float.test @@ -0,0 +1,35 @@ +#--- invalid_float.hlsl +[[vk::constant_id(0)]] +const float spec_float = 0.0; +RWStructuredBuffer OutFloat : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutFloat[GI] = spec_float; +} +#--- invalid_float.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: "not a number", Type: Float32 } +Buffers: + - { Name: OutFloat, Format: Float32, Stride: 4, FillSize: 4 } +DescriptorSets: + - Resources: + - Name: OutFloat + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/invalid_float.hlsl +# RUN: not %offloader %t/invalid_float.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid float value for specialization constant 'not a number' diff --git a/test/Feature/SpecializationConstant/invalid_int16.test b/test/Feature/SpecializationConstant/invalid_int16.test new file mode 100644 index 000000000..d00f46557 --- /dev/null +++ b/test/Feature/SpecializationConstant/invalid_int16.test @@ -0,0 +1,35 @@ +#--- invalid_int16.hlsl +[[vk::constant_id(0)]] +const int16_t spec_short = 0; +RWStructuredBuffer OutShort : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutShort[GI] = spec_short; +} +#--- invalid_int16.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: "not a number", Type: Int16 } +Buffers: + - { Name: OutShort, Format: Int16, Stride: 2, FillSize: 2 } +DescriptorSets: + - Resources: + - Name: OutShort + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -enable-16bit-types -Fo %t.o %t/invalid_int16.hlsl +# RUN: not %offloader %t/invalid_int16.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid int16 value for specialization constant 'not a number' diff --git a/test/Feature/SpecializationConstant/invalid_int32.test b/test/Feature/SpecializationConstant/invalid_int32.test new file mode 100644 index 000000000..8d007416c --- /dev/null +++ b/test/Feature/SpecializationConstant/invalid_int32.test @@ -0,0 +1,35 @@ +#--- invalid_int32.hlsl +[[vk::constant_id(0)]] +const int spec_int = 0; +RWStructuredBuffer OutInt : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutInt[GI] = spec_int; +} +#--- invalid_int32.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: "not a number", Type: Int32 } +Buffers: + - { Name: OutInt, Format: Int32, Stride: 4, FillSize: 4 } +DescriptorSets: + - Resources: + - Name: OutInt + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/invalid_int32.hlsl +# RUN: not %offloader %t/invalid_int32.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid int32 value for specialization constant 'not a number' diff --git a/test/Feature/SpecializationConstant/invalid_uint16.test b/test/Feature/SpecializationConstant/invalid_uint16.test new file mode 100644 index 000000000..deb9ada47 --- /dev/null +++ b/test/Feature/SpecializationConstant/invalid_uint16.test @@ -0,0 +1,35 @@ +#--- invalid_uint16.hlsl +[[vk::constant_id(0)]] +const uint16_t spec_ushort = 0; +RWStructuredBuffer OutUShort : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutUShort[GI] = spec_ushort; +} +#--- invalid_uint16.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: "not a number", Type: UInt16 } +Buffers: + - { Name: OutUShort, Format: UInt16, Stride: 2, FillSize: 2 } +DescriptorSets: + - Resources: + - Name: OutUShort + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -enable-16bit-types -Fo %t.o %t/invalid_uint16.hlsl +# RUN: not %offloader %t/invalid_uint16.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid uint16 value for specialization constant 'not a number' diff --git a/test/Feature/SpecializationConstant/invalid_uint32.test b/test/Feature/SpecializationConstant/invalid_uint32.test new file mode 100644 index 000000000..f74cdabe5 --- /dev/null +++ b/test/Feature/SpecializationConstant/invalid_uint32.test @@ -0,0 +1,35 @@ +#--- invalid_uint32.hlsl +[[vk::constant_id(0)]] +const uint spec_uint = 0; +RWStructuredBuffer OutUInt : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + OutUInt[GI] = spec_uint; +} +#--- invalid_uint32.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: "not a number", Type: UInt32 } +Buffers: + - { Name: OutUInt, Format: UInt32, Stride: 4, FillSize: 4 } +DescriptorSets: + - Resources: + - Name: OutUInt + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/invalid_uint32.hlsl +# RUN: not %offloader %t/invalid_uint32.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: Invalid uint32 value for specialization constant 'not a number' From 1d127495f7b2aa973c8c241c50fa552a5f98cea7 Mon Sep 17 00:00:00 2001 From: Steven Perron Date: Thu, 13 Nov 2025 09:09:39 -0500 Subject: [PATCH 3/5] Add test for multiple spec constants in the HLSL. --- .../duplicate_spec_id.test | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/Feature/SpecializationConstant/duplicate_spec_id.test diff --git a/test/Feature/SpecializationConstant/duplicate_spec_id.test b/test/Feature/SpecializationConstant/duplicate_spec_id.test new file mode 100644 index 000000000..0e7d90621 --- /dev/null +++ b/test/Feature/SpecializationConstant/duplicate_spec_id.test @@ -0,0 +1,39 @@ +#--- duplicate_spec_id.hlsl +[[vk::constant_id(0)]] +const int spec_int_A = 0; +[[vk::constant_id(0)]] +const int spec_int_B = 0; + +RWStructuredBuffer Out : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + Out[0] = spec_int_A; + Out[1] = spec_int_B; +} +#--- duplicate_spec_id.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: 123, Type: Int32 } +Buffers: + - { Name: Out, Format: Int32, Stride: 4, FillSize: 8 } +DescriptorSets: + - Resources: + - Name: Out + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/duplicate_spec_id.hlsl +# RUN: %offloader %t/duplicate_spec_id.yaml %t.o | FileCheck %s + +# CHECK: Data: [ 123, 123 ] From ed1f8ed0dde8a60d766661db09ce9c01570da604 Mon Sep 17 00:00:00 2001 From: Steven Perron Date: Thu, 13 Nov 2025 09:22:05 -0500 Subject: [PATCH 4/5] Add error if the same spec id is defined twice. --- lib/API/VK/Device.cpp | 8 ++++ .../duplicate_spec_id_in_config.test | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 test/Feature/SpecializationConstant/duplicate_spec_id_in_config.test diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index 3a0471149..96f6051a5 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -11,6 +11,7 @@ #include "API/Device.h" #include "Support/Pipeline.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/Support/Error.h" #include @@ -1407,7 +1408,14 @@ class VKDevice : public offloadtest::Device { llvm::SmallVector SpecData; VkSpecializationInfo SpecInfo = {}; if (!Shader.SpecializationConstants.empty()) { + llvm::DenseSet SeenConstantIDs; for (const auto &SpecConst : Shader.SpecializationConstants) { + if (!SeenConstantIDs.insert(SpecConst.ConstantID).second) + return llvm::createStringError( + std::errc::invalid_argument, + "Specialization constant ID %u is already defined", + SpecConst.ConstantID); + VkSpecializationMapEntry Entry; if (auto Err = parseSpecializationConstant(SpecConst, Entry, SpecData)) diff --git a/test/Feature/SpecializationConstant/duplicate_spec_id_in_config.test b/test/Feature/SpecializationConstant/duplicate_spec_id_in_config.test new file mode 100644 index 000000000..7dd106c83 --- /dev/null +++ b/test/Feature/SpecializationConstant/duplicate_spec_id_in_config.test @@ -0,0 +1,37 @@ +#--- duplicate_spec_id_in_config.hlsl +[[vk::constant_id(0)]] +const int spec_int = 0; + +RWStructuredBuffer Out : register(u0, space0); + +[numthreads(1,1,1)] +void main(uint GI : SV_GroupIndex) { + Out[GI] = spec_int; +} +#--- duplicate_spec_id_in_config.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + SpecializationConstants: + - { ConstantID: 0, Value: 123, Type: Int32 } + - { ConstantID: 0, Value: 456, Type: Int32 } +Buffers: + - { Name: Out, Format: Int32, Stride: 4, FillSize: 4 } +DescriptorSets: + - Resources: + - Name: Out + Kind: RWStructuredBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } +... +#--- end + +# REQUIRES: Vulkan + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/duplicate_spec_id_in_config.hlsl +# RUN: not %offloader %t/duplicate_spec_id_in_config.yaml %t.o 2>&1 | FileCheck %s + +# CHECK: gpu-exec: error: Specialization constant ID 0 is already defined From 880ae6aa0deadf570286e0ef6cd3a12facaea51f Mon Sep 17 00:00:00 2001 From: Steven Perron Date: Thu, 13 Nov 2025 11:11:28 -0500 Subject: [PATCH 5/5] XFAIL tests with short and double. --- test/Feature/SpecializationConstant/invalid_double.test | 3 +++ test/Feature/SpecializationConstant/invalid_int16.test | 3 +++ test/Feature/SpecializationConstant/invalid_uint16.test | 3 +++ .../Feature/SpecializationConstant/spec_const_other_sizes.test | 1 + 4 files changed, 10 insertions(+) diff --git a/test/Feature/SpecializationConstant/invalid_double.test b/test/Feature/SpecializationConstant/invalid_double.test index 2245991bf..f05600526 100644 --- a/test/Feature/SpecializationConstant/invalid_double.test +++ b/test/Feature/SpecializationConstant/invalid_double.test @@ -28,6 +28,9 @@ DescriptorSets: # REQUIRES: Vulkan +# Bug https://github.com/microsoft/DirectXShaderCompiler/issues/7886 +# XFAIL: DXC + # RUN: split-file %s %t # RUN: %dxc_target -T cs_6_2 -Fo %t.o %t/invalid_double.hlsl # RUN: not %offloader %t/invalid_double.yaml %t.o 2>&1 | FileCheck %s diff --git a/test/Feature/SpecializationConstant/invalid_int16.test b/test/Feature/SpecializationConstant/invalid_int16.test index d00f46557..5807e3b17 100644 --- a/test/Feature/SpecializationConstant/invalid_int16.test +++ b/test/Feature/SpecializationConstant/invalid_int16.test @@ -28,6 +28,9 @@ DescriptorSets: # REQUIRES: Vulkan +# Bug https://github.com/microsoft/DirectXShaderCompiler/issues/7886 +# XFAIL: DXC + # RUN: split-file %s %t # RUN: %dxc_target -T cs_6_2 -enable-16bit-types -Fo %t.o %t/invalid_int16.hlsl # RUN: not %offloader %t/invalid_int16.yaml %t.o 2>&1 | FileCheck %s diff --git a/test/Feature/SpecializationConstant/invalid_uint16.test b/test/Feature/SpecializationConstant/invalid_uint16.test index deb9ada47..eed1cb952 100644 --- a/test/Feature/SpecializationConstant/invalid_uint16.test +++ b/test/Feature/SpecializationConstant/invalid_uint16.test @@ -28,6 +28,9 @@ DescriptorSets: # REQUIRES: Vulkan +# Bug https://github.com/microsoft/DirectXShaderCompiler/issues/7886 +# XFAIL: DXC + # RUN: split-file %s %t # RUN: %dxc_target -T cs_6_2 -enable-16bit-types -Fo %t.o %t/invalid_uint16.hlsl # RUN: not %offloader %t/invalid_uint16.yaml %t.o 2>&1 | FileCheck %s diff --git a/test/Feature/SpecializationConstant/spec_const_other_sizes.test b/test/Feature/SpecializationConstant/spec_const_other_sizes.test index 1edaa3b48..b60ed5658 100644 --- a/test/Feature/SpecializationConstant/spec_const_other_sizes.test +++ b/test/Feature/SpecializationConstant/spec_const_other_sizes.test @@ -53,6 +53,7 @@ DescriptorSets: # REQUIRES: Vulkan +# Bug https://github.com/microsoft/DirectXShaderCompiler/issues/7886 # XFAIL: DXC # RUN: split-file %s %t