From 6df3664c86b6622402ada4c5206a31caf99afb06 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Mon, 10 Aug 2026 13:03:07 -0500 Subject: [PATCH 1/3] Fix crash when heap subscript result is discarded Without this change DXC crashes if the result of a descriptor heap subscript is discarded. The crash is caused by not correctly handling lifetime marker intrinsics when rewriting resources that otherwise have no uses. The fix here ignores bitcasts to i8* which are associated with lifetime markers when rewriting resource accesses. This fix is a targeted "avoid the crash" fix, rather than wholistic since this rewriting _really_ shouldn't be happening at the end of IR generation, it should likely be in a separate IR pass. Assisted by: Copilot, Claude Sonnet 5. ../tools/clang/test/CodeGenDXIL/discarded_dynamic_res.hlsl --- .../lib/CodeGen/CGHLSLMSFinishCodeGen.cpp | 19 +++++++- .../CodeGenDXIL/discarded_dynamic_res.hlsl | 48 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tools/clang/test/CodeGenDXIL/discarded_dynamic_res.hlsl diff --git a/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp b/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp index 13edadf9df..e3f8bc509b 100644 --- a/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp +++ b/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp @@ -12,6 +12,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Analysis/DxilValueCache.h" +#include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/CFG.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Function.h" @@ -330,6 +331,19 @@ void LowerGetResourceFromHeap( for (auto uit = resPtr->user_begin(); uit != resPtr->user_end();) { User *U = *(uit++); BitCastInst *BCI = cast(U); + // If the temporary resource variable is never actually read (e.g. its + // value is discarded, as in `ResourceDescriptorHeap[i];`), clang still + // emits lifetime markers for it. Those markers use an i8* bitcast of + // the resource pointer rather than a bitcast to the resource type. + // Leave them alone here: they are still valid uses of resPtr, and + // later legalization passes (which already know how to handle + // bitcasts that are onlyUsedByLifetimeMarkers) will clean them up + // once resPtr has no other real uses. + if (BCI->getType()->getPointerElementType()->isIntegerTy(8)) { + DXASSERT(onlyUsedByLifetimeMarkers(BCI), + "otherwise, unexpected use of i8* cast of resource ptr"); + continue; + } DXASSERT( dxilutil::IsHLSLResourceType( BCI->getType()->getPointerElementType()) || @@ -348,7 +362,10 @@ void LowerGetResourceFromHeap( } BCI->eraseFromParent(); } - resPtr->eraseFromParent(); + // resPtr may still have lifetime-marker-only bitcast uses left in place + // above; only erase it once it has no remaining uses. + if (resPtr->use_empty()) + resPtr->eraseFromParent(); } } diff --git a/tools/clang/test/CodeGenDXIL/discarded_dynamic_res.hlsl b/tools/clang/test/CodeGenDXIL/discarded_dynamic_res.hlsl new file mode 100644 index 0000000000..b632762130 --- /dev/null +++ b/tools/clang/test/CodeGenDXIL/discarded_dynamic_res.hlsl @@ -0,0 +1,48 @@ +// RUN: %dxc -T cs_6_6 -fcgl %s | FileCheck %s +// RUN: %dxc -T cs_6_6 -Od -fcgl %s | FileCheck --check-prefix=NO_LIFETIMES %s + +// Regression test: indexing into a resource heap (or sampler heap) as a +// discarded-value expression (i.e. its result is never read) used to crash +// the compiler. Clang still emits an alloca plus lifetime markers for the +// implicit temporary in that case, and CGHLSLMSHelper::LowerGetResourceFromHeap +// only expected the resource pointer to be used through a resource-typed +// bitcast that is loaded from; it did not expect the i8* bitcast used by +// llvm.lifetime.start/llvm.lifetime.end, which triggered an invalid cast<> +// (see FinishIntrinsics -> LowerGetResourceFromHeap). + +// When `-Od` is passed lifetime markers are implicitly disabled, so the +// remainder of the IR changes are irrelevant. +// NO_LIFETIMES: define void @main + +// CHECK: [[ResourceX:%.*]] = alloca %struct..Resource +// CHECK: [[SamplerX:%.*]] = alloca %struct..Sampler +// CHECK: [[ResourceY:%.*]] = alloca %struct..Resource +// CHECK: [[SamplerY:%.*]] = alloca %struct..Sampler + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) { + // CHECK: [[Start:%.*]] = bitcast %struct..Resource* [[ResourceX]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start(i64 4, i8* [[Start]]) + // CHECK: [[End:%.*]] = bitcast %struct..Resource* [[ResourceX]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.end(i64 4, i8* [[End]]) + ResourceDescriptorHeap[tid.x]; + + // CHECK: [[Start:%.*]] = bitcast %struct..Sampler* [[SamplerX]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start(i64 4, i8* [[Start]]) + // CHECK: [[End:%.*]] = bitcast %struct..Sampler* [[SamplerX]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.end(i64 4, i8* [[End]]) + SamplerDescriptorHeap[tid.x]; + + + // CHECK: [[Start:%.*]] = bitcast %struct..Resource* [[ResourceY]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start(i64 4, i8* [[Start]]) + // CHECK: [[End:%.*]] = bitcast %struct..Resource* [[ResourceY]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.end(i64 4, i8* [[End]]) + (void)ResourceDescriptorHeap[tid.y]; + + // CHECK: [[Start:%.*]] = bitcast %struct..Sampler* [[SamplerY]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start(i64 4, i8* [[Start]]) + // CHECK: [[End:%.*]] = bitcast %struct..Sampler* [[SamplerY]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.end(i64 4, i8* [[End]]) + (void)SamplerDescriptorHeap[tid.y]; +} From d5534305ae74aea5b173c1090c84b07ec581e7c9 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Mon, 10 Aug 2026 14:50:56 -0500 Subject: [PATCH 2/3] Cleanup comments --- tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp b/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp index e3f8bc509b..7aa5eb4ad4 100644 --- a/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp +++ b/tools/clang/lib/CodeGen/CGHLSLMSFinishCodeGen.cpp @@ -331,14 +331,8 @@ void LowerGetResourceFromHeap( for (auto uit = resPtr->user_begin(); uit != resPtr->user_end();) { User *U = *(uit++); BitCastInst *BCI = cast(U); - // If the temporary resource variable is never actually read (e.g. its - // value is discarded, as in `ResourceDescriptorHeap[i];`), clang still - // emits lifetime markers for it. Those markers use an i8* bitcast of - // the resource pointer rather than a bitcast to the resource type. - // Leave them alone here: they are still valid uses of resPtr, and - // later legalization passes (which already know how to handle - // bitcasts that are onlyUsedByLifetimeMarkers) will clean them up - // once resPtr has no other real uses. + // Ignore uses of the resource which are just bitcasts to i8* for lifetime + // markers. These will get cleaned up in later legalization. if (BCI->getType()->getPointerElementType()->isIntegerTy(8)) { DXASSERT(onlyUsedByLifetimeMarkers(BCI), "otherwise, unexpected use of i8* cast of resource ptr"); @@ -362,8 +356,8 @@ void LowerGetResourceFromHeap( } BCI->eraseFromParent(); } - // resPtr may still have lifetime-marker-only bitcast uses left in place - // above; only erase it once it has no remaining uses. + // Only erase the resource if it has no remaining uses. The correct fix here + // is to just not generate these resources, but that is a larger change. if (resPtr->use_empty()) resPtr->eraseFromParent(); } From 1bc59f0d57580bb8202f77dc8923277c179d7135 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Mon, 10 Aug 2026 14:54:51 -0500 Subject: [PATCH 3/3] Add release note --- docs/ReleaseNotes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 19c0d75174..75eba81c57 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -30,6 +30,9 @@ line upon naming the release. Refer to previous for appropriate section names. - SPIR-V: Fixed an invalid `OpSelect` being generated when optimizing for SPIR-V 1.3 and earlier [#8603](https://github.com/microsoft/DirectXShaderCompiler/issues/8603). +- Fix a crash generating DXIL from sources containing a dynamic resource heap + access that was discarded. Identified during development of SPIR-V support for + [descriptor heaps](https://github.com/microsoft/DirectXShaderCompiler/pull/8517#discussion_r3752113078). #### HLSL Language