diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index 0d25d5d319e750..62e3e5ad0a52be 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -1388,6 +1388,42 @@ static bool addNoRecurseAttrs(const SCCNodeSet &SCCNodes) { return true; } +static bool instructionDoesNotReturn(Instruction &I) { + if (auto *CB = dyn_cast(&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(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 Functions) { SCCNodesResult Res; Res.HasUnknownCall = false; @@ -1431,6 +1467,7 @@ static bool deriveAttrsInPostOrder(ArrayRef 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. diff --git a/llvm/test/Transforms/FunctionAttrs/noreturn.ll b/llvm/test/Transforms/FunctionAttrs/noreturn.ll new file mode 100644 index 00000000000000..acc538d3cceef7 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/noreturn.ll @@ -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 +} diff --git a/llvm/test/Transforms/PruneEH/simplenoreturntest.ll b/llvm/test/Transforms/PruneEH/simplenoreturntest.ll index 814f8b4a686f0d..ccb9f5eb6f59c7 100644 --- a/llvm/test/Transforms/PruneEH/simplenoreturntest.ll +++ b/llvm/test/Transforms/PruneEH/simplenoreturntest.ll @@ -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