From fe6cad44fe7c46db299a9c9e6995b607cc06cb99 Mon Sep 17 00:00:00 2001 From: mmoult <30711895+mmoult@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:56:02 -0500 Subject: [PATCH] [SPIR-V] Honor inline spir-v attributes on functions The [[vk::ext_decorate]], [[vk::ext_capability]], and [[vk::ext_extension]] attributes were only applied to variables, parameters, stage variables, entry-point functions, and functions carrying [[vk::ext_instruction]]. A plain function that carried them was skipped, so the attributes were silently dropped: no OpDecorate was emitted for its OpFunction, and no OpCapability / OpExtension was added to the module. Apply them when the function is registered (getOrRegisterFn), next to the existing linkage decoration. This reuses the same helpers as the variable and parameter paths: - the decoration goes through a new SpirvFunction overload of decorateWithIntrinsicAttrs, and - the capability/extension loops are factored into registerCapabilitiesAndExtensionsForDecl, now shared with the variable path. Only the literal form of ext_decorate is supported on functions; the id and string variants have no SpirvFunction-target decoration and are now diagnosed rather than silently dropped. --- .../clang/include/clang/SPIRV/SpirvBuilder.h | 6 +++ tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 45 +++++++++++++++++++ tools/clang/lib/SPIRV/DeclResultIdMapper.h | 13 ++++++ tools/clang/lib/SPIRV/SpirvBuilder.cpp | 10 +++++ tools/clang/lib/SPIRV/SpirvEmitter.cpp | 12 +---- .../spv.intrinsicDecorate.function.error.hlsl | 17 +++++++ .../spv.intrinsicDecorate.function.hlsl | 28 ++++++++++++ 7 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.error.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.hlsl diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index dba103207c..2e697a94e0 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -801,6 +801,12 @@ class SpirvBuilder { void decorateWithLiterals(SpirvInstruction *targetInst, unsigned decorate, llvm::ArrayRef literals, SourceLocation); + /// \brief Decorates the given function with information from VKDecorateExt. + /// Used for [[vk::ext_decorate(...)]] placed directly on a function, which + /// emits an OpDecorate targeting the OpFunction. + void decorateWithLiterals(SpirvFunction *targetFunc, unsigned decorate, + llvm::ArrayRef literals, SourceLocation); + /// \brief Decorates the given target with result ids of SPIR-V /// instructions. void decorateWithIds(SpirvInstruction *targetInst, unsigned decorate, diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 1a1078bf0b..dea3569754 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -1826,6 +1826,17 @@ SpirvFunction *DeclResultIdMapper::getOrRegisterFn(const FunctionDecl *fn) { spv::LinkageType::Export, fn->getLocation()); } + // Honor inline-SPIR-V attributes placed directly on a function. The + // entry-point path handles these only for entry functions, and the + // vk::ext_instruction path only for functions lowered to an instruction, so a + // plain function was previously skipped and these attributes silently + // dropped. These reuse the same helpers as the variable/parameter paths: + // [[vk::ext_decorate(d, ...)]] -> OpDecorate targeting the OpFunction + // [[vk::ext_capability(c)]] -> OpCapability for the module + // [[vk::ext_extension("...")]] -> OpExtension for the module + decorateWithIntrinsicAttrs(fn, spirvFunction); + registerCapabilitiesAndExtensionsForDecl(fn); + // No need to dereference to get the pointer. Function returns that are // stand-alone aliases are already pointers to values. All other cases should // be normal rvalues. @@ -5047,6 +5058,29 @@ void DeclResultIdMapper::decorateWithIntrinsicAttrs( } } +void DeclResultIdMapper::decorateWithIntrinsicAttrs(const NamedDecl *decl, + SpirvFunction *targetFunc) { + if (!decl->hasAttrs()) + return; + + for (auto &attr : decl->getAttrs()) { + if (auto *decoAttr = dyn_cast(attr)) { + spvBuilder.decorateWithLiterals( + targetFunc, decoAttr->getDecorate(), + {decoAttr->literals_begin(), decoAttr->literals_end()}, + decl->getLocation()); + continue; + } + // The id/string forms decorate a SpirvInstruction target; there is no + // SpirvFunction-target equivalent yet, so reject rather than silently drop. + if (isa(attr) || isa(attr)) { + emitError("vk::ext_decorate_id and vk::ext_decorate_string are not " + "supported on functions", + decl->getLocation()); + } + } +} + void DeclResultIdMapper::decorateStageVarWithIntrinsicAttrs( const NamedDecl *decl, StageVar *stageVar, SpirvVariable *varInst) { auto checkBuiltInLocationDecoration = @@ -5136,6 +5170,17 @@ void DeclResultIdMapper::storeOutStageVarsToStorage( } } +void DeclResultIdMapper::registerCapabilitiesAndExtensionsForDecl( + const NamedDecl *decl) { + for (auto *attribute : decl->specific_attrs()) { + spvBuilder.requireExtension(attribute->getName(), decl->getLocation()); + } + for (auto *attribute : decl->specific_attrs()) { + spv::Capability cap = spv::Capability(attribute->getCapability()); + spvBuilder.requireCapability(cap, decl->getLocation()); + } +} + void DeclResultIdMapper::registerCapabilitiesAndExtensionsForType( const TypedefType *type) { for (const auto *decl : typeAliasesWithAttributes) { diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.h b/tools/clang/lib/SPIRV/DeclResultIdMapper.h index 6e8177edf2..adfded231e 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.h +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.h @@ -576,6 +576,13 @@ class DeclResultIdMapper { llvm::function_ref extraFunctionForDecoAttr = [](VKDecorateExtAttr *) {}); + /// Applies [[vk::ext_decorate(...)]] placed directly on a function to the + /// function itself, emitting an OpDecorate that targets the OpFunction. Only + /// the literal form is supported; the id/string variants are diagnosed as + /// unsupported on functions (they lack a SpirvFunction-target decoration). + void decorateWithIntrinsicAttrs(const NamedDecl *decl, + SpirvFunction *targetFunc); + /// \brief Creates instructions to load the value of output stage variable /// defined by outputPatchDecl and store it to ptr. Since the output stage /// variable for OutputPatch is an array whose number of elements is the @@ -589,6 +596,12 @@ class DeclResultIdMapper { spv::ExecutionMode getInterlockExecutionMode(); + /// Records any Spir-V capabilities and extensions declared directly on the + /// given decl via [[vk::ext_capability(...)]] / [[vk::ext_extension(...)]] + /// so they will be added to the SPIR-V module. Shared by the variable and + /// function declaration paths. + void registerCapabilitiesAndExtensionsForDecl(const NamedDecl *decl); + /// Records any Spir-V capabilities and extensions for the given type so /// they will be added to the SPIR-V module. The capabilities and extension /// required for the type will be sourced from the decls that were recorded diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index f61d61ed71..d8eceaccc6 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -1949,6 +1949,16 @@ void SpirvBuilder::decorateWithLiterals(SpirvInstruction *targetInst, mod->addDecoration(decor); } +void SpirvBuilder::decorateWithLiterals(SpirvFunction *targetFunc, + unsigned decorate, + llvm::ArrayRef literals, + SourceLocation srcLoc) { + SpirvDecoration *decor = new (context) SpirvDecoration( + srcLoc, targetFunc, static_cast(decorate), literals); + assert(decor != nullptr); + mod->addDecoration(decor); +} + void SpirvBuilder::decorateWithIds(SpirvInstruction *targetInst, unsigned decorate, llvm::ArrayRef ids, diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index b7fd447787..8cd7f85747 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -1924,16 +1924,8 @@ bool SpirvEmitter::validateVKAttributes(const NamedDecl *decl) { void SpirvEmitter::registerCapabilitiesAndExtensionsForVarDecl( const VarDecl *varDecl) { - // First record any extensions that are part of the actual variable - // declaration. - for (auto *attribute : varDecl->specific_attrs()) { - clang::StringRef extensionName = attribute->getName(); - spvBuilder.requireExtension(extensionName, varDecl->getLocation()); - } - for (auto *attribute : varDecl->specific_attrs()) { - spv::Capability cap = spv::Capability(attribute->getCapability()); - spvBuilder.requireCapability(cap, varDecl->getLocation()); - } + // First record any extensions/capabilities declared on the variable itself. + declIdMapper.registerCapabilitiesAndExtensionsForDecl(varDecl); // Now check for any capabilities or extensions that are part of the type. const TypedefType *type = dyn_cast(varDecl->getType()); diff --git a/tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.error.hlsl b/tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.error.hlsl new file mode 100644 index 0000000000..da92a80b3c --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.error.hlsl @@ -0,0 +1,17 @@ +// RUN: not %dxc -T cs_6_0 -E main -fcgl %s -spirv 2>&1 | FileCheck %s + +// vk::ext_decorate_id and vk::ext_decorate_string decorate a value-id target +// and have no OpFunction-target form, so applying them to a function must be +// diagnosed rather than silently dropped. + +[[vk::ext_decorate_string(/* UserTypeGOOGLE */ 5636, "myType")]] +[noinline] uint Identity(uint x) { return x; } + +// CHECK: error: vk::ext_decorate_id and vk::ext_decorate_string are not supported on functions + +RWStructuredBuffer buf; + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + buf[0] = Identity(tid.x); +} diff --git a/tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.hlsl b/tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.hlsl new file mode 100644 index 0000000000..a70225f65a --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/inline-spirv/spv.intrinsicDecorate.function.hlsl @@ -0,0 +1,28 @@ +// RUN: %dxc -T cs_6_0 -E main -fcgl -Vd %s -spirv | FileCheck %s + +// Inline-SPIR-V attributes placed directly on a function must be honored: +// vk::ext_decorate emits an OpDecorate targeting the OpFunction, and +// vk::ext_capability / vk::ext_extension add the capability / extension to the +// module. Known SPIR-V enums are used here so the disassembler can render them; +// the mechanism is identical for vendor-specific decoration numbers. + +// CHECK-DAG: OpCapability Int8 +// CHECK-DAG: OpExtension "some_extension" + +[[vk::ext_capability(/* Int8 */ 39)]] +[[vk::ext_extension("some_extension")]] +[[vk::ext_decorate(/* RelaxedPrecision */ 0)]] +[[vk::ext_decorate(/* Alignment */ 44, 16)]] +[noinline] uint Identity(uint x) { return x; } + +// CHECK-DAG: OpDecorate %Identity RelaxedPrecision +// CHECK-DAG: OpDecorate %Identity Alignment 16 + +// CHECK: %Identity = OpFunction %uint DontInline + +RWStructuredBuffer buf; + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + buf[0] = Identity(tid.x); +}