From 7eb7c0af2bcca60861bd345e94335c33c6ff026d Mon Sep 17 00:00:00 2001 From: Steve Urquhart Date: Thu, 23 Apr 2026 13:31:56 -0400 Subject: [PATCH] [SPIRV] Allow globallycoherent on DescriptorHeap accesses (#7740) --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 60 +++++++++++++++++++ tools/clang/lib/SPIRV/SpirvEmitter.h | 4 ++ ...sm6_6.descriptorheap.globallycoherent.hlsl | 54 +++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.globallycoherent.hlsl diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 288b926f24..18a65fb5aa 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -32,6 +32,7 @@ #include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/SaveAndRestore.h" #ifdef SUPPORT_QUERY_GIT_COMMIT_INFO #include "clang/Basic/Version.h" @@ -1559,6 +1560,9 @@ bool SpirvEmitter::handleNodePayloadArrayType(const ParmVarDecl *decl, } void SpirvEmitter::doFunctionDecl(const FunctionDecl *decl) { + llvm::SaveAndRestore savedCurFunctionDecl( + curFunctionDecl, decl); + // Forward declaration of a function inside another. if (!decl->isThisDeclarationADefinition()) { addFunctionToWorkQueue(spvContext.getCurrentShaderModelKind(), decl, @@ -6658,6 +6662,62 @@ SpirvEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr, auto *decl = cast(declRefExpr->getDecl()); auto *var = declIdMapper.createResourceHeap(decl, resourceType); + // Decide whether the destination of this heap access is + // globallycoherent. Three sources: + // (a) An enclosing DeclStmt VarDecl initializer + // (b) A file-scope VarDecl initializer + // (c) A ReturnStmt inside a globallycoherent returning helper + auto isCoherentDest = [](const VarDecl *vd) { + return vd && vd->hasAttr(); + }; + + bool destIsCoherent = false; + + // (a) and (c): walk Stmt parents through transparent wrappers. + { + const Stmt *cur = parentExpr; + while (cur) { + const Stmt *p = parentMap->getParent(cur); + if (!p) + break; + if (isa(p) || isa(p)) { + cur = p; + continue; + } + if (const auto *ds = dyn_cast(p)) { + for (const Decl *d : ds->decls()) + if (const auto *vd = dyn_cast(d)) + if (isCoherentDest(vd)) { + destIsCoherent = true; + break; + } + } else if (isa(p) && curFunctionDecl && + curFunctionDecl->hasAttr()) { + destIsCoherent = true; + } + break; + } + } + // (b) File-scope init: VarDecl is the AST parent of the top cast. + if (!destIsCoherent) { + const Expr *top = parentExpr; + while (true) { + auto parents = astContext.getParents(*top); + if (parents.empty()) + break; + if (const auto *castParent = parents[0].get()) { + top = castParent; + continue; + } + if (const auto *vd = parents[0].get()) { + if (isCoherentDest(vd)) + destIsCoherent = true; + } + break; + } + } + if (destIsCoherent) + spvBuilder.decorateCoherent(var, baseExpr->getExprLoc()); auto *index = doExpr(indexExpr); if (spirvOptions.useDescriptorHeap) { diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 10cc31023c..0228c2ed1b 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -1648,6 +1648,10 @@ class SpirvEmitter : public ASTConsumer { /// ParentMap of the current function. std::unique_ptr parentMap = nullptr; + + /// AST FunctionDecl currently being lowered. Used by the descriptor-heap + /// access path to detect globallycoherent returning helpers. + const FunctionDecl *curFunctionDecl = nullptr; }; void SpirvEmitter::doDeclStmt(const DeclStmt *declStmt) { diff --git a/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.globallycoherent.hlsl b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.globallycoherent.hlsl new file mode 100644 index 0000000000..ac3eb81f32 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/sm6_6.descriptorheap.globallycoherent.hlsl @@ -0,0 +1,54 @@ +// RUN: %dxc -T cs_6_6 -E CSMain -spirv -DTYPE=0 %s | FileCheck %s --check-prefix=TYPE0 +// RUN: %dxc -T cs_6_6 -E CSMain -spirv -DTYPE=1 %s | FileCheck %s --check-prefix=TYPE1 +// RUN: %dxc -T cs_6_6 -E CSMain -spirv -DTYPE=2 %s | FileCheck %s --check-prefix=TYPE2 +// RUN: %dxc -T cs_6_6 -E CSMain -spirv -DTYPE=3 %s | FileCheck %s --check-prefix=TYPE3 + +// TYPE=0: direct ResourceDescriptorHeap[] init of a `globallycoherent` static. +// TYPE0-DAG: OpDecorate %ResourceDescriptorHeap{{(_[0-9]+)?}} DescriptorSet 0 +// TYPE0-DAG: OpDecorate %ResourceDescriptorHeap{{(_[0-9]+)?}} Binding 0 +// TYPE0-DAG: OpDecorate %ResourceDescriptorHeap{{(_[0-9]+)?}} Coherent + +// TYPE=1: bindless array carries the qualifier. +// TYPE1-DAG: OpDecorate %FakeHeapOfA DescriptorSet 0 +// TYPE1-DAG: OpDecorate %FakeHeapOfA Binding 0 +// TYPE1-DAG: OpDecorate %FakeHeapOfA Coherent + +// TYPE=2: stand-alone `globallycoherent` UAV. +// TYPE2-DAG: OpDecorate %A DescriptorSet 0 +// TYPE2-DAG: OpDecorate %A Binding 0 +// TYPE2-DAG: OpDecorate %A Coherent + +// TYPE=3: heap access lives inside a `globallycoherent`-returning helper, and +// a `globallycoherent static` captures the result. +// TYPE3-DAG: OpDecorate %ResourceDescriptorHeap{{(_[0-9]+)?}} DescriptorSet 0 +// TYPE3-DAG: OpDecorate %ResourceDescriptorHeap{{(_[0-9]+)?}} Binding {{[0-9]+}} +// TYPE3-DAG: OpDecorate %ResourceDescriptorHeap{{(_[0-9]+)?}} Coherent +// TYPE3-NOT: OpDecorate %Buf Coherent + +#if TYPE == 0 +globallycoherent static RWStructuredBuffer A = ResourceDescriptorHeap[0]; +#elif TYPE == 1 +globallycoherent RWStructuredBuffer FakeHeapOfA[]; +globallycoherent static RWStructuredBuffer A = FakeHeapOfA[0]; +#elif TYPE == 2 +globallycoherent RWStructuredBuffer A; +#elif TYPE == 3 +uint BindlessUAV_Buf; +typedef RWByteAddressBuffer SafeTypeBuf; +globallycoherent SafeTypeBuf GetBuf() { return ResourceDescriptorHeap[BindlessUAV_Buf]; } +static const globallycoherent SafeTypeBuf A = GetBuf(); +#endif + +[numthreads(64, 1, 1)] +void CSMain(uint3 ThreadId : SV_DispatchThreadId) +{ +#if TYPE == 3 + A.InterlockedAdd(0, 1); + AllMemoryBarrierWithGroupSync(); + A.Store(0, 42); +#else + InterlockedAdd(A[0], 1); + AllMemoryBarrierWithGroupSync(); + InterlockedAdd(A[1], A[0]); +#endif +} \ No newline at end of file