mirror of
https://github.com/RPCSX/llvm.git
synced 2025-02-24 15:12:36 +00:00
[attrs] Move the norecurse deduction to operate on the node set rather
than the SCC object, and have it scan the instruction stream directly rather than relying on call records. This makes the behavior of this routine consistent between libc routines and LLVM intrinsics for libc routines. We can go and start teaching it about those being norecurse, but we should behave the same for the intrinsic and the libc routine rather than differently. I chatted with James Molloy and the inconsistency doesn't seem intentional and likely is due to intrinsic calls not being modelled in the call graph analyses. This also fixes a bug where we would deduce norecurse on optnone functions, when generally we try to handle optnone functions as-if they were replaceable and thus unanalyzable. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@260813 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
ae2af9f101
commit
2c37b35a91
@ -991,31 +991,32 @@ static bool setDoesNotRecurse(Function &F) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool addNoRecurseAttrs(const CallGraphSCC &SCC) {
|
static bool addNoRecurseAttrs(const SCCNodeSet &SCCNodes) {
|
||||||
// Try and identify functions that do not recurse.
|
// Try and identify functions that do not recurse.
|
||||||
|
|
||||||
// If the SCC contains multiple nodes we know for sure there is recursion.
|
// If the SCC contains multiple nodes we know for sure there is recursion.
|
||||||
if (!SCC.isSingular())
|
if (SCCNodes.size() != 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const CallGraphNode *CGN = *SCC.begin();
|
Function *F = *SCCNodes.begin();
|
||||||
Function *F = CGN->getFunction();
|
|
||||||
if (!F || F->isDeclaration() || F->doesNotRecurse())
|
if (!F || F->isDeclaration() || F->doesNotRecurse())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If all of the calls in F are identifiable and are to norecurse functions, F
|
// If all of the calls in F are identifiable and are to norecurse functions, F
|
||||||
// is norecurse. This check also detects self-recursion as F is not currently
|
// is norecurse. This check also detects self-recursion as F is not currently
|
||||||
// marked norecurse, so any called from F to F will not be marked norecurse.
|
// marked norecurse, so any called from F to F will not be marked norecurse.
|
||||||
if (std::all_of(CGN->begin(), CGN->end(),
|
for (Instruction &I : instructions(*F))
|
||||||
[](const CallGraphNode::CallRecord &CR) {
|
if (auto CS = CallSite(&I)) {
|
||||||
Function *F = CR.second->getFunction();
|
Function *Callee = CS.getCalledFunction();
|
||||||
return F && F->doesNotRecurse();
|
if (!Callee || Callee == F || !Callee->doesNotRecurse())
|
||||||
}))
|
// Function calls a potentially recursive function.
|
||||||
// Function calls a potentially recursive function.
|
return false;
|
||||||
return setDoesNotRecurse(*F);
|
}
|
||||||
|
|
||||||
// Nothing else we can deduce usefully during the postorder traversal.
|
// Every call was to a non-recursive function other than this function, and
|
||||||
return false;
|
// we have no indirect recursion as the SCC size is one. This function cannot
|
||||||
|
// recurse.
|
||||||
|
return setDoesNotRecurse(*F);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PostOrderFunctionAttrs::runOnSCC(CallGraphSCC &SCC) {
|
bool PostOrderFunctionAttrs::runOnSCC(CallGraphSCC &SCC) {
|
||||||
@ -1060,9 +1061,9 @@ bool PostOrderFunctionAttrs::runOnSCC(CallGraphSCC &SCC) {
|
|||||||
Changed |= addNoAliasAttrs(SCCNodes);
|
Changed |= addNoAliasAttrs(SCCNodes);
|
||||||
Changed |= addNonNullAttrs(SCCNodes, *TLI);
|
Changed |= addNonNullAttrs(SCCNodes, *TLI);
|
||||||
Changed |= removeConvergentAttrs(SCCNodes);
|
Changed |= removeConvergentAttrs(SCCNodes);
|
||||||
|
Changed |= addNoRecurseAttrs(SCCNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Changed |= addNoRecurseAttrs(SCC);
|
|
||||||
return Changed;
|
return Changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,13 +43,13 @@ define void @test1_no(i32* %p) nounwind {
|
|||||||
; This is unusual, since the function is memcpy, but as above, this
|
; This is unusual, since the function is memcpy, but as above, this
|
||||||
; isn't necessarily invalid.
|
; isn't necessarily invalid.
|
||||||
|
|
||||||
; CHECK: define void @test2_yes(i8* nocapture %p, i8* nocapture %q, i64 %n) #0 {
|
; CHECK: define void @test2_yes(i8* nocapture %p, i8* nocapture %q, i64 %n) #4 {
|
||||||
define void @test2_yes(i8* %p, i8* %q, i64 %n) nounwind {
|
define void @test2_yes(i8* %p, i8* %q, i64 %n) nounwind {
|
||||||
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %p, i8* %q, i64 %n, i32 1, i1 false), !tbaa !1
|
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %p, i8* %q, i64 %n, i32 1, i1 false), !tbaa !1
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
; CHECK: define void @test2_no(i8* nocapture %p, i8* nocapture readonly %q, i64 %n) #1 {
|
; CHECK: define void @test2_no(i8* nocapture %p, i8* nocapture readonly %q, i64 %n) #3 {
|
||||||
define void @test2_no(i8* %p, i8* %q, i64 %n) nounwind {
|
define void @test2_no(i8* %p, i8* %q, i64 %n) nounwind {
|
||||||
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %p, i8* %q, i64 %n, i32 1, i1 false), !tbaa !2
|
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %p, i8* %q, i64 %n, i32 1, i1 false), !tbaa !2
|
||||||
ret void
|
ret void
|
||||||
@ -76,7 +76,8 @@ declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i32, i1) nounwind
|
|||||||
; CHECK: attributes #1 = { norecurse nounwind }
|
; CHECK: attributes #1 = { norecurse nounwind }
|
||||||
; CHECK: attributes #2 = { nounwind readonly }
|
; CHECK: attributes #2 = { nounwind readonly }
|
||||||
; CHECK: attributes #3 = { nounwind }
|
; CHECK: attributes #3 = { nounwind }
|
||||||
; CHECK: attributes #4 = { argmemonly nounwind }
|
; CHECK: attributes #4 = { nounwind readnone }
|
||||||
|
; CHECK: attributes #5 = { argmemonly nounwind }
|
||||||
|
|
||||||
; Root note.
|
; Root note.
|
||||||
!0 = !{ }
|
!0 = !{ }
|
||||||
|
@ -29,6 +29,13 @@ define i32 @extern() {
|
|||||||
}
|
}
|
||||||
declare i32 @k() readnone
|
declare i32 @k() readnone
|
||||||
|
|
||||||
|
; CHECK: define void @intrinsic(i8* nocapture %dest, i8* nocapture readonly %src, i32 %len) {
|
||||||
|
define void @intrinsic(i8* %dest, i8* %src, i32 %len) {
|
||||||
|
call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i32 1, i1 false)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
declare void @llvm.memcpy.p0i8.p0i8.i32(i8*, i8*, i32, i32, i1)
|
||||||
|
|
||||||
; CHECK: define internal i32 @called_by_norecurse() #0
|
; CHECK: define internal i32 @called_by_norecurse() #0
|
||||||
define internal i32 @called_by_norecurse() {
|
define internal i32 @called_by_norecurse() {
|
||||||
%a = call i32 @k()
|
%a = call i32 @k()
|
||||||
|
@ -16,11 +16,9 @@ define void @test_optnone(i8* %p) noinline optnone {
|
|||||||
|
|
||||||
declare i8 @strlen(i8*) noinline optnone
|
declare i8 @strlen(i8*) noinline optnone
|
||||||
; CHECK-LABEL: @strlen
|
; CHECK-LABEL: @strlen
|
||||||
; CHECK: (i8*) #2
|
; CHECK: (i8*) #1
|
||||||
|
|
||||||
; CHECK-LABEL: attributes #0
|
; CHECK-LABEL: attributes #0
|
||||||
; CHECK: = { norecurse readnone }
|
; CHECK: = { norecurse readnone }
|
||||||
; CHECK-LABEL: attributes #1
|
; CHECK-LABEL: attributes #1
|
||||||
; CHECK: = { noinline norecurse optnone }
|
|
||||||
; CHECK-LABEL: attributes #2
|
|
||||||
; CHECK: = { noinline optnone }
|
; CHECK: = { noinline optnone }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user