Skip to content

[DirectX] Legalize llvm.lifetime.* intrinsics in EmbedDXILPass #150100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion llvm/lib/Target/DirectX/DXILShaderFlags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ void ModuleShaderFlags::updateFunctionFlags(ComputedShaderFlags &CSF,
if (!CSF.Int64Ops)
CSF.Int64Ops = I.getType()->isIntegerTy(64);

if (!CSF.Int64Ops) {
if (!CSF.Int64Ops && !isa<LifetimeIntrinsic>(&I)) {
for (const Value *Op : I.operands()) {
if (Op->getType()->isIntegerTy(64)) {
CSF.Int64Ops = true;
Expand Down
55 changes: 55 additions & 0 deletions llvm/lib/Target/DirectX/DXILWriter/DXILWriterPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "llvm/Analysis/ModuleSummaryAnalysis.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/InitializePasses.h"
Expand Down Expand Up @@ -52,6 +53,51 @@ class WriteDXILPass : public llvm::ModulePass {
}
};

static void legalizeLifetimeIntrinsics(Module &M) {
for (Function &F : M) {
Intrinsic::ID IID = F.getIntrinsicID();
if (IID != Intrinsic::lifetime_start && IID != Intrinsic::lifetime_end)
continue;

// Lifetime intrinsics in LLVM 3.7 do not have the memory FnAttr
F.removeFnAttr(Attribute::Memory);

// Lifetime intrinsics in LLVM 3.7 do not have mangled names
F.setName(Intrinsic::getBaseName(IID));

// LLVM 3.7 Lifetime intrinics require an i8* operand, so we insert bitcasts
// to ensure that is the case
for (auto *User : make_early_inc_range(F.users())) {
CallInst *CI = dyn_cast<CallInst>(User);
assert(CI && "Expected user of a lifetime intrinsic function to be a "
"lifetime intrinsic call");
Value *PtrOperand = CI->getArgOperand(1);
PointerType *PtrTy = cast<PointerType>(PtrOperand->getType());
Value *NoOpBitCast = CastInst::Create(Instruction::BitCast, PtrOperand,
PtrTy, "", CI->getIterator());
CI->setArgOperand(1, NoOpBitCast);
}
}
}

static void removeLifetimeIntrinsics(Module &M) {
for (Function &F : make_early_inc_range(M))
if (Intrinsic::ID IID = F.getIntrinsicID();
IID == Intrinsic::lifetime_start || IID == Intrinsic::lifetime_end) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd usually recommend inverting the condition and having a continue here instead of nesting further. That matches what you did above and aligns with the coding standards.

https://llvm.org/docs/CodingStandards.html#use-early-exits-and-continue-to-simplify-code

for (User *U : make_early_inc_range(F.users())) {
LifetimeIntrinsic *LI = dyn_cast<LifetimeIntrinsic>(U);
assert(LI && "Expected user of lifetime intrinsic function to be "
"a LifetimeIntrinsic instruction");
BitCastInst *BCI = dyn_cast<BitCastInst>(LI->getArgOperand(1));
assert(BCI && "Expected pointer operand of LifetimeIntrinsic to be a "
"BitCastInst");
LI->eraseFromParent();
BCI->eraseFromParent();
}
F.eraseFromParent();
}
}

class EmbedDXILPass : public llvm::ModulePass {
public:
static char ID; // Pass identification, replacement for typeid
Expand All @@ -70,8 +116,17 @@ class EmbedDXILPass : public llvm::ModulePass {
// Only the output bitcode need to be DXIL triple.
M.setTargetTriple(Triple("dxil-ms-dx"));

// Perform late legalization of lifetime intrinsics that would otherwise
// fail the Module Verifier if performed in an earlier pass
legalizeLifetimeIntrinsics(M);

WriteDXILToFile(M, OS);

// We no longer need lifetime intrinsics after bitcode serialization, so we
// simply remove them to keep the Module Verifier happy after our
// not-so-legal legalizations
removeLifetimeIntrinsics(M);

// Recover triple.
M.setTargetTriple(OriginalTriple);

Expand Down
36 changes: 36 additions & 0 deletions llvm/test/CodeGen/DirectX/ShaderFlags/lifetimes-noint64op.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
; RUN: opt -S --passes="print-dx-shader-flags" 2>&1 %s | FileCheck %s
; RUN: llc %s --filetype=obj -o - | obj2yaml | FileCheck %s --check-prefix=DXC

target triple = "dxil-pc-shadermodel6.7-library"

; CHECK: ; Combined Shader Flags for Module
; CHECK-NEXT: ; Shader Flags Value: 0x00000000
; CHECK-NEXT: ;
; CHECK-NOT: ; Note: shader requires additional functionality:
; CHECK-NOT: ; 64-Bit integer
; CHECK-NOT: ; Note: extra DXIL module flags:
; CHECK-NOT: ;
; CHECK-NEXT: ; Shader Flags for Module Functions
; CHECK-NEXT: ; Function lifetimes : 0x00000000

define void @lifetimes() #0 {
%a = alloca [4 x i32], align 8
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %a)
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %a)
ret void
}

; Function Attrs: nounwind memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64, ptr) #1

; Function Attrs: nounwind memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64, ptr) #1

attributes #0 = { convergent norecurse nounwind "hlsl.export"}
attributes #1 = { nounwind memory(argmem: readwrite) }

; DXC: - Name: SFI0
; DXC-NEXT: Size: 8
; DXC-NOT: Flags:
; DXC-NOT: Int64Ops: true
; DXC: ...
38 changes: 38 additions & 0 deletions llvm/test/tools/dxil-dis/lifetimes.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
; RUN: llc --filetype=obj %s -o - | dxil-dis -o - | FileCheck %s
target triple = "dxil-unknown-shadermodel6.7-library"

define void @test_lifetimes() {
; CHECK-LABEL: test_lifetimes
; CHECK-NEXT: [[ALLOCA:%.*]] = alloca [2 x i32], align 4
; CHECK-NEXT: [[GEP:%.*]] = getelementptr [2 x i32], [2 x i32]* [[ALLOCA]], i32 0, i32 0
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast [2 x i32]* [[ALLOCA]] to i8*
; CHECK-NEXT: call void @llvm.lifetime.start(i64 4, i8* nonnull [[BITCAST]])
; CHECK-NEXT: store i32 0, i32* [[GEP]], align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast [2 x i32]* [[ALLOCA]] to i8*
; CHECK-NEXT: call void @llvm.lifetime.end(i64 4, i8* nonnull [[BITCAST]])
; CHECK-NEXT: ret void
;
%a = alloca [2 x i32], align 4
%gep = getelementptr [2 x i32], ptr %a, i32 0, i32 0
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %a)
store i32 0, ptr %gep, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %a)
ret void
}

; CHECK-DAG: attributes [[LIFETIME_ATTRS:#.*]] = { nounwind }

; CHECK-DAG: ; Function Attrs: nounwind
; CHECK-DAG: declare void @llvm.lifetime.start(i64, i8* nocapture) [[LIFETIME_ATTRS]]

; CHECK-DAG: ; Function Attrs: nounwind
; CHECK-DAG: declare void @llvm.lifetime.end(i64, i8* nocapture) [[LIFETIME_ATTRS]]

; Function Attrs: nounwind memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64, ptr) #0

; Function Attrs: nounwind memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64, ptr) #0

attributes #0 = { nounwind memory(argmem: readwrite) }

Loading