Skip to content

Commit 3839b57

Browse files
author
Johannes Doerfert
committed
[Attributor] Teach AANoCapture to use information in-flight more aggressively
AAReturnedValues, AAMemoryBehavior, and AANoUnwind, can provide information that helps during the tracking or even justifies no-capture. We now use this information and enable no-capture in some test cases designed a long while a ago for these cases. llvm-svn: 375382
1 parent e784146 commit 3839b57

File tree

6 files changed

+103
-21
lines changed

6 files changed

+103
-21
lines changed

llvm/lib/Transforms/IPO/Attributor.cpp

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,7 +2939,7 @@ struct AANoCaptureImpl : public AANoCapture {
29392939

29402940
// Check what state the associated function can actually capture.
29412941
if (F)
2942-
determineFunctionCaptureCapabilities(*F, *this);
2942+
determineFunctionCaptureCapabilities(IRP, *F, *this);
29432943
else
29442944
indicatePessimisticFixpoint();
29452945
}
@@ -2965,7 +2965,8 @@ struct AANoCaptureImpl : public AANoCapture {
29652965
/// Set the NOT_CAPTURED_IN_MEM and NOT_CAPTURED_IN_RET bits in \p Known
29662966
/// depending on the ability of the function associated with \p IRP to capture
29672967
/// state in memory and through "returning/throwing", respectively.
2968-
static void determineFunctionCaptureCapabilities(const Function &F,
2968+
static void determineFunctionCaptureCapabilities(const IRPosition &IRP,
2969+
const Function &F,
29692970
IntegerState &State) {
29702971
// TODO: Once we have memory behavior attributes we should use them here.
29712972

@@ -2987,6 +2988,21 @@ struct AANoCaptureImpl : public AANoCapture {
29872988
// exceptions and doesn not return values.
29882989
if (F.doesNotThrow() && F.getReturnType()->isVoidTy())
29892990
State.addKnownBits(NOT_CAPTURED_IN_RET);
2991+
2992+
// Check existing "returned" attributes.
2993+
int ArgNo = IRP.getArgNo();
2994+
if (F.doesNotThrow() && ArgNo >= 0) {
2995+
for (unsigned u = 0, e = F.arg_size(); u< e; ++u)
2996+
if (F.hasParamAttribute(u, Attribute::Returned)) {
2997+
if (u == ArgNo)
2998+
State.removeAssumedBits(NOT_CAPTURED_IN_RET);
2999+
else if (F.onlyReadsMemory())
3000+
State.addKnownBits(NO_CAPTURE);
3001+
else
3002+
State.addKnownBits(NOT_CAPTURED_IN_RET);
3003+
break;
3004+
}
3005+
}
29903006
}
29913007

29923008
/// See AbstractState::getAsStr().
@@ -3158,15 +3174,54 @@ ChangeStatus AANoCaptureImpl::updateImpl(Attributor &A) {
31583174
const Function *F =
31593175
getArgNo() >= 0 ? IRP.getAssociatedFunction() : IRP.getAnchorScope();
31603176
assert(F && "Expected a function!");
3161-
const auto &IsDeadAA = A.getAAFor<AAIsDead>(*this, IRPosition::function(*F));
3177+
const IRPosition &FnPos = IRPosition::function(*F);
3178+
const auto &IsDeadAA = A.getAAFor<AAIsDead>(*this, FnPos);
31623179

31633180
AANoCapture::StateType T;
3164-
// TODO: Once we have memory behavior attributes we should use them here
3165-
// similar to the reasoning in
3166-
// AANoCaptureImpl::determineFunctionCaptureCapabilities(...).
31673181

3168-
// TODO: Use the AAReturnedValues to learn if the argument can return or
3169-
// not.
3182+
// Readonly means we cannot capture through memory.
3183+
const auto &FnMemAA = A.getAAFor<AAMemoryBehavior>(*this, FnPos);
3184+
if (FnMemAA.isAssumedReadOnly()) {
3185+
T.addKnownBits(NOT_CAPTURED_IN_MEM);
3186+
if (FnMemAA.isKnownReadOnly())
3187+
addKnownBits(NOT_CAPTURED_IN_MEM);
3188+
}
3189+
3190+
// Make sure all returned values are different than the underlying value.
3191+
// TODO: we could do this in a more sophisticated way inside
3192+
// AAReturnedValues, e.g., track all values that escape through returns
3193+
// directly somehow.
3194+
auto CheckReturnedArgs = [&](const AAReturnedValues &RVAA) {
3195+
bool SeenConstant = false;
3196+
for (auto &It : RVAA.returned_values()) {
3197+
if (isa<Constant>(It.first)) {
3198+
if (SeenConstant)
3199+
return false;
3200+
SeenConstant = true;
3201+
} else if (!isa<Argument>(It.first) ||
3202+
It.first == getAssociatedArgument())
3203+
return false;
3204+
}
3205+
return true;
3206+
};
3207+
3208+
const auto &NoUnwindAA = A.getAAFor<AANoUnwind>(*this, FnPos);
3209+
if (NoUnwindAA.isAssumedNoUnwind()) {
3210+
bool IsVoidTy = F->getReturnType()->isVoidTy();
3211+
const AAReturnedValues *RVAA =
3212+
IsVoidTy ? nullptr : &A.getAAFor<AAReturnedValues>(*this, FnPos);
3213+
if (IsVoidTy || CheckReturnedArgs(*RVAA)) {
3214+
T.addKnownBits(NOT_CAPTURED_IN_RET);
3215+
if (T.isKnown(NOT_CAPTURED_IN_MEM))
3216+
return ChangeStatus::UNCHANGED;
3217+
if (NoUnwindAA.isKnownNoUnwind() &&
3218+
(IsVoidTy || RVAA->getState().isAtFixpoint())) {
3219+
addKnownBits(NOT_CAPTURED_IN_RET);
3220+
if (isKnown(NOT_CAPTURED_IN_MEM))
3221+
return indicateOptimisticFixpoint();
3222+
}
3223+
}
3224+
}
31703225

31713226
// Use the CaptureTracker interface and logic with the specialized tracker,
31723227
// defined in AACaptureUseTracker, that can look at in-flight abstract

llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -405,35 +405,33 @@ entry:
405405
;
406406
; Make sure the returned flag on %r is strong enough to justify nocapture on %b but **not** on %r.
407407
;
408-
; FIXME: The "returned" information is not propagated to the fullest extend causing us to miss "nocapture" on %b in the following:
409-
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either1(i32* readonly %b, i32* readonly returned %r)
408+
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either1(i32* nocapture readonly %b, i32* readonly returned %r)
410409
;
411-
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either2(i32* readonly %b, i32* readonly returned %r)
412-
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either3(i32* readonly %b, i32* readonly returned %r)
410+
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either2(i32* nocapture readonly %b, i32* readonly returned %r)
411+
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either3(i32* nocapture readonly %b, i32* readonly returned %r)
413412
;
414-
; FIXME: The "nounwind" information is not derived to the fullest extend causing us to miss "nocapture" on %b in the following:
415-
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either4(i32* readonly %b, i32* readonly returned %r)
416-
define i32* @not_captured_by_readonly_call_not_returned_either1(i32* %b, i32* returned %r) #0 {
413+
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either4(i32* nocapture readonly %b, i32* readonly returned %r)
414+
define i32* @not_captured_by_readonly_call_not_returned_either1(i32* %b, i32* returned %r) {
417415
entry:
418416
%call = call i32* @readonly_unknown(i32* %b, i32* %r) nounwind
419417
ret i32* %call
420418
}
421419

422420
declare i32* @readonly_unknown_r1a(i32*, i32* returned) readonly
423-
define i32* @not_captured_by_readonly_call_not_returned_either2(i32* %b, i32* %r) #0 {
421+
define i32* @not_captured_by_readonly_call_not_returned_either2(i32* %b, i32* %r) {
424422
entry:
425423
%call = call i32* @readonly_unknown_r1a(i32* %b, i32* %r) nounwind
426424
ret i32* %call
427425
}
428426

429427
declare i32* @readonly_unknown_r1b(i32*, i32* returned) readonly nounwind
430-
define i32* @not_captured_by_readonly_call_not_returned_either3(i32* %b, i32* %r) #0 {
428+
define i32* @not_captured_by_readonly_call_not_returned_either3(i32* %b, i32* %r) {
431429
entry:
432430
%call = call i32* @readonly_unknown_r1b(i32* %b, i32* %r)
433431
ret i32* %call
434432
}
435433

436-
define i32* @not_captured_by_readonly_call_not_returned_either4(i32* %b, i32* %r) #0 {
434+
define i32* @not_captured_by_readonly_call_not_returned_either4(i32* %b, i32* %r) nounwind {
437435
entry:
438436
%call = call i32* @readonly_unknown_r1a(i32* %b, i32* %r)
439437
ret i32* %call

llvm/test/Transforms/FunctionAttrs/arg_returned.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefix=FNATTR
2-
; RUN: opt -attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=6 -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
2+
; RUN: opt -attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=7 -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
33
; RUN: opt -attributor -attributor-manifest-internal -attributor-disable=false -functionattrs -S < %s | FileCheck %s --check-prefix=BOTH
44
;
55
; Test cases specifically designed for the "returned" argument attribute.

llvm/test/Transforms/FunctionAttrs/nocapture.ll

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@ l1:
3939
ret i1 1 ; escaping value not caught by def-use chaining.
4040
}
4141

42+
; c4b is c4 but without the escaping part
43+
; FNATTR: define i1 @c4b(i32* %q, i32 %bitno)
44+
; ATTRIBUTOR: define i1 @c4b(i32* nocapture readnone %q, i32 %bitno)
45+
define i1 @c4b(i32* %q, i32 %bitno) {
46+
%tmp = ptrtoint i32* %q to i32
47+
%tmp2 = lshr i32 %tmp, %bitno
48+
%bit = trunc i32 %tmp2 to i1
49+
br i1 %bit, label %l1, label %l0
50+
l0:
51+
ret i1 0 ; not escaping!
52+
l1:
53+
ret i1 0 ; not escaping!
54+
}
55+
4256
@lookup_table = global [2 x i1] [ i1 0, i1 1 ]
4357

4458
; FNATTR: define i1 @c5(i32* %q, i32 %bitno)
@@ -331,5 +345,20 @@ entry:
331345
ret void
332346
}
333347

348+
declare i8* @unknownpi8pi8(i8*,i8* returned)
349+
define i8* @test_returned1(i8* %A, i8* returned %B) nounwind readonly {
350+
; ATTRIBUTOR: define i8* @test_returned1(i8* nocapture readonly %A, i8* readonly returned %B)
351+
entry:
352+
%p = call i8* @unknownpi8pi8(i8* %A, i8* %B)
353+
ret i8* %p
354+
}
355+
356+
define i8* @test_returned2(i8* %A, i8* %B) {
357+
; ATTRIBUTOR: define i8* @test_returned2(i8* nocapture readonly %A, i8* readonly returned %B)
358+
entry:
359+
%p = call i8* @unknownpi8pi8(i8* %A, i8* %B) nounwind readonly
360+
ret i8* %p
361+
}
362+
334363
declare i8* @llvm.launder.invariant.group.p0i8(i8*)
335364
declare i8* @llvm.strip.invariant.group.p0i8(i8*)

llvm/test/Transforms/FunctionAttrs/nonnull.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ bb:
220220

221221
define dso_local noalias i32* @f3(i32* %arg) {
222222
; FIXME: missing nonnull. It should be nonnull @f3(i32* nonnull readonly %arg)
223-
; ATTRIBUTOR: define dso_local noalias i32* @f3(i32* readonly %arg)
223+
; ATTRIBUTOR: define dso_local noalias i32* @f3(i32* nocapture readonly %arg)
224224
bb:
225225
; FIXME: missing nonnull. It should be @f1(i32* nonnull readonly %arg)
226226
; ATTRIBUTOR: %tmp = call i32* @f1(i32* readonly %arg)

llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
; RUN: opt -functionattrs -enable-nonnull-arg-prop -attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=7 -S < %s | FileCheck %s
1+
; RUN: opt -functionattrs -enable-nonnull-arg-prop -attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=8 -S < %s | FileCheck %s
22
;
33
; This is an evolved example to stress test SCC parameter attribute propagation.
44
; The SCC in this test is made up of the following six function, three of which

0 commit comments

Comments
 (0)