Skip to content

Commit 29901a0

Browse files
hazzlimjrbyrnes
authored andcommitted
[ArgPromotion] Perform alias analysis on actual arguments of Calls (llvm#106216)
Teach Argument Promotion to perform alias analysis on actual arguments of Calls to a Function, to try to prove that all Calls to the Function do not modify the memory pointed to by an argument. This surfaces more opportunities to perform Argument Promotion in cases where simply looking at a Function's instructions is insufficient to prove that the pointer argument is not invalidated before all loads from it.
1 parent a4639a3 commit 29901a0

File tree

2 files changed

+263
-5
lines changed

2 files changed

+263
-5
lines changed

llvm/lib/Transforms/IPO/ArgumentPromotion.cpp

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,36 @@ static bool allCallersPassValidPointerForArgument(
470470
});
471471
}
472472

473+
// Try to prove that all Calls to F do not modify the memory pointed to by Arg,
474+
// using alias analysis local to each caller of F.
475+
static bool isArgUnmodifiedByAllCalls(Argument *Arg,
476+
FunctionAnalysisManager &FAM) {
477+
for (User *U : Arg->getParent()->users()) {
478+
479+
// Bail if we find an unexpected (non CallInst) use of the function.
480+
auto *Call = dyn_cast<CallInst>(U);
481+
if (!Call)
482+
return false;
483+
484+
MemoryLocation Loc =
485+
MemoryLocation::getForArgument(Call, Arg->getArgNo(), nullptr);
486+
487+
AAResults &AAR = FAM.getResult<AAManager>(*Call->getFunction());
488+
// Bail as soon as we find a Call where Arg may be modified.
489+
if (isModSet(AAR.getModRefInfo(Call, Loc)))
490+
return false;
491+
}
492+
493+
// All Users are Calls which do not modify the Arg.
494+
return true;
495+
}
496+
473497
/// Determine that this argument is safe to promote, and find the argument
474498
/// parts it can be promoted into.
475499
static bool findArgParts(Argument *Arg, const DataLayout &DL, AAResults &AAR,
476500
unsigned MaxElements, bool IsRecursive,
477-
SmallVectorImpl<OffsetAndArgPart> &ArgPartsVec) {
501+
SmallVectorImpl<OffsetAndArgPart> &ArgPartsVec,
502+
FunctionAnalysisManager &FAM) {
478503
// Quick exit for unused arguments
479504
if (Arg->use_empty())
480505
return true;
@@ -705,10 +730,16 @@ static bool findArgParts(Argument *Arg, const DataLayout &DL, AAResults &AAR,
705730
return true;
706731

707732
// Okay, now we know that the argument is only used by load instructions, and
708-
// it is safe to unconditionally perform all of them. Use alias analysis to
709-
// check to see if the pointer is guaranteed to not be modified from entry of
710-
// the function to each of the load instructions.
733+
// it is safe to unconditionally perform all of them.
734+
735+
// If we can determine that no call to the Function modifies the memory region
736+
// accessed through Arg, through alias analysis using actual arguments in the
737+
// callers, we know that it is guaranteed to be safe to promote the argument.
738+
if (isArgUnmodifiedByAllCalls(Arg, FAM))
739+
return true;
711740

741+
// Otherwise, use alias analysis to check if the pointer is guaranteed to not
742+
// be modified from entry of the function to each of the load instructions.
712743
for (LoadInst *Load : Loads) {
713744
// Check to see if the load is invalidated from the start of the block to
714745
// the load itself.
@@ -835,7 +866,8 @@ static Function *promoteArguments(Function *F, FunctionAnalysisManager &FAM,
835866
// If we can promote the pointer to its value.
836867
SmallVector<OffsetAndArgPart, 4> ArgParts;
837868

838-
if (findArgParts(PtrArg, DL, AAR, MaxElements, IsRecursive, ArgParts)) {
869+
if (findArgParts(PtrArg, DL, AAR, MaxElements, IsRecursive, ArgParts,
870+
FAM)) {
839871
SmallVector<Type *, 4> Types;
840872
for (const auto &Pair : ArgParts)
841873
Types.push_back(Pair.second.Ty);
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes
2+
; RUN: opt -S -passes=argpromotion < %s | FileCheck %s
3+
4+
; In the following tests, the call to @callee may invalidate ptr %test_c and so
5+
; prohibit removing loads of %test_c following the call, preventing Argument
6+
; Promotion of %test_c in the general case.
7+
8+
; This is called by @caller_ptr_args, from which we cannot prove anything about
9+
; whether %test_c may alias %p and so we cannot promote %test_c.
10+
;
11+
define internal i32 @test_cannot_promote_1(ptr %p, ptr nocapture readonly %test_c) {
12+
; CHECK-LABEL: define {{[^@]+}}@test_cannot_promote_1
13+
; CHECK-SAME: (ptr [[P:%.*]], ptr nocapture readonly [[TEST_C:%.*]]) {
14+
; CHECK-NEXT: [[TEST_C_VAL:%.*]] = load i32, ptr [[TEST_C]], align 4
15+
; CHECK-NEXT: [[RES:%.*]] = call i32 @callee(ptr [[P]], i32 [[TEST_C_VAL]])
16+
; CHECK-NEXT: [[LTEST_C:%.*]] = load i32, ptr [[TEST_C]], align 4
17+
; CHECK-NEXT: [[SUM:%.*]] = add i32 [[LTEST_C]], [[RES]]
18+
; CHECK-NEXT: ret i32 [[SUM]]
19+
;
20+
%res = call i32 @callee(ptr %p, ptr %test_c)
21+
22+
%ltest_c = load i32, ptr %test_c
23+
24+
%sum = add i32 %ltest_c, %res
25+
26+
ret i32 %sum
27+
}
28+
29+
; This is called by @caller_aliased_args, from which we can see that %test_c may
30+
; alias %p and so we cannot promote %test_c.
31+
;
32+
define internal i32 @test_cannot_promote_2(ptr %p, ptr nocapture readonly %test_c) {
33+
; CHECK-LABEL: define {{[^@]+}}@test_cannot_promote_2
34+
; CHECK-SAME: (ptr [[P:%.*]], ptr nocapture readonly [[TEST_C:%.*]]) {
35+
; CHECK-NEXT: [[TEST_C_VAL:%.*]] = load i32, ptr [[TEST_C]], align 4
36+
; CHECK-NEXT: [[RES:%.*]] = call i32 @callee(ptr [[P]], i32 [[TEST_C_VAL]])
37+
; CHECK-NEXT: [[LTEST_C:%.*]] = load i32, ptr [[TEST_C]], align 4
38+
; CHECK-NEXT: [[SUM:%.*]] = add i32 [[LTEST_C]], [[RES]]
39+
; CHECK-NEXT: ret i32 [[SUM]]
40+
;
41+
%res = call i32 @callee(ptr %p, ptr %test_c)
42+
43+
%ltest_c = load i32, ptr %test_c
44+
45+
%sum = add i32 %ltest_c, %res
46+
47+
ret i32 %sum
48+
}
49+
50+
; This is called by @caller_safe_args_1, but also from @caller_aliased_args, so
51+
; we cannot promote %test_c.
52+
;
53+
define internal i32 @test_cannot_promote_3(ptr %p, ptr nocapture readonly %test_c) {
54+
; CHECK-LABEL: define {{[^@]+}}@test_cannot_promote_3
55+
; CHECK-SAME: (ptr [[P:%.*]], ptr nocapture readonly [[TEST_C:%.*]]) {
56+
; CHECK-NEXT: [[TEST_C_VAL:%.*]] = load i32, ptr [[TEST_C]], align 4
57+
; CHECK-NEXT: [[RES:%.*]] = call i32 @callee(ptr [[P]], i32 [[TEST_C_VAL]])
58+
; CHECK-NEXT: [[LTEST_C:%.*]] = load i32, ptr [[TEST_C]], align 4
59+
; CHECK-NEXT: [[SUM:%.*]] = add i32 [[LTEST_C]], [[RES]]
60+
; CHECK-NEXT: ret i32 [[SUM]]
61+
;
62+
%res = call i32 @callee(ptr %p, ptr %test_c)
63+
64+
%ltest_c = load i32, ptr %test_c
65+
66+
%sum = add i32 %ltest_c, %res
67+
68+
ret i32 %sum
69+
}
70+
71+
; This is called only by @caller_safe_args_1, from which we can prove that
72+
; %test_c does not alias %p for any Call to the function, so we can promote it.
73+
;
74+
define internal i32 @test_can_promote_1(ptr %p, ptr nocapture readonly %test_c) {
75+
; CHECK-LABEL: define {{[^@]+}}@test_can_promote_1
76+
; CHECK-SAME: (ptr [[P:%.*]], i32 [[TEST_C_0_VAL:%.*]]) {
77+
; CHECK-NEXT: [[RES:%.*]] = call i32 @callee(ptr [[P]], i32 [[TEST_C_0_VAL]])
78+
; CHECK-NEXT: [[SUM:%.*]] = add i32 [[TEST_C_0_VAL]], [[RES]]
79+
; CHECK-NEXT: ret i32 [[SUM]]
80+
;
81+
%res = call i32 @callee(ptr %p, ptr %test_c)
82+
83+
%ltest_c = load i32, ptr %test_c
84+
85+
%sum = add i32 %ltest_c, %res
86+
87+
ret i32 %sum
88+
}
89+
90+
; This is called by multiple callers (@caller_safe_args_1, @caller_safe_args_2),
91+
; from which we can prove that %test_c does not alias %p for any Call to the
92+
; function, so we can promote it.
93+
;
94+
define internal i32 @test_can_promote_2(ptr %p, ptr nocapture readonly %test_c) {
95+
; CHECK-LABEL: define {{[^@]+}}@test_can_promote_2
96+
; CHECK-SAME: (ptr [[P:%.*]], i32 [[TEST_C_0_VAL:%.*]]) {
97+
; CHECK-NEXT: [[RES:%.*]] = call i32 @callee(ptr [[P]], i32 [[TEST_C_0_VAL]])
98+
; CHECK-NEXT: [[SUM:%.*]] = add i32 [[TEST_C_0_VAL]], [[RES]]
99+
; CHECK-NEXT: ret i32 [[SUM]]
100+
;
101+
%res = call i32 @callee(ptr %p, ptr %test_c)
102+
103+
%ltest_c = load i32, ptr %test_c
104+
105+
%sum = add i32 %ltest_c, %res
106+
107+
ret i32 %sum
108+
}
109+
110+
; Called by @test_XXX
111+
define internal i32 @callee(ptr %p, ptr nocapture readonly %callee_c) {
112+
; CHECK-LABEL: define {{[^@]+}}@callee
113+
; CHECK-SAME: (ptr [[P:%.*]], i32 [[CALLEE_C_0_VAL:%.*]]) {
114+
; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4
115+
; CHECK-NEXT: [[SUM:%.*]] = add i32 [[A]], [[CALLEE_C_0_VAL]]
116+
; CHECK-NEXT: store i32 [[SUM]], ptr [[P]], align 4
117+
; CHECK-NEXT: ret i32 [[SUM]]
118+
;
119+
%a = load i32, ptr %p
120+
121+
%lcallee_c = load i32, ptr %callee_c
122+
123+
%sum = add i32 %a, %lcallee_c
124+
125+
store i32 %sum, ptr %p
126+
127+
ret i32 %sum
128+
}
129+
130+
; Calls @test_cannot_promote_1
131+
define i32 @caller_ptr_args(i64 %n, ptr %p1, ptr %p2) {
132+
; CHECK-LABEL: define {{[^@]+}}@caller_ptr_args
133+
; CHECK-SAME: (i64 [[N:%.*]], ptr [[P1:%.*]], ptr [[P2:%.*]]) {
134+
; CHECK-NEXT: call void @memset(ptr [[P1]], i64 0, i64 [[N]])
135+
; CHECK-NEXT: store i32 5, ptr [[P2]], align 4
136+
; CHECK-NEXT: [[RES:%.*]] = call i32 @test_cannot_promote_1(ptr [[P1]], ptr [[P2]])
137+
; CHECK-NEXT: ret i32 [[RES]]
138+
;
139+
call void @memset(ptr %p1, i64 0, i64 %n)
140+
141+
store i32 5, ptr %p2
142+
143+
%res = call i32 @test_cannot_promote_1(ptr %p1, ptr %p2)
144+
145+
ret i32 %res
146+
}
147+
148+
; Calls @test_cannot_promote_2
149+
; Calls @test_cannot_promote_3
150+
define i32 @caller_aliased_args() {
151+
; CHECK-LABEL: define {{[^@]+}}@caller_aliased_args() {
152+
; CHECK-NEXT: [[CALLER_C:%.*]] = alloca i32, align 4
153+
; CHECK-NEXT: store i32 5, ptr [[CALLER_C]], align 4
154+
; CHECK-NEXT: [[RES1:%.*]] = call i32 @test_cannot_promote_2(ptr [[CALLER_C]], ptr [[CALLER_C]])
155+
; CHECK-NEXT: [[RES2:%.*]] = call i32 @test_cannot_promote_3(ptr [[CALLER_C]], ptr [[CALLER_C]])
156+
; CHECK-NEXT: [[RES:%.*]] = add i32 [[RES1]], [[RES2]]
157+
; CHECK-NEXT: ret i32 [[RES]]
158+
;
159+
%caller_c = alloca i32
160+
store i32 5, ptr %caller_c
161+
162+
%res1 = call i32 @test_cannot_promote_2(ptr %caller_c, ptr %caller_c)
163+
%res2 = call i32 @test_cannot_promote_3(ptr %caller_c, ptr %caller_c)
164+
165+
%res = add i32 %res1, %res2
166+
167+
ret i32 %res
168+
}
169+
170+
; Calls @test_cannot_promote_3
171+
; Calls @test_can_promote_1
172+
; Calls @test_can_promote_2
173+
define i32 @caller_safe_args_1(i64 %n) {
174+
; CHECK-LABEL: define {{[^@]+}}@caller_safe_args_1
175+
; CHECK-SAME: (i64 [[N:%.*]]) {
176+
; CHECK-NEXT: [[P:%.*]] = alloca [5 x double], i64 [[N]], align 8
177+
; CHECK-NEXT: call void @memset(ptr [[P]], i64 0, i64 [[N]])
178+
; CHECK-NEXT: [[CALLER_C:%.*]] = alloca i32, align 4
179+
; CHECK-NEXT: store i32 5, ptr [[CALLER_C]], align 4
180+
; CHECK-NEXT: [[RES1:%.*]] = call i32 @test_cannot_promote_3(ptr [[P]], ptr [[CALLER_C]])
181+
; CHECK-NEXT: [[CALLER_C_VAL:%.*]] = load i32, ptr [[CALLER_C]], align 4
182+
; CHECK-NEXT: [[RES2:%.*]] = call i32 @test_can_promote_1(ptr [[P]], i32 [[CALLER_C_VAL]])
183+
; CHECK-NEXT: [[CALLER_C_VAL1:%.*]] = load i32, ptr [[CALLER_C]], align 4
184+
; CHECK-NEXT: [[RES3:%.*]] = call i32 @test_can_promote_2(ptr [[P]], i32 [[CALLER_C_VAL1]])
185+
; CHECK-NEXT: [[RES12:%.*]] = add i32 [[RES1]], [[RES2]]
186+
; CHECK-NEXT: [[RES:%.*]] = add i32 [[RES12]], [[RES3]]
187+
; CHECK-NEXT: ret i32 [[RES]]
188+
;
189+
%p = alloca [5 x double], i64 %n
190+
call void @memset(ptr %p, i64 0, i64 %n)
191+
192+
%caller_c = alloca i32
193+
store i32 5, ptr %caller_c
194+
195+
%res1 = call i32 @test_cannot_promote_3(ptr %p, ptr %caller_c)
196+
%res2 = call i32 @test_can_promote_1(ptr %p, ptr %caller_c)
197+
%res3 = call i32 @test_can_promote_2(ptr %p, ptr %caller_c)
198+
199+
%res12 = add i32 %res1, %res2
200+
%res = add i32 %res12, %res3
201+
202+
ret i32 %res
203+
}
204+
205+
; Calls @test_can_promote_2
206+
define i32 @caller_safe_args_2(i64 %n, ptr %p) {
207+
; CHECK-LABEL: define {{[^@]+}}@caller_safe_args_2
208+
; CHECK-SAME: (i64 [[N:%.*]], ptr [[P:%.*]]) {
209+
; CHECK-NEXT: call void @memset(ptr [[P]], i64 0, i64 [[N]])
210+
; CHECK-NEXT: [[CALLER_C:%.*]] = alloca i32, align 4
211+
; CHECK-NEXT: store i32 5, ptr [[CALLER_C]], align 4
212+
; CHECK-NEXT: [[CALLER_C_VAL:%.*]] = load i32, ptr [[CALLER_C]], align 4
213+
; CHECK-NEXT: [[RES:%.*]] = call i32 @test_can_promote_2(ptr [[P]], i32 [[CALLER_C_VAL]])
214+
; CHECK-NEXT: ret i32 [[RES]]
215+
;
216+
call void @memset(ptr %p, i64 0, i64 %n)
217+
218+
%caller_c = alloca i32
219+
store i32 5, ptr %caller_c
220+
221+
%res = call i32 @test_can_promote_2(ptr %p, ptr %caller_c)
222+
223+
ret i32 %res
224+
}
225+
226+
declare void @memset(ptr, i64, i64)

0 commit comments

Comments
 (0)