Skip to content

Commit

Permalink
[FuncAttrs] Infer noreturn
Browse files Browse the repository at this point in the history
A function is noreturn if all blocks terminating with a ReturnInst
contain a call to a noreturn function. Skip looking at naked functions
since there may be asm that returns.

This can be further refined in the future by checking unreachable blocks
and taking into account recursion. It looks like the attributor pass
does this, but that is not yet enabled by default.

This seems to help with code size under the new PM since PruneEH does
not run under the new PM, missing opportunities to mark some functions
noreturn, which in turn doesn't allow simplifycfg to clean up dead code.
https://bugs.llvm.org/show_bug.cgi?id=46858.

Reviewed By: rnk

Differential Revision: https://reviews.llvm.org/D93946
  • Loading branch information
aeubanks committed Jan 5, 2021
1 parent 90b8fd6 commit 8cf1cc5
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 0 deletions.
37 changes: 37 additions & 0 deletions llvm/lib/Transforms/IPO/FunctionAttrs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,42 @@ static bool addNoRecurseAttrs(const SCCNodeSet &SCCNodes) {
return true;
}

static bool instructionDoesNotReturn(Instruction &I) {
if (auto *CB = dyn_cast<CallBase>(&I)) {
Function *Callee = CB->getCalledFunction();
return Callee && Callee->doesNotReturn();
}
return false;
}

// A basic block can only return if it terminates with a ReturnInst and does not
// contain calls to noreturn functions.
static bool basicBlockCanReturn(BasicBlock &BB) {
if (!isa<ReturnInst>(BB.getTerminator()))
return false;
return none_of(BB, instructionDoesNotReturn);
}

// Set the noreturn function attribute if possible.
static bool addNoReturnAttrs(const SCCNodeSet &SCCNodes) {
bool Changed = false;

for (Function *F : SCCNodes) {
if (!F || !F->hasExactDefinition() || F->hasFnAttribute(Attribute::Naked) ||
F->doesNotReturn())
continue;

// The function can return if any basic blocks can return.
// FIXME: this doesn't handle recursion or unreachable blocks.
if (none_of(*F, basicBlockCanReturn)) {
F->setDoesNotReturn();
Changed = true;
}
}

return Changed;
}

static SCCNodesResult createSCCNodeSet(ArrayRef<Function *> Functions) {
SCCNodesResult Res;
Res.HasUnknownCall = false;
Expand Down Expand Up @@ -1431,6 +1467,7 @@ static bool deriveAttrsInPostOrder(ArrayRef<Function *> Functions,
Changed |= addReadAttrs(Nodes.SCCNodes, AARGetter);
Changed |= addArgumentAttrs(Nodes.SCCNodes);
Changed |= inferConvergent(Nodes.SCCNodes);
Changed |= addNoReturnAttrs(Nodes.SCCNodes);

// If we have no external nodes participating in the SCC, we can deduce some
// more precise attributes as well.
Expand Down
66 changes: 66 additions & 0 deletions llvm/test/Transforms/FunctionAttrs/noreturn.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
; RUN: opt < %s -passes='function-attrs' -S | FileCheck %s

declare i32 @f()

; CHECK: Function Attrs: noreturn
; CHECK-NEXT: @noreturn()
declare i32 @noreturn() noreturn

; CHECK: Function Attrs: noreturn
; CHECK-NEXT: @caller()
define i32 @caller() {
%c = call i32 @noreturn()
ret i32 %c
}

; CHECK: Function Attrs: noreturn
; CHECK-NEXT: @caller2()
define i32 @caller2() {
%c = call i32 @caller()
ret i32 %c
}

; CHECK: Function Attrs: noreturn
; CHECK-NEXT: @caller3()
define i32 @caller3() {
entry:
br label %end
end:
%c = call i32 @noreturn()
ret i32 %c
}

; CHECK-NOT: Function Attrs: {{.*}}noreturn
; CHECK: define i32 @caller4()
define i32 @caller4() {
entry:
br label %end
end:
%c = call i32 @f()
ret i32 %c
}

; CHECK-NOT: Function Attrs: {{.*}}noreturn
; CHECK: @caller5()
; We currently don't handle unreachable blocks.
define i32 @caller5() {
entry:
%c = call i32 @noreturn()
ret i32 %c
unreach:
%d = call i32 @f()
ret i32 %d
}

; CHECK-NOT: Function Attrs: {{.*}}noreturn
; CHECK: @caller6()
define i32 @caller6() naked {
%c = call i32 @noreturn()
ret i32 %c
}

; CHECK: Function Attrs: {{.*}}noreturn
; CHECK-NEXT: @alreadynoreturn()
define i32 @alreadynoreturn() noreturn {
unreachable
}
1 change: 1 addition & 0 deletions llvm/test/Transforms/PruneEH/simplenoreturntest.ll
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
; RUN: opt < %s -prune-eh -S -enable-new-pm=0 | not grep "ret i32"
; RUN: opt < %s -passes='function-attrs,function(simplifycfg)' -S | not grep "ret i32"

declare void @noreturn() noreturn

Expand Down

0 comments on commit 8cf1cc5

Please sign in to comment.