Skip to content

Commit 0319e4a

Browse files
jaladreipspszymich
authored andcommitted
Fixes for allocation based liveness analysis
Fixes: * The assumption a loop header is a good place to start the lifetime is incorrect. Loop header is a part of the loop, so we should either choose the preheader or, if there is none, use the domtree to find common dominator for all predecessors. * Infinite lifetime handling would color the entire function if the first use of the allocation would not dominate all return instructions. This has been changed to a simple reachability search algorithm that will find all the blocks reachable from the liveness end points. It's probably too much, but it will not go back all the way to the functions entry point. * Only mark basic block as inflowing or outflowing if they actually have predecessors or successors. Previously, a function entry point would be marked as "inflow" even though there aren't any blocks pointing to it. (cherry picked from commit 40d5e01)
1 parent c194738 commit 0319e4a

File tree

6 files changed

+269
-60
lines changed

6 files changed

+269
-60
lines changed

IGC/AdaptorCommon/RayTracing/MergeAllocas.cpp

+123-47
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ AllocationBasedLivenessAnalysis::LivenessData* AllocationBasedLivenessAnalysis::
9595
}
9696

9797
// figure out the potential accesses to the memory via GEP and bitcasts
98-
while (!worklist.empty() && !hasNoLifetimeEnd)
98+
while (!worklist.empty())
9999
{
100100
auto* use = worklist.pop_back_val();
101101
auto* II = cast<Instruction>(use->getUser());
@@ -139,22 +139,44 @@ AllocationBasedLivenessAnalysis::LivenessData* AllocationBasedLivenessAnalysis::
139139
}
140140
}
141141

142-
// we add the return instructions to the list of users to express the infinite lifetime
143-
if (hasNoLifetimeEnd)
142+
return new LivenessData(I, allUsers, *LI, *DT, commonDominator, hasNoLifetimeEnd);
143+
}
144+
145+
template<typename range>
146+
static inline void doWorkLoop(
147+
SmallVector<BasicBlock*>& worklist,
148+
DenseSet<BasicBlock*>& bbSet1,
149+
DenseSet<BasicBlock*>& bbSet2,
150+
std::function<range(BasicBlock*)> iterate,
151+
std::function<bool(BasicBlock*)> continueCondition
152+
) {
153+
// perform data flow analysis
154+
while (!worklist.empty())
144155
{
145-
for_each(instructions(*I->getFunction()),
146-
[&](auto& II)
147-
{
148-
if (isa<ReturnInst>(&II))
149-
allUsers.insert(&II);
150-
}
151-
);
156+
auto* currbb = worklist.pop_back_val();
157+
158+
if (continueCondition(currbb))
159+
continue;
160+
161+
bool addToSet1 = false;
162+
163+
for (auto* pbb : iterate(currbb))
164+
{
165+
addToSet1 = true;
166+
167+
bool inserted = bbSet2.insert(pbb).second;
168+
169+
if (inserted)
170+
worklist.push_back(pbb);
171+
}
172+
173+
if (addToSet1)
174+
bbSet1.insert(currbb);
152175
}
153176

154-
return new LivenessData(I, allUsers, *LI, commonDominator);
155177
}
156178

157-
AllocationBasedLivenessAnalysis::LivenessData::LivenessData(Instruction* allocationInstruction, const SetVector<Instruction*>& usersOfAllocation, const LoopInfo& LI, BasicBlock* userDominatorBlock)
179+
AllocationBasedLivenessAnalysis::LivenessData::LivenessData(Instruction* allocationInstruction, const SetVector<Instruction*>& usersOfAllocation, const LoopInfo& LI, const DominatorTree& DT, BasicBlock* userDominatorBlock, bool isLifetimeInfinite)
158180
{
159181
if (!userDominatorBlock)
160182
userDominatorBlock = allocationInstruction->getParent();
@@ -171,38 +193,44 @@ AllocationBasedLivenessAnalysis::LivenessData::LivenessData(Instruction* allocat
171193
// Keep track of loop header of blocks that contain allocation instruction
172194
auto* allocationParent = allocationInstruction->getParent();
173195
llvm::SmallPtrSet<llvm::BasicBlock*, 4> containedLoopHeaders;
174-
if (const auto* parentLoop = LI.getLoopFor(allocationParent);
175-
parentLoop != nullptr) {
196+
if (const auto* parentLoop = LI.getLoopFor(allocationParent))
197+
{
176198
containedLoopHeaders.insert(parentLoop->getHeader());
177-
while (parentLoop->getParentLoop() != nullptr) {
199+
while (parentLoop->getParentLoop()) {
178200
parentLoop = parentLoop->getParentLoop();
179201
containedLoopHeaders.insert(parentLoop->getHeader());
180202
}
181203
}
204+
182205
// perform data flow analysis
183-
while (!worklist.empty())
206+
doWorkLoop<llvm::pred_range>(
207+
worklist,
208+
bbIn,
209+
bbOut,
210+
[&](auto* currbb) { return llvm::predecessors(currbb); },
211+
[&](auto* currbb) { return bbIn.contains(currbb) || currbb == userDominatorBlock || containedLoopHeaders.contains(currbb); }
212+
);
213+
214+
// handle infinite lifetime
215+
if (isLifetimeInfinite)
184216
{
185-
auto* currbb = worklist.pop_back_val();
186-
187-
if (bbIn.contains(currbb) || currbb == userDominatorBlock)
188-
continue;
189-
190-
// If alloca is defined in the loop, we skip loop header
191-
// so that we don't escape loop scope.
192-
if (containedLoopHeaders.count(currbb) != 0)
193-
{
194-
continue;
195-
}
196-
197-
if (currbb != allocationParent)
198-
{
199-
bbIn.insert(currbb);
200-
}
201-
for (auto* pbb : llvm::predecessors(currbb))
202-
{
203-
bbOut.insert(pbb);
204-
worklist.push_back(pbb);
205-
}
217+
// traverse all the successors until there are no left.
218+
auto bbInOnly = bbIn;
219+
set_subtract(bbInOnly, bbOut);
220+
221+
for (auto* bb : bbInOnly)
222+
worklist.push_back(bb);
223+
224+
// in case the only use is the one that causes lifetime escape
225+
worklist.push_back(userDominatorBlock);
226+
227+
doWorkLoop<llvm::succ_range>(
228+
worklist,
229+
bbOut,
230+
bbIn,
231+
[&](auto* currbb) { return llvm::successors(currbb); },
232+
[&](auto* currbb) { return false; }
233+
);
206234
}
207235

208236
// if the lifetime escapes any loop, we should make sure all the loops blocks are included
@@ -215,12 +243,38 @@ AllocationBasedLivenessAnalysis::LivenessData::LivenessData(Instruction* allocat
215243
{
216244
llvm::for_each(
217245
loop->blocks(),
218-
[&](auto* block) {
219-
bbOut.insert(block);
220-
if (block != loop->getHeader())
221-
bbIn.insert(block);
222-
}
246+
[&](auto* block) { bbOut.insert(block); bbIn.insert(block); }
223247
);
248+
249+
if (loop->getLoopPreheader())
250+
{
251+
bbOut.insert(loop->getLoopPreheader());
252+
}
253+
else
254+
{
255+
// if the header has multiple predecessors, we need to find the common dominator of all of these
256+
auto* commonDominator = loop->getHeader();
257+
for (auto* bb : llvm::predecessors(loop->getHeader()))
258+
{
259+
if (loop->contains(bb))
260+
continue;
261+
262+
commonDominator = DT.findNearestCommonDominator(commonDominator, bb);
263+
worklist.push_back(bb);
264+
}
265+
266+
// acknowledge lifetime flow out of the common dominator block
267+
bbOut.insert(commonDominator);
268+
269+
// add all blocks inbetween
270+
doWorkLoop<llvm::pred_range>(
271+
worklist,
272+
bbIn,
273+
bbOut,
274+
[&](auto* currbb) { return llvm::predecessors(currbb); },
275+
[&](auto* currbb) { return bbOut.contains(currbb) || currbb == commonDominator; }
276+
);
277+
}
224278
}
225279
}
226280

@@ -264,7 +318,7 @@ AllocationBasedLivenessAnalysis::LivenessData::LivenessData(Instruction* allocat
264318
{
265319
for (auto& I : llvm::reverse(*bb))
266320
{
267-
if (usersOfAllocation.contains(&I))
321+
if (usersOfAllocation.contains(&I) || isLifetimeInfinite)
268322
{
269323
lifetimeEnds.push_back(&I);
270324
break;
@@ -289,12 +343,34 @@ bool AllocationBasedLivenessAnalysis::LivenessData::OverlapsWith(const LivenessD
289343
// check lifetime boundaries
290344
for (auto& [LD1, LD2] : { std::make_pair(*this, LD), std::make_pair(LD, *this) })
291345
{
292-
if (LD1.lifetimeEnds.size() == 1 && *LD1.lifetimeEnds.begin() == LD1.lifetimeStart)
293-
continue;
294-
295346
for (auto* I : LD1.lifetimeEnds)
296347
{
297-
if (I->getParent() == LD2.lifetimeStart->getParent())
348+
// what if LD1 is contained in a single block
349+
if (I->getParent() == LD1.lifetimeStart->getParent())
350+
{
351+
auto* bb = I->getParent();
352+
bool inflow = LD2.bbIn.contains(bb);
353+
bool outflow = LD2.bbOut.contains(bb);
354+
bool lifetimeStart = LD2.lifetimeStart->getParent() == bb && LD2.lifetimeStart->comesBefore(I);
355+
356+
auto* LD1_lifetimeStart = LD1.lifetimeStart; // we have to copy LD1.lifetimeStart to avoid clang complaining about LD1 being captured by the lambda
357+
bool lifetimeEnd = any_of(LD2.lifetimeEnds, [&](auto* lifetimeEnd) {
358+
return lifetimeEnd->getParent() == bb && LD1_lifetimeStart->comesBefore(lifetimeEnd);
359+
});
360+
361+
if (inflow && outflow)
362+
return true;
363+
364+
if (inflow && lifetimeEnd)
365+
return true;
366+
367+
if (outflow && lifetimeStart)
368+
return true;
369+
370+
if (lifetimeEnd && lifetimeStart)
371+
return true;
372+
}
373+
else if (I->getParent() == LD2.lifetimeStart->getParent())
298374
{
299375
if (LD2.lifetimeStart->comesBefore(I))
300376
return true;

IGC/AdaptorCommon/RayTracing/MergeAllocas.h

+9-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SPDX-License-Identifier: MIT
1414

1515
namespace llvm {
1616
class BasicBlock;
17+
class DominatorTree;
1718
class Function;
1819
class Instruction;
1920
class LoopInfo;
@@ -68,7 +69,14 @@ namespace IGC
6869
llvm::DenseSet<llvm::BasicBlock*> bbIn;
6970
llvm::DenseSet<llvm::BasicBlock*> bbOut;
7071

71-
LivenessData(llvm::Instruction* allocationInstruction, const llvm::SetVector<llvm::Instruction*>& usersOfAllocation, const llvm::LoopInfo& LI, llvm::BasicBlock* userDominatorBlock = nullptr);
72+
LivenessData(
73+
llvm::Instruction* allocationInstruction,
74+
const llvm::SetVector<llvm::Instruction*>& usersOfAllocation,
75+
const llvm::LoopInfo& LI,
76+
const llvm::DominatorTree& DT,
77+
llvm::BasicBlock* userDominatorBlock = nullptr,
78+
bool isLifetimeInfinite = false
79+
);
7280

7381
bool OverlapsWith(const LivenessData& LD) const;
7482
};
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
;=========================== begin_copyright_notice ============================
2+
;
3+
; Copyright (C) 2024 Intel Corporation
4+
;
5+
; SPDX-License-Identifier: MIT
6+
;
7+
;============================ end_copyright_notice =============================
8+
9+
; RUN: igc_opt --opaque-pointers -S --platformbmg --igc-merge-allocas %s | FileCheck %s
10+
11+
; Test interaction between control flow and escaping lifetime
12+
13+
target datalayout = "e-p:64:64:64-p1:64:64:64-p2:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:32:32-v96:32:32-v128:32:32-a0:0:32-n8:16:32-S32"
14+
target triple = "dxil-ms-dx"
15+
16+
declare void @escape(ptr %a)
17+
18+
; Function Attrs: alwaysinline nounwind
19+
define void @fun() #0 {
20+
start:
21+
%alloca_inf = alloca float, align 8
22+
%alloca = alloca float, align 8
23+
; CHECK: alloca float
24+
; CHECK-NOT: alloca float
25+
br label %altpath
26+
27+
altpath:
28+
%a = load float, ptr %alloca
29+
br i1 undef, label %altpath1, label %altpath2
30+
31+
altpath1:
32+
call void @escape(ptr %alloca_inf)
33+
br label %altpathexit
34+
35+
altpath2:
36+
br label %altpathexit
37+
38+
altpathexit:
39+
ret void
40+
}
41+
42+
attributes #0 = { nounwind }
43+
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
;=========================== begin_copyright_notice ============================
2+
;
3+
; Copyright (C) 2025 Intel Corporation
4+
;
5+
; SPDX-License-Identifier: MIT
6+
;
7+
;============================ end_copyright_notice =============================
8+
9+
target datalayout = "e-p:64:64:64-p1:64:64:64-p2:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:32:32-v96:32:32-v128:32:32-a0:0:32-n8:16:32-S32"
10+
target triple = "dxil-ms-dx"
11+
12+
; RUN: igc_opt --opaque-pointers -S --platformbmg --igc-merge-allocas %s | FileCheck %s
13+
14+
; Test interaction between control flow and escaping lifetime
15+
16+
declare void @escape(ptr %a)
17+
18+
; Function Attrs: alwaysinline nounwind
19+
define void @fun() #0 {
20+
start:
21+
%alloca_inf = alloca float
22+
%alloca = alloca float
23+
; CHECK: alloca float
24+
; CHECK: alloca float
25+
br label %altpath
26+
27+
altpath:
28+
br i1 undef, label %altpath1, label %altpath2
29+
30+
altpath1:
31+
call void @escape(ptr %alloca_inf)
32+
br label %altpathexit
33+
34+
altpath2:
35+
br label %altpathexit
36+
37+
altpathexit:
38+
%a = load float, ptr %alloca
39+
ret void
40+
}
41+
42+
attributes #0 = { nounwind }

IGC/Compiler/tests/MergeAllocas/loop-1.ll

+1-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;=========================== begin_copyright_notice ============================
22
;
3-
; Copyright (C) 2024 Intel Corporation
3+
; Copyright (C) 2025 Intel Corporation
44
;
55
; SPDX-License-Identifier: MIT
66
;
@@ -43,14 +43,3 @@ commonret:
4343
}
4444

4545
attributes #0 = { nounwind readnone }
46-
47-
48-
!llvm.ident = !{!0, !0, !0, !0}
49-
!dx.version = !{!1, !1, !1, !1}
50-
!dx.valver = !{!2, !2, !2, !2}
51-
!dx.shaderModel = !{!3, !3, !3, !3}
52-
53-
!0 = !{!"clang version 3.7 (tags/RELEASE_370/final)"}
54-
!1 = !{i32 1, i32 5}
55-
!2 = !{i32 1, i32 6}
56-
!3 = !{!"lib", i32 6, i32 5}

0 commit comments

Comments
 (0)