Skip to content

Commit 6bd8448

Browse files
authored
[FuncSpec] Skip SCCP on blocks of dead functions and poison their callsites (#154668)
Fixes #153295. For test case below: ```llvm define i32 @caller() { entry: %call1 = call i32 @callee(i32 1) %call2 = call i32 @callee(i32 0) %cond = icmp eq i32 %call2, 0 br i1 %cond, label %common.ret, label %if.then common.ret: ; preds = %entry ret i32 0 if.then: ; preds = %entry %unreachable_call = call i32 @callee(i32 2) ret i32 %unreachable_call } define internal i32 @callee(i32 %ac) { entry: br label %ai ai: ; preds = %ai, %entry %add = or i32 0, 0 %cond = icmp eq i32 %ac, 1 br i1 %cond, label %aj, label %ai aj: ; preds = %ai ret i32 0 } ``` Before specialization, the SCCP solver determines that `unreachable_call` is unexecutable, as the value of `callee` can only be zero. After specializing the call sites `call1` and `call2`, FnSpecializer announces `callee` is a dead function since all executable call sites are specialized. However, the unexecutable call sites can become executable again after solving specialized calls. In this testcase, `call2` is considered `Overdefined` after specialization, making `cond` also `Overdefined`. Thus, `unreachable_call` becomes executable. This patch skips SCCP on the blocks in dead functions, and poisons the call sites of dead functions.
1 parent e2dcb8b commit 6bd8448

File tree

4 files changed

+159
-5
lines changed

4 files changed

+159
-5
lines changed

llvm/include/llvm/Transforms/IPO/FunctionSpecialization.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ class FunctionSpecializer {
246246
std::function<AssumptionCache &(Function &)> GetAC;
247247

248248
SmallPtrSet<Function *, 32> Specializations;
249-
SmallPtrSet<Function *, 32> FullySpecialized;
249+
SmallPtrSet<Function *, 32> DeadFunctions;
250250
DenseMap<Function *, CodeMetrics> FunctionMetrics;
251251
DenseMap<Function *, unsigned> FunctionGrowth;
252252
unsigned NGlobals = 0;
@@ -270,6 +270,8 @@ class FunctionSpecializer {
270270
return InstCostVisitor(GetBFI, F, M.getDataLayout(), TTI, Solver);
271271
}
272272

273+
bool isDeadFunction(Function *F) { return DeadFunctions.contains(F); }
274+
273275
private:
274276
Constant *getPromotableAlloca(AllocaInst *Alloca, CallInst *Call);
275277

llvm/lib/Transforms/IPO/FunctionSpecialization.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -838,14 +838,24 @@ bool FunctionSpecializer::run() {
838838
}
839839

840840
void FunctionSpecializer::removeDeadFunctions() {
841-
for (Function *F : FullySpecialized) {
841+
for (Function *F : DeadFunctions) {
842842
LLVM_DEBUG(dbgs() << "FnSpecialization: Removing dead function "
843843
<< F->getName() << "\n");
844844
if (FAM)
845845
FAM->clear(*F, F->getName());
846+
847+
// Remove all the callsites that were proven unreachable once, and replace
848+
// them with poison.
849+
for (User *U : make_early_inc_range(F->users())) {
850+
assert((isa<CallInst>(U) || isa<InvokeInst>(U)) &&
851+
"User of dead function must be call or invoke");
852+
Instruction *CS = cast<Instruction>(U);
853+
CS->replaceAllUsesWith(PoisonValue::get(CS->getType()));
854+
CS->eraseFromParent();
855+
}
846856
F->eraseFromParent();
847857
}
848-
FullySpecialized.clear();
858+
DeadFunctions.clear();
849859
}
850860

851861
/// Clone the function \p F and remove the ssa_copy intrinsics added by
@@ -1206,8 +1216,11 @@ void FunctionSpecializer::updateCallSites(Function *F, const Spec *Begin,
12061216

12071217
// If the function has been completely specialized, the original function
12081218
// is no longer needed. Mark it unreachable.
1209-
if (NCallsLeft == 0 && Solver.isArgumentTrackedFunction(F)) {
1219+
// NOTE: If the address of a function is taken, we cannot treat it as dead
1220+
// function.
1221+
if (NCallsLeft == 0 && Solver.isArgumentTrackedFunction(F) &&
1222+
!F->hasAddressTaken()) {
12101223
Solver.markFunctionUnreachable(F);
1211-
FullySpecialized.insert(F);
1224+
DeadFunctions.insert(F);
12121225
}
12131226
}

llvm/lib/Transforms/IPO/SCCP.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ static bool runIPSCCP(
169169
for (Function &F : M) {
170170
if (F.isDeclaration())
171171
continue;
172+
// Skip the dead functions marked by FunctionSpecializer, avoiding removing
173+
// blocks in dead functions.
174+
if (IsFuncSpecEnabled && Specializer.isDeadFunction(&F))
175+
continue;
172176

173177
SmallVector<BasicBlock *, 512> BlocksToErase;
174178

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; RUN: opt -passes=ipsccp --funcspec-min-function-size=1 -S < %s | FileCheck %s
3+
4+
define i32 @caller() {
5+
; CHECK-LABEL: define i32 @caller() {
6+
; CHECK-NEXT: [[ENTRY:.*:]]
7+
; CHECK-NEXT: [[CALL1:%.*]] = call i32 @callee.specialized.1(i32 1)
8+
; CHECK-NEXT: [[CALL2:%.*]] = call i32 @callee.specialized.2(i32 0)
9+
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 undef, 0
10+
; CHECK-NEXT: br i1 [[COND]], label %[[COMMON_RET:.*]], label %[[IF_THEN:.*]]
11+
; CHECK: [[COMMON_RET]]:
12+
; CHECK-NEXT: ret i32 0
13+
; CHECK: [[IF_THEN]]:
14+
; CHECK-NEXT: ret i32 0
15+
;
16+
entry:
17+
%call1 = call i32 @callee(i32 1)
18+
%call2 = call i32 @callee(i32 0)
19+
%cond = icmp eq i32 %call2, 0
20+
br i1 %cond, label %common.ret, label %if.then
21+
22+
common.ret: ; preds = %entry
23+
ret i32 0
24+
25+
if.then: ; preds = %entry
26+
%unreachable_call = call i32 @callee(i32 2)
27+
ret i32 %unreachable_call
28+
}
29+
30+
define internal i32 @callee(i32 %arg) {
31+
entry:
32+
br label %loop
33+
34+
loop: ; preds = %ai, %entry
35+
%add = or i32 0, 0
36+
%cond = icmp eq i32 %arg, 1
37+
br i1 %cond, label %exit, label %loop
38+
39+
exit: ; preds = %ai
40+
ret i32 0
41+
}
42+
43+
declare void @other_user(ptr)
44+
45+
define i32 @caller2() {
46+
; CHECK-LABEL: define i32 @caller2() {
47+
; CHECK-NEXT: [[ENTRY:.*:]]
48+
; CHECK-NEXT: call void @other_user(ptr @callee2)
49+
; CHECK-NEXT: [[CALL1:%.*]] = call i32 @callee2.specialized.3(i32 1)
50+
; CHECK-NEXT: [[CALL2:%.*]] = call i32 @callee2.specialized.4(i32 0)
51+
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 undef, 0
52+
; CHECK-NEXT: br i1 [[COND]], label %[[COMMON_RET:.*]], label %[[IF_THEN:.*]]
53+
; CHECK: [[COMMON_RET]]:
54+
; CHECK-NEXT: ret i32 0
55+
; CHECK: [[IF_THEN]]:
56+
; CHECK-NEXT: [[UNREACHABLE_CALL:%.*]] = call i32 @callee2.specialized.7(i32 2)
57+
; CHECK-NEXT: ret i32 undef
58+
;
59+
entry:
60+
call void @other_user(ptr @callee2)
61+
%call1 = call i32 @callee2(i32 1)
62+
%call2 = call i32 @callee2(i32 0)
63+
%cond = icmp eq i32 %call2, 0
64+
br i1 %cond, label %common.ret, label %if.then
65+
66+
common.ret: ; preds = %entry
67+
ret i32 0
68+
69+
if.then: ; preds = %entry
70+
%unreachable_call = call i32 @callee2(i32 2)
71+
ret i32 %unreachable_call
72+
}
73+
74+
define internal i32 @callee2(i32 %arg) {
75+
; CHECK-LABEL: define internal i32 @callee2(
76+
; CHECK-SAME: i32 [[ARG:%.*]]) {
77+
; CHECK-NEXT: [[ENTRY:.*:]]
78+
; CHECK-NEXT: br label %[[LOOP:.*]]
79+
; CHECK: [[LOOP]]:
80+
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[ARG]], 1
81+
; CHECK-NEXT: br i1 [[COND]], label %[[EXIT:.*]], label %[[LOOP]]
82+
; CHECK: [[EXIT]]:
83+
; CHECK-NEXT: ret i32 0
84+
;
85+
entry:
86+
br label %loop
87+
88+
loop: ; preds = %ai, %entry
89+
%add = or i32 0, 0
90+
%cond = icmp eq i32 %arg, 1
91+
br i1 %cond, label %exit, label %loop
92+
93+
exit: ; preds = %ai
94+
ret i32 0
95+
}
96+
97+
define i32 @caller3(i32 %arg) {
98+
; CHECK-LABEL: define range(i32 2, 1) i32 @caller3(
99+
; CHECK-SAME: i32 [[ARG:%.*]]) {
100+
; CHECK-NEXT: [[ENTRY:.*:]]
101+
; CHECK-NEXT: [[CALL1:%.*]] = call i32 @callee3.specialized.5(i32 0)
102+
; CHECK-NEXT: [[CALL2:%.*]] = call i32 @callee3.specialized.6(i32 1)
103+
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 undef, 0
104+
; CHECK-NEXT: br i1 [[COND]], label %[[COMMON_RET:.*]], label %[[IF_THEN:.*]]
105+
; CHECK: [[COMMON_RET]]:
106+
; CHECK-NEXT: ret i32 0
107+
; CHECK: [[IF_THEN]]:
108+
; CHECK-NEXT: ret i32 poison
109+
;
110+
entry:
111+
%call1 = call i32 @callee3(i32 0)
112+
%call2 = call i32 @callee3(i32 1)
113+
%cond = icmp eq i32 %call2, 0
114+
br i1 %cond, label %common.ret, label %if.then
115+
116+
common.ret: ; preds = %entry
117+
ret i32 0
118+
119+
if.then: ; preds = %entry
120+
%unreachable_call = call i32 @callee3(i32 %arg)
121+
ret i32 %unreachable_call
122+
}
123+
124+
define internal i32 @callee3(i32 %arg) {
125+
entry:
126+
br label %loop
127+
128+
loop: ; preds = %ai, %entry
129+
%add = or i32 0, 0
130+
%cond = icmp ne i32 %arg, 1
131+
br i1 %cond, label %exit, label %loop
132+
133+
exit: ; preds = %ai
134+
ret i32 %arg
135+
}

0 commit comments

Comments
 (0)