Skip to content

Commit 40d5e01

Browse files
jaladreipsigcbot
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.
1 parent 5270b5d commit 40d5e01

File tree

6 files changed

+269
-60
lines changed

6 files changed

+269
-60
lines changed

IGC/AdaptorCommon/RayTracing/MergeAllocas.cpp

Lines changed: 123 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ AllocationBasedLivenessAnalysis::LivenessData* AllocationBasedLivenessAnalysis::
9696
}
9797

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

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

155-
return new LivenessData(I, allUsers, *LI, commonDominator);
156178
}
157179

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

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

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

IGC/AdaptorCommon/RayTracing/MergeAllocas.h

Lines changed: 9 additions & 1 deletion
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
};
Lines changed: 43 additions & 0 deletions
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+
Lines changed: 42 additions & 0 deletions
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

Lines changed: 1 addition & 12 deletions
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)