Skip to content

Commit c3c72c1

Browse files
authored
[DirectX] Legalize llvm.lifetime.* intrinsics in EmbedDXILPass (#150100)
Fixes #147395 This PR: - Excludes lifetime intrinsics from the Int64Ops shader flags analysis to match DXC behavior and pass DXIL validation. - Performs legalization of `llvm.lifetime.*` intrinsics in the EmbedDXILPass just before invoking the DXILBitcodeWriter. - After invoking the DXILBitcodeWriter, all lifetime intrinsics and associated bitcasts are removed from the module to keep the Module Verifier happy. This is fine since lifetime intrinsics are not needed by any passes after the EmbedDXILPass.
1 parent 7fc6556 commit c3c72c1

File tree

5 files changed

+152
-15
lines changed

5 files changed

+152
-15
lines changed

llvm/lib/Target/DirectX/DXILShaderFlags.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ void ModuleShaderFlags::updateFunctionFlags(ComputedShaderFlags &CSF,
152152
if (!CSF.Int64Ops)
153153
CSF.Int64Ops = I.getType()->isIntegerTy(64);
154154

155-
if (!CSF.Int64Ops) {
155+
if (!CSF.Int64Ops && !isa<LifetimeIntrinsic>(&I)) {
156156
for (const Value *Op : I.operands()) {
157157
if (Op->getType()->isIntegerTy(64)) {
158158
CSF.Int64Ops = true;

llvm/lib/Target/DirectX/DXILWriter/DXILWriterPass.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "llvm/Analysis/ModuleSummaryAnalysis.h"
1818
#include "llvm/IR/Constants.h"
1919
#include "llvm/IR/GlobalVariable.h"
20+
#include "llvm/IR/IntrinsicInst.h"
2021
#include "llvm/IR/Module.h"
2122
#include "llvm/IR/PassManager.h"
2223
#include "llvm/InitializePasses.h"
@@ -52,6 +53,53 @@ class WriteDXILPass : public llvm::ModulePass {
5253
}
5354
};
5455

56+
static void legalizeLifetimeIntrinsics(Module &M) {
57+
for (Function &F : M) {
58+
Intrinsic::ID IID = F.getIntrinsicID();
59+
if (IID != Intrinsic::lifetime_start && IID != Intrinsic::lifetime_end)
60+
continue;
61+
62+
// Lifetime intrinsics in LLVM 3.7 do not have the memory FnAttr
63+
F.removeFnAttr(Attribute::Memory);
64+
65+
// Lifetime intrinsics in LLVM 3.7 do not have mangled names
66+
F.setName(Intrinsic::getBaseName(IID));
67+
68+
// LLVM 3.7 Lifetime intrinics require an i8* operand, so we insert bitcasts
69+
// to ensure that is the case
70+
for (auto *User : make_early_inc_range(F.users())) {
71+
CallInst *CI = dyn_cast<CallInst>(User);
72+
assert(CI && "Expected user of a lifetime intrinsic function to be a "
73+
"lifetime intrinsic call");
74+
Value *PtrOperand = CI->getArgOperand(1);
75+
PointerType *PtrTy = cast<PointerType>(PtrOperand->getType());
76+
Value *NoOpBitCast = CastInst::Create(Instruction::BitCast, PtrOperand,
77+
PtrTy, "", CI->getIterator());
78+
CI->setArgOperand(1, NoOpBitCast);
79+
}
80+
}
81+
}
82+
83+
static void removeLifetimeIntrinsics(Module &M) {
84+
for (Function &F : make_early_inc_range(M)) {
85+
if (Intrinsic::ID IID = F.getIntrinsicID();
86+
IID != Intrinsic::lifetime_start && IID != Intrinsic::lifetime_end)
87+
continue;
88+
89+
for (User *U : make_early_inc_range(F.users())) {
90+
LifetimeIntrinsic *LI = dyn_cast<LifetimeIntrinsic>(U);
91+
assert(LI && "Expected user of lifetime intrinsic function to be "
92+
"a LifetimeIntrinsic instruction");
93+
BitCastInst *BCI = dyn_cast<BitCastInst>(LI->getArgOperand(1));
94+
assert(BCI && "Expected pointer operand of LifetimeIntrinsic to be a "
95+
"BitCastInst");
96+
LI->eraseFromParent();
97+
BCI->eraseFromParent();
98+
}
99+
F.eraseFromParent();
100+
}
101+
}
102+
55103
class EmbedDXILPass : public llvm::ModulePass {
56104
public:
57105
static char ID; // Pass identification, replacement for typeid
@@ -70,8 +118,17 @@ class EmbedDXILPass : public llvm::ModulePass {
70118
// Only the output bitcode need to be DXIL triple.
71119
M.setTargetTriple(Triple("dxil-ms-dx"));
72120

121+
// Perform late legalization of lifetime intrinsics that would otherwise
122+
// fail the Module Verifier if performed in an earlier pass
123+
legalizeLifetimeIntrinsics(M);
124+
73125
WriteDXILToFile(M, OS);
74126

127+
// We no longer need lifetime intrinsics after bitcode serialization, so we
128+
// simply remove them to keep the Module Verifier happy after our
129+
// not-so-legal legalizations
130+
removeLifetimeIntrinsics(M);
131+
75132
// Recover triple.
76133
M.setTargetTriple(OriginalTriple);
77134

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
; RUN: opt -S --passes="print-dx-shader-flags" 2>&1 %s | FileCheck %s
2+
; RUN: llc %s --filetype=obj -o - | obj2yaml | FileCheck %s --check-prefix=DXC
3+
4+
target triple = "dxil-pc-shadermodel6.7-library"
5+
6+
; CHECK: ; Combined Shader Flags for Module
7+
; CHECK-NEXT: ; Shader Flags Value: 0x00000000
8+
; CHECK-NEXT: ;
9+
; CHECK-NOT: ; Note: shader requires additional functionality:
10+
; CHECK-NOT: ; 64-Bit integer
11+
; CHECK-NOT: ; Note: extra DXIL module flags:
12+
; CHECK-NOT: ;
13+
; CHECK-NEXT: ; Shader Flags for Module Functions
14+
; CHECK-NEXT: ; Function lifetimes : 0x00000000
15+
16+
define void @lifetimes() #0 {
17+
%a = alloca [4 x i32], align 8
18+
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %a)
19+
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %a)
20+
ret void
21+
}
22+
23+
; Function Attrs: nounwind memory(argmem: readwrite)
24+
declare void @llvm.lifetime.start.p0(i64, ptr) #1
25+
26+
; Function Attrs: nounwind memory(argmem: readwrite)
27+
declare void @llvm.lifetime.end.p0(i64, ptr) #1
28+
29+
attributes #0 = { convergent norecurse nounwind "hlsl.export"}
30+
attributes #1 = { nounwind memory(argmem: readwrite) }
31+
32+
; DXC: - Name: SFI0
33+
; DXC-NEXT: Size: 8
34+
; DXC-NOT: Flags:
35+
; DXC-NOT: Int64Ops: true
36+
; DXC: ...

llvm/test/CodeGen/DirectX/legalize-lifetimes-valver-1.6.ll

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
; RUN: opt -S -passes='dxil-op-lower' -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s --check-prefixes=CHECK,CHECK-SM63
22
; RUN: opt -S -passes='dxil-op-lower' -mtriple=dxil-pc-shadermodel6.6-library %s | FileCheck %s --check-prefixes=CHECK,CHECK-SM66
3+
; RUN: opt -S -dxil-prepare -dxil-embed -mtriple=dxil-pc-shadermodel6.6-library %s | FileCheck %s --check-prefixes=CHECK,CHECK-EMBED
4+
5+
; Lifetime intrinsics are not valid prior to shader model 6.6 and are instead
6+
; replaced with undef stores, provided the validator version is 1.6 or greater
7+
8+
; The dxil-embed pass will remove lifetime intrinsics because they transformed
9+
; in a way that is illegal in modern LLVM IR before serializing to DXIL bitcode.
10+
; So we check that no bitcast or lifetime intrinsics remain after dxil-embed
311

412
; CHECK-LABEL: define void @test_legal_lifetime() {
5-
;
6-
; CHECK-SM63-NEXT: [[ACCUM_I_FLAT:%.*]] = alloca [1 x i32], align 4
7-
; CHECK-SM63-NEXT: [[GEP:%.*]] = getelementptr i32, ptr [[ACCUM_I_FLAT]], i32 0
8-
; CHECK-SM63-NEXT: store [1 x i32] undef, ptr [[ACCUM_I_FLAT]], align 4
9-
; CHECK-SM63-NEXT: store i32 0, ptr [[GEP]], align 4
10-
; CHECK-SM63-NEXT: store [1 x i32] undef, ptr [[ACCUM_I_FLAT]], align 4
11-
;
12-
; CHECK-SM66-NEXT: [[ACCUM_I_FLAT:%.*]] = alloca [1 x i32], align 4
13-
; CHECK-SM66-NEXT: [[GEP:%.*]] = getelementptr i32, ptr [[ACCUM_I_FLAT]], i32 0
14-
; CHECK-SM66-NEXT: call void @llvm.lifetime.start.p0(i64 4, ptr nonnull [[ACCUM_I_FLAT]])
15-
; CHECK-SM66-NEXT: store i32 0, ptr [[GEP]], align 4
16-
; CHECK-SM66-NEXT: call void @llvm.lifetime.end.p0(i64 4, ptr nonnull [[ACCUM_I_FLAT]])
17-
;
18-
; CHECK-NEXT: ret void
13+
; CHECK-NEXT: [[ACCUM_I_FLAT:%.*]] = alloca [1 x i32], align 4
14+
; CHECK-NEXT: [[GEP:%.*]] = getelementptr i32, ptr [[ACCUM_I_FLAT]], i32 0
15+
; CHECK-SM63-NEXT: store [1 x i32] undef, ptr [[ACCUM_I_FLAT]], align 4
16+
; CHECK-SM66-NEXT: call void @llvm.lifetime.start.p0(i64 4, ptr nonnull [[ACCUM_I_FLAT]])
17+
; CHECK-EMBED-NOT: bitcast
18+
; CHECK-EMBED-NOT: lifetime
19+
; CHECK-NEXT: store i32 0, ptr [[GEP]], align 4
20+
; CHECK-SM63-NEXT: store [1 x i32] undef, ptr [[ACCUM_I_FLAT]], align 4
21+
; CHECK-SM66-NEXT: call void @llvm.lifetime.end.p0(i64 4, ptr nonnull [[ACCUM_I_FLAT]])
22+
; CHECK-EMBED-NOT: bitcast
23+
; CHECK-EMBED-NOT: lifetime
24+
; CHECK-NEXT: ret void
1925
;
2026
define void @test_legal_lifetime() {
2127
%accum.i.flat = alloca [1 x i32], align 4

llvm/test/tools/dxil-dis/lifetimes.ll

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
; RUN: llc --filetype=obj %s -o - | dxil-dis -o - | FileCheck %s
2+
target triple = "dxil-unknown-shadermodel6.7-library"
3+
4+
define void @test_lifetimes() {
5+
; CHECK-LABEL: test_lifetimes
6+
; CHECK-NEXT: [[ALLOCA:%.*]] = alloca [2 x i32], align 4
7+
; CHECK-NEXT: [[GEP:%.*]] = getelementptr [2 x i32], [2 x i32]* [[ALLOCA]], i32 0, i32 0
8+
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast [2 x i32]* [[ALLOCA]] to i8*
9+
; CHECK-NEXT: call void @llvm.lifetime.start(i64 4, i8* nonnull [[BITCAST]])
10+
; CHECK-NEXT: store i32 0, i32* [[GEP]], align 4
11+
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast [2 x i32]* [[ALLOCA]] to i8*
12+
; CHECK-NEXT: call void @llvm.lifetime.end(i64 4, i8* nonnull [[BITCAST]])
13+
; CHECK-NEXT: ret void
14+
;
15+
%a = alloca [2 x i32], align 4
16+
%gep = getelementptr [2 x i32], ptr %a, i32 0, i32 0
17+
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %a)
18+
store i32 0, ptr %gep, align 4
19+
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %a)
20+
ret void
21+
}
22+
23+
; CHECK-DAG: attributes [[LIFETIME_ATTRS:#.*]] = { nounwind }
24+
25+
; CHECK-DAG: ; Function Attrs: nounwind
26+
; CHECK-DAG: declare void @llvm.lifetime.start(i64, i8* nocapture) [[LIFETIME_ATTRS]]
27+
28+
; CHECK-DAG: ; Function Attrs: nounwind
29+
; CHECK-DAG: declare void @llvm.lifetime.end(i64, i8* nocapture) [[LIFETIME_ATTRS]]
30+
31+
; Function Attrs: nounwind memory(argmem: readwrite)
32+
declare void @llvm.lifetime.end.p0(i64, ptr) #0
33+
34+
; Function Attrs: nounwind memory(argmem: readwrite)
35+
declare void @llvm.lifetime.start.p0(i64, ptr) #0
36+
37+
attributes #0 = { nounwind memory(argmem: readwrite) }
38+

0 commit comments

Comments
 (0)