forked from OSchip/llvm-project
[Attributor] Deduce the "no-return" attribute for functions
A function is "no-return" if we never reach a return instruction, either because there are none or the ones that exist are dead. Test have been adjusted: - either noreturn was added, or - noreturn was avoided by modifying the code. The new noreturn_{sync,async} test make sure we do handle invoke instructions with a noreturn (and potentially nowunwind) callee correctly, even in the presence of potential asynchronous exceptions. llvm-svn: 367948
This commit is contained in:
parent
a5c25c5d46
commit
e83f303938
|
@ -1480,8 +1480,9 @@ example:
|
|||
target-specific ABI normally permits it.
|
||||
``noreturn``
|
||||
This function attribute indicates that the function never returns
|
||||
normally. This produces undefined behavior at runtime if the
|
||||
function ever does dynamically return.
|
||||
normally, hence through a return instruction. This produces undefined
|
||||
behavior at runtime if the function ever does dynamically return. Annotated
|
||||
functions may still raise an exception, i.a., ``nounwind`` is not implied.
|
||||
``norecurse``
|
||||
This function attribute indicates that the function does not call itself
|
||||
either directly or indirectly down any possible call path. This produces
|
||||
|
|
|
@ -76,6 +76,7 @@ STATISTIC(NumCSArgumentDereferenceable,
|
|||
STATISTIC(NumFnReturnedAlign, "Number of function return values marked align");
|
||||
STATISTIC(NumFnArgumentAlign, "Number of function arguments marked align");
|
||||
STATISTIC(NumCSArgumentAlign, "Number of call site arguments marked align");
|
||||
STATISTIC(NumFnNoReturn, "Number of functions marked noreturn");
|
||||
|
||||
// TODO: Determine a good default value.
|
||||
//
|
||||
|
@ -179,6 +180,9 @@ static void bookkeeping(AbstractAttribute::ManifestPosition MP,
|
|||
case Attribute::WillReturn:
|
||||
NumFnWillReturn++;
|
||||
break;
|
||||
case Attribute::NoReturn:
|
||||
NumFnNoReturn++;
|
||||
return;
|
||||
case Attribute::NoAlias:
|
||||
NumFnArgumentNoAlias++;
|
||||
return;
|
||||
|
@ -2336,6 +2340,60 @@ ChangeStatus AAAlignCallSiteArgument::updateImpl(Attributor &A) {
|
|||
: ChangeStatus::CHANGED;
|
||||
}
|
||||
|
||||
/// ------------------ Function No-Return Attribute ----------------------------
|
||||
struct AANoReturnFunction final : public AANoReturn, BooleanState {
|
||||
|
||||
AANoReturnFunction(Function &F, InformationCache &InfoCache)
|
||||
: AANoReturn(F, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::getState()
|
||||
/// {
|
||||
AbstractState &getState() override { return *this; }
|
||||
const AbstractState &getState() const override { return *this; }
|
||||
/// }
|
||||
|
||||
/// Return true if the underlying object is known to never return.
|
||||
bool isKnownNoReturn() const override { return getKnown(); }
|
||||
|
||||
/// Return true if the underlying object is assumed to never return.
|
||||
bool isAssumedNoReturn() const override { return getAssumed(); }
|
||||
|
||||
/// See AbstractAttribute::getManifestPosition().
|
||||
ManifestPosition getManifestPosition() const override { return MP_FUNCTION; }
|
||||
|
||||
/// See AbstractAttribute::getAsStr().
|
||||
const std::string getAsStr() const override {
|
||||
return getAssumed() ? "noreturn" : "may-return";
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::initialize(...).
|
||||
void initialize(Attributor &A) override {
|
||||
Function &F = getAnchorScope();
|
||||
if (F.hasFnAttribute(getAttrKind()))
|
||||
indicateOptimisticFixpoint();
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::updateImpl(Attributor &A).
|
||||
virtual ChangeStatus updateImpl(Attributor &A) override {
|
||||
Function &F = getAnchorScope();
|
||||
|
||||
// The map from instruction opcodes to those instructions in the function.
|
||||
auto &OpcodeInstMap = InfoCache.getOpcodeInstMapForFunction(F);
|
||||
|
||||
// Look at all return instructions.
|
||||
auto &ReturnInsts = OpcodeInstMap[Instruction::Ret];
|
||||
if (ReturnInsts.empty())
|
||||
return indicateOptimisticFixpoint();
|
||||
|
||||
auto *LivenessAA = A.getAAFor<AAIsDead>(*this, F);
|
||||
if (!LivenessAA ||
|
||||
LivenessAA->isLiveInstSet(ReturnInsts.begin(), ReturnInsts.end()))
|
||||
return indicatePessimisticFixpoint();
|
||||
|
||||
return ChangeStatus::UNCHANGED;
|
||||
}
|
||||
};
|
||||
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Attributor
|
||||
/// ----------------------------------------------------------------------------
|
||||
|
@ -2539,6 +2597,9 @@ void Attributor::identifyDefaultAbstractAttributes(
|
|||
// Every function might be "no-free".
|
||||
registerAA(*new AANoFreeFunction(F, InfoCache));
|
||||
|
||||
// Every function might be "no-return".
|
||||
registerAA(*new AANoReturnFunction(F, InfoCache));
|
||||
|
||||
// Return attributes are only appropriate if the return type is non void.
|
||||
Type *ReturnType = F.getReturnType();
|
||||
if (!ReturnType->isVoidTy()) {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
; RUN: opt -functionattrs -attributor -attributor-disable=false -S < %s | FileCheck %s
|
||||
; RUN: opt -functionattrs -attributor -attributor-disable=false -attributor-verify=true -S < %s | FileCheck %s
|
||||
;
|
||||
; Test cases specifically designed for the "no-capture" argument attribute.
|
||||
; We use FIXME's to indicate problems and missing attributes.
|
||||
|
@ -87,11 +86,12 @@ entry:
|
|||
;
|
||||
; Other arguments are possible here due to the no-return behavior.
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @srec16(i32* nocapture readnone %a)
|
||||
define i32* @srec16(i32* %a) #0 {
|
||||
entry:
|
||||
%call = call i32* @srec16(i32* %a)
|
||||
; CHECK: %call = call i32* @srec16(i32* %a)
|
||||
; CHECK-NEXT: unreachable
|
||||
%call1 = call i32* @srec16(i32* %call)
|
||||
%call2 = call i32* @srec16(i32* %call1)
|
||||
%call3 = call i32* @srec16(i32* %call2)
|
||||
|
@ -131,7 +131,7 @@ entry:
|
|||
; }
|
||||
;
|
||||
; void *scc_C(short *a) {
|
||||
; return scc_A((int*)(scc_C(a) ? scc_B((double*)a) : scc_C(a)));
|
||||
; return scc_A((int*)(scc_A(a) ? scc_B((double*)a) : scc_C(a)));
|
||||
; }
|
||||
define float* @scc_A(i32* %a) {
|
||||
entry:
|
||||
|
@ -183,8 +183,10 @@ cond.end: ; preds = %cond.false, %cond.t
|
|||
|
||||
define i8* @scc_C(i16* %a) {
|
||||
entry:
|
||||
%call = call i8* @scc_C(i16* %a)
|
||||
%tobool = icmp ne i8* %call, null
|
||||
%bc = bitcast i16* %a to i32*
|
||||
%call = call float* @scc_A(i32* %bc)
|
||||
%bc2 = bitcast float* %call to i8*
|
||||
%tobool = icmp ne i8* %bc2, null
|
||||
br i1 %tobool, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %entry
|
||||
|
|
|
@ -259,10 +259,9 @@ return: ; preds = %cond.end, %if.then3
|
|||
; return *a ? a : rt0(a);
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; FNATTR: define i32* @rt0(i32* readonly %a)
|
||||
; BOTH: Function Attrs: nofree noinline nosync nounwind readonly uwtable
|
||||
; BOTH-NEXT: define i32* @rt0(i32* readonly returned %a)
|
||||
; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable
|
||||
; BOTH-NEXT: define i32* @rt0(i32* readonly %a)
|
||||
define i32* @rt0(i32* %a) #0 {
|
||||
entry:
|
||||
%v = load i32, i32* %a, align 4
|
||||
|
@ -278,9 +277,8 @@ entry:
|
|||
; return *a ? undef : rt1(a);
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a)
|
||||
; BOTH: Function Attrs: nofree noinline nosync nounwind readonly uwtable
|
||||
; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable
|
||||
; BOTH-NEXT: define noalias i32* @rt1(i32* nocapture readonly %a)
|
||||
define i32* @rt1(i32* %a) #0 {
|
||||
entry:
|
||||
|
@ -746,7 +744,7 @@ attributes #0 = { noinline nounwind uwtable }
|
|||
; BOTH-NOT: attributes #
|
||||
; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline norecurse nosync nounwind readnone uwtable willreturn }
|
||||
; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline nosync nounwind readnone uwtable }
|
||||
; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline nosync nounwind readonly uwtable }
|
||||
; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline noreturn nosync nounwind readonly uwtable }
|
||||
; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind uwtable }
|
||||
; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline nosync nounwind readnone uwtable willreturn }
|
||||
; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline nosync nounwind uwtable willreturn }
|
||||
|
|
|
@ -1,26 +1,18 @@
|
|||
; RUN: opt -functionattrs -attributor -attributor-disable=false -S < %s | FileCheck %s
|
||||
; RUN: opt -functionattrs -attributor -attributor-disable=false -attributor-verify=true -S < %s | FileCheck %s
|
||||
;
|
||||
; Test cases specifically designed for the "no-return" function attribute.
|
||||
; We use FIXME's to indicate problems and missing attributes.
|
||||
;
|
||||
; TEST 1: singleton SCC void return type
|
||||
; TEST 2: singleton SCC int return type with a lot of recursive calls
|
||||
; TEST 3: endless loop, no return instruction
|
||||
; TEST 4: endless loop, dead return instruction
|
||||
; TEST 5: all paths contain a no-return function call
|
||||
;
|
||||
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
|
||||
; TEST 1
|
||||
; TEST 1, singleton SCC void return type
|
||||
;
|
||||
; void srec0() {
|
||||
; return srec0();
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: nofree noinline nosync nounwind readnone uwtable
|
||||
; CHECK: Function Attrs: nofree noinline noreturn nosync nounwind readnone uwtable
|
||||
; CHECK: define void @srec0()
|
||||
;
|
||||
define void @srec0() #0 {
|
||||
|
@ -30,14 +22,13 @@ entry:
|
|||
}
|
||||
|
||||
|
||||
; TEST 2
|
||||
; TEST 2: singleton SCC int return type with a lot of recursive calls
|
||||
;
|
||||
; int srec16(int a) {
|
||||
; return srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(a))))))))))))))));
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: nofree noinline nosync nounwind readnone uwtable
|
||||
; CHECK: Function Attrs: nofree noinline noreturn nosync nounwind readnone uwtable
|
||||
; CHECK: define i32 @srec16(i32 %a)
|
||||
;
|
||||
define i32 @srec16(i32 %a) #0 {
|
||||
|
@ -58,18 +49,20 @@ entry:
|
|||
%call13 = call i32 @srec16(i32 %call12)
|
||||
%call14 = call i32 @srec16(i32 %call13)
|
||||
%call15 = call i32 @srec16(i32 %call14)
|
||||
br label %exit
|
||||
|
||||
exit:
|
||||
ret i32 %call15
|
||||
}
|
||||
|
||||
|
||||
; TEST 3
|
||||
; TEST 3: endless loop, no return instruction
|
||||
;
|
||||
; int endless_loop(int a) {
|
||||
; while (1);
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind readnone uwtable
|
||||
; CHECK: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readnone uwtable
|
||||
; CHECK: define i32 @endless_loop(i32 %a)
|
||||
;
|
||||
define i32 @endless_loop(i32 %a) #0 {
|
||||
|
@ -81,15 +74,15 @@ while.body: ; preds = %entry, %while.body
|
|||
}
|
||||
|
||||
|
||||
; TEST 4
|
||||
; TEST 4: endless loop, dead return instruction
|
||||
;
|
||||
; int endless_loop(int a) {
|
||||
; while (1);
|
||||
; return a;
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: nofree noinline norecurse nosync nounwind readnone uwtable
|
||||
; FIXME: no-return missing (D65243 should fix this)
|
||||
; CHECK: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readnone uwtable
|
||||
; CHECK: define i32 @dead_return(i32 returned %a)
|
||||
;
|
||||
define i32 @dead_return(i32 %a) #0 {
|
||||
|
@ -104,14 +97,13 @@ return: ; No predecessors!
|
|||
}
|
||||
|
||||
|
||||
; TEST 5
|
||||
; TEST 5: all paths contain a no-return function call
|
||||
;
|
||||
; int multiple_noreturn_calls(int a) {
|
||||
; return a == 0 ? endless_loop(a) : srec16(a);
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: nofree noinline nosync nounwind readnone uwtable
|
||||
; CHECK: Function Attrs: nofree noinline noreturn nosync nounwind readnone uwtable
|
||||
; CHECK: define i32 @multiple_noreturn_calls(i32 %a)
|
||||
;
|
||||
define i32 @multiple_noreturn_calls(i32 %a) #0 {
|
||||
|
|
|
@ -26,7 +26,7 @@ define internal i32 @internal_load(i32*) norecurse nounwind uwtable {
|
|||
}
|
||||
; TEST 1: Only first block is live.
|
||||
|
||||
; CHECK: Function Attrs: nofree nosync nounwind
|
||||
; CHECK: Function Attrs: nofree noreturn nosync nounwind
|
||||
; CHECK-NEXT: define i32 @first_block_no_return(i32 %a, i32* nonnull %ptr1, i32* %ptr2)
|
||||
define i32 @first_block_no_return(i32 %a, i32* nonnull %ptr1, i32* %ptr2) #0 {
|
||||
entry:
|
||||
|
|
|
@ -67,8 +67,15 @@ define void @free_in_scc1(i8* nocapture %0) local_unnamed_addr #0 {
|
|||
; ATTRIBUTOR-NOT: nofree
|
||||
; ATTRIBUTOR: define void @free_in_scc2(i8* nocapture %0) local_unnamed_addr
|
||||
define void @free_in_scc2(i8* nocapture %0) local_unnamed_addr #0 {
|
||||
tail call void @free_in_scc1(i8* %0)
|
||||
%cmp = icmp eq i8* %0, null
|
||||
br i1 %cmp, label %rec, label %call
|
||||
call:
|
||||
tail call void @free(i8* %0) #1
|
||||
br label %end
|
||||
rec:
|
||||
tail call void @free_in_scc1(i8* %0)
|
||||
br label %end
|
||||
end:
|
||||
ret void
|
||||
}
|
||||
|
||||
|
@ -85,7 +92,7 @@ define void @free_in_scc2(i8* nocapture %0) local_unnamed_addr #0 {
|
|||
|
||||
; FNATTR: Function Attrs: noinline nounwind readnone uwtable
|
||||
; FNATTR-NEXT: define void @mutual_recursion1()
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline noreturn nosync nounwind uwtable
|
||||
; ATTRIBUTOR-NEXT: define void @mutual_recursion1()
|
||||
define void @mutual_recursion1() #0 {
|
||||
call void @mutual_recursion2()
|
||||
|
@ -94,7 +101,7 @@ define void @mutual_recursion1() #0 {
|
|||
|
||||
; FNATTR: Function Attrs: noinline nounwind readnone uwtable
|
||||
; FNATTR-NEXT: define void @mutual_recursion2()
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline noreturn nosync nounwind uwtable
|
||||
; ATTRIBUTOR-NEXT: define void @mutual_recursion2()
|
||||
define void @mutual_recursion2() #0 {
|
||||
call void @mutual_recursion1()
|
||||
|
|
|
@ -21,16 +21,20 @@ define i8* @test2(i8* nonnull %p) {
|
|||
|
||||
; Given an SCC where one of the functions can not be marked nonnull,
|
||||
; can we still mark the other one which is trivially nonnull
|
||||
define i8* @scc_binder() {
|
||||
define i8* @scc_binder(i1 %c) {
|
||||
; FNATTR: define i8* @scc_binder
|
||||
; ATTRIBUTOR: define noalias i8* @scc_binder
|
||||
call i8* @test3()
|
||||
br i1 %c, label %rec, label %end
|
||||
rec:
|
||||
call i8* @test3(i1 %c)
|
||||
br label %end
|
||||
end:
|
||||
ret i8* null
|
||||
}
|
||||
|
||||
define i8* @test3() {
|
||||
define i8* @test3(i1 %c) {
|
||||
; BOTH: define nonnull i8* @test3
|
||||
call i8* @scc_binder()
|
||||
call i8* @scc_binder(i1 %c)
|
||||
%ret = call i8* @ret_nonnull()
|
||||
ret i8* %ret
|
||||
}
|
||||
|
@ -54,17 +58,21 @@ define i8* @test4() {
|
|||
|
||||
; Given a mutual recursive set of functions which *can* return null
|
||||
; make sure we haven't marked them as nonnull.
|
||||
define i8* @test5_helper() {
|
||||
define i8* @test5_helper(i1 %c) {
|
||||
; FNATTR: define noalias i8* @test5_helper
|
||||
; ATTRIBUTOR: define noalias i8* @test5_helper
|
||||
%ret = call i8* @test5()
|
||||
br i1 %c, label %rec, label %end
|
||||
rec:
|
||||
%ret = call i8* @test5(i1 %c)
|
||||
br label %end
|
||||
end:
|
||||
ret i8* null
|
||||
}
|
||||
|
||||
define i8* @test5() {
|
||||
define i8* @test5(i1 %c) {
|
||||
; FNATTR: define noalias i8* @test5
|
||||
; ATTRIBUTOR: define noalias i8* @test5
|
||||
%ret = call i8* @test5_helper()
|
||||
%ret = call i8* @test5_helper(i1 %c)
|
||||
ret i8* %ret
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
; RUN: opt -functionattrs -attributor -attributor-disable=false -S < %s | FileCheck %s
|
||||
;
|
||||
; This file is the same as noreturn_sync.ll but with a personality which
|
||||
; indicates that the exception handler *can* catch asynchronous exceptions. As
|
||||
; a consequence, invokes to noreturn and nounwind functions are not translated
|
||||
; to calls followed by an unreachable but the unwind edge is considered live.
|
||||
;
|
||||
; https://reviews.llvm.org/D59978#inline-586873
|
||||
;
|
||||
; Make sure we handle invoke of a noreturn function correctly.
|
||||
;
|
||||
; This test is also a reminder of how we handle (=ignore) stackoverflow exception handling.
|
||||
;
|
||||
target datalayout = "e-m:w-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-pc-windows-msvc19.16.27032"
|
||||
|
||||
@"??_C@_0BG@CMNEKHOP@Exception?5NOT?5caught?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [22 x i8] c"Exception NOT caught\0A\00", align 1
|
||||
@"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [18 x i8] c"Exception caught\0A\00", align 1
|
||||
@"??_C@_0BK@JHJLGDKL@Done?5execution?5result?$DN?$CFi?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [26 x i8] c"Done execution result=%i\0A\00", align 1
|
||||
@"?_OptionsStorage@?1??__local_stdio_printf_options@@9@4_KA" = linkonce_odr dso_local global i64 0, align 8
|
||||
|
||||
|
||||
define dso_local void @"?overflow@@YAXXZ"() {
|
||||
entry:
|
||||
; CHECK: Function Attrs: nofree noreturn nosync nounwind
|
||||
; CHECK-NEXT: define
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: call void @"?overflow@@YAXXZ"()
|
||||
; CHECK-NEXT: unreachable
|
||||
call void @"?overflow@@YAXXZ"()
|
||||
%call3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
; CHECK-NOT: nounwind
|
||||
; CHECK-NOT: noreturn
|
||||
; CHECK: define
|
||||
; CHECK-SAME: @"?catchoverflow@@YAHXZ"()
|
||||
define dso_local i32 @"?catchoverflow@@YAHXZ"() personality i8* bitcast (i32 (...)* @__C_specific_handler to i8*) {
|
||||
entry:
|
||||
%retval = alloca i32, align 4
|
||||
%__exception_code = alloca i32, align 4
|
||||
; CHECK: invoke void @"?overflow@@YAXXZ"()
|
||||
; CHECK: to label %invoke.cont unwind label %catch.dispatch
|
||||
invoke void @"?overflow@@YAXXZ"()
|
||||
to label %invoke.cont unwind label %catch.dispatch
|
||||
|
||||
invoke.cont: ; preds = %entry
|
||||
; CHECK: invoke.cont:
|
||||
; CHECK-NEXT: unreachable
|
||||
br label %invoke.cont1
|
||||
|
||||
catch.dispatch: ; preds = %invoke.cont, %entry
|
||||
%0 = catchswitch within none [label %__except] unwind to caller
|
||||
|
||||
__except: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [i8* null]
|
||||
catchret from %1 to label %__except2
|
||||
|
||||
__except2: ; preds = %__except
|
||||
%2 = call i32 @llvm.eh.exceptioncode(token %1)
|
||||
store i32 1, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
invoke.cont1: ; preds = %invoke.cont
|
||||
store i32 0, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
__try.cont: ; No predecessors!
|
||||
store i32 2, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
return: ; preds = %__try.cont, %__except2, %invoke.cont1
|
||||
%3 = load i32, i32* %retval, align 4
|
||||
ret i32 %3
|
||||
}
|
||||
|
||||
|
||||
define dso_local void @"?overflow@@YAXXZ_may_throw"() {
|
||||
entry:
|
||||
; CHECK: Function Attrs: noreturn
|
||||
; CHECK-NOT: nounwind
|
||||
; CHECK-NEXT: define
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: %call3 = call i32 (i8*, ...) @printf(i8* dereferenceable_or_null(18) getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
; CHECK-NEXT: call void @"?overflow@@YAXXZ_may_throw"()
|
||||
; CHECK-NEXT: unreachable
|
||||
%call3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
call void @"?overflow@@YAXXZ_may_throw"()
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
; CHECK-NOT: nounwind
|
||||
; CHECK-NOT: noreturn
|
||||
; CHECK: define
|
||||
; CHECK-SAME: @"?catchoverflow@@YAHXZ_may_throw"()
|
||||
define dso_local i32 @"?catchoverflow@@YAHXZ_may_throw"() personality i8* bitcast (i32 (...)* @__C_specific_handler to i8*) {
|
||||
entry:
|
||||
%retval = alloca i32, align 4
|
||||
%__exception_code = alloca i32, align 4
|
||||
; CHECK: invoke void @"?overflow@@YAXXZ_may_throw"()
|
||||
; CHECK: to label %invoke.cont unwind label %catch.dispatch
|
||||
invoke void @"?overflow@@YAXXZ_may_throw"()
|
||||
to label %invoke.cont unwind label %catch.dispatch
|
||||
|
||||
invoke.cont: ; preds = %entry
|
||||
; CHECK: invoke.cont:
|
||||
; CHECK-NEXT: unreachable
|
||||
br label %invoke.cont1
|
||||
|
||||
catch.dispatch: ; preds = %invoke.cont, %entry
|
||||
%0 = catchswitch within none [label %__except] unwind to caller
|
||||
|
||||
__except: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [i8* null]
|
||||
catchret from %1 to label %__except2
|
||||
|
||||
__except2: ; preds = %__except
|
||||
%2 = call i32 @llvm.eh.exceptioncode(token %1)
|
||||
store i32 1, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
invoke.cont1: ; preds = %invoke.cont
|
||||
store i32 0, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
__try.cont: ; No predecessors!
|
||||
store i32 2, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
return: ; preds = %__try.cont, %__except2, %invoke.cont1
|
||||
%3 = load i32, i32* %retval, align 4
|
||||
ret i32 %3
|
||||
}
|
||||
|
||||
declare dso_local i32 @__C_specific_handler(...)
|
||||
|
||||
declare dso_local i32 @printf(i8* %_Format, ...)
|
||||
|
||||
declare i32 @llvm.eh.exceptioncode(token)
|
|
@ -0,0 +1,138 @@
|
|||
; RUN: opt -functionattrs -attributor -attributor-disable=false -S < %s | FileCheck %s
|
||||
;
|
||||
; This file is the same as noreturn_async.ll but with a personality which
|
||||
; indicates that the exception handler *cannot* catch asynchronous exceptions.
|
||||
; As a consequence, invokes to noreturn and nounwind functions are translated
|
||||
; to calls followed by an unreachable.
|
||||
;
|
||||
; https://reviews.llvm.org/D59978#inline-586873
|
||||
;
|
||||
; Make sure we handle invoke of a noreturn function correctly.
|
||||
;
|
||||
; This test is also a reminder of how we handle (=ignore) stackoverflow exception handling.
|
||||
;
|
||||
target datalayout = "e-m:w-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-pc-linux-gnu"
|
||||
|
||||
@"??_C@_0BG@CMNEKHOP@Exception?5NOT?5caught?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [22 x i8] c"Exception NOT caught\0A\00", align 1
|
||||
@"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [18 x i8] c"Exception caught\0A\00", align 1
|
||||
@"??_C@_0BK@JHJLGDKL@Done?5execution?5result?$DN?$CFi?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [26 x i8] c"Done execution result=%i\0A\00", align 1
|
||||
@"?_OptionsStorage@?1??__local_stdio_printf_options@@9@4_KA" = linkonce_odr dso_local global i64 0, align 8
|
||||
|
||||
|
||||
define dso_local void @"?overflow@@YAXXZ"() {
|
||||
entry:
|
||||
; CHECK: Function Attrs: nofree noreturn nosync nounwind
|
||||
; CHECK-NEXT: define
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: call void @"?overflow@@YAXXZ"()
|
||||
; CHECK-NEXT: unreachable
|
||||
call void @"?overflow@@YAXXZ"()
|
||||
%call3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
; CHECK: Function Attrs: nofree noreturn nosync nounwind
|
||||
; CHECK-NEXT: @"?catchoverflow@@YAHXZ"()
|
||||
define dso_local i32 @"?catchoverflow@@YAHXZ"() personality i8* bitcast (i32 (...)* @__gcc_personality_v0 to i8*) {
|
||||
entry:
|
||||
%retval = alloca i32, align 4
|
||||
%__exception_code = alloca i32, align 4
|
||||
invoke void @"?overflow@@YAXXZ"()
|
||||
to label %invoke.cont unwind label %catch.dispatch
|
||||
; CHECK: call void @"?overflow@@YAXXZ"()
|
||||
; CHECK-NEXT: unreachable
|
||||
|
||||
invoke.cont: ; preds = %entry
|
||||
br label %invoke.cont1
|
||||
|
||||
catch.dispatch: ; preds = %invoke.cont, %entry
|
||||
%0 = catchswitch within none [label %__except] unwind to caller
|
||||
|
||||
__except: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [i8* null]
|
||||
catchret from %1 to label %__except2
|
||||
|
||||
__except2: ; preds = %__except
|
||||
%2 = call i32 @llvm.eh.exceptioncode(token %1)
|
||||
store i32 1, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
invoke.cont1: ; preds = %invoke.cont
|
||||
store i32 0, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
__try.cont: ; No predecessors!
|
||||
store i32 2, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
return: ; preds = %__try.cont, %__except2, %invoke.cont1
|
||||
%3 = load i32, i32* %retval, align 4
|
||||
ret i32 %3
|
||||
}
|
||||
|
||||
|
||||
define dso_local void @"?overflow@@YAXXZ_may_throw"() {
|
||||
entry:
|
||||
; CHECK: Function Attrs: noreturn
|
||||
; CHECK-NOT: nounwind
|
||||
; CHECK-NEXT: define
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: %call3 = call i32 (i8*, ...) @printf(i8* dereferenceable_or_null(18) getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
; CHECK-NEXT: call void @"?overflow@@YAXXZ_may_throw"()
|
||||
; CHECK-NEXT: unreachable
|
||||
%call3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
call void @"?overflow@@YAXXZ_may_throw"()
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
; CHECK-NOT: nounwind
|
||||
; CHECK-NOT: noreturn
|
||||
; CHECK: define
|
||||
; CHECK-SAME: @"?catchoverflow@@YAHXZ_may_throw"()
|
||||
define dso_local i32 @"?catchoverflow@@YAHXZ_may_throw"() personality i8* bitcast (i32 (...)* @__gcc_personality_v0 to i8*) {
|
||||
entry:
|
||||
%retval = alloca i32, align 4
|
||||
%__exception_code = alloca i32, align 4
|
||||
; CHECK: invoke void @"?overflow@@YAXXZ_may_throw"()
|
||||
; CHECK: to label %invoke.cont unwind label %catch.dispatch
|
||||
invoke void @"?overflow@@YAXXZ_may_throw"()
|
||||
to label %invoke.cont unwind label %catch.dispatch
|
||||
|
||||
invoke.cont: ; preds = %entry
|
||||
; CHECK: invoke.cont:
|
||||
; CHECK-NEXT: unreachable
|
||||
br label %invoke.cont1
|
||||
|
||||
catch.dispatch: ; preds = %invoke.cont, %entry
|
||||
%0 = catchswitch within none [label %__except] unwind to caller
|
||||
|
||||
__except: ; preds = %catch.dispatch
|
||||
%1 = catchpad within %0 [i8* null]
|
||||
catchret from %1 to label %__except2
|
||||
|
||||
__except2: ; preds = %__except
|
||||
%2 = call i32 @llvm.eh.exceptioncode(token %1)
|
||||
store i32 1, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
invoke.cont1: ; preds = %invoke.cont
|
||||
store i32 0, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
__try.cont: ; No predecessors!
|
||||
store i32 2, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
return: ; preds = %__try.cont, %__except2, %invoke.cont1
|
||||
%3 = load i32, i32* %retval, align 4
|
||||
ret i32 %3
|
||||
}
|
||||
|
||||
declare dso_local i32 @__gcc_personality_v0(...)
|
||||
|
||||
declare dso_local i32 @printf(i8* %_Format, ...)
|
||||
|
||||
declare i32 @llvm.eh.exceptioncode(token)
|
|
@ -180,13 +180,12 @@ define void @call_might_sync() nounwind uwtable noinline {
|
|||
ret void
|
||||
}
|
||||
|
||||
; TEST 11 - negative, should not deduce nosync
|
||||
; volatile operation in same scc. Call volatile_load defined in TEST 8.
|
||||
; TEST 11 - positive, should deduce nosync
|
||||
; volatile operation in same scc but dead. Call volatile_load defined in TEST 8.
|
||||
|
||||
; FNATTR: Function Attrs: nofree noinline nounwind uwtable
|
||||
; FNATTR-NEXT: define i32 @scc1(i32* %0)
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: nosync
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline noreturn nosync nounwind uwtable
|
||||
; ATTRIBUTOR-NEXT: define i32 @scc1(i32* %0)
|
||||
define i32 @scc1(i32* %0) noinline nounwind uwtable {
|
||||
tail call void @scc2(i32* %0);
|
||||
|
@ -196,8 +195,7 @@ define i32 @scc1(i32* %0) noinline nounwind uwtable {
|
|||
|
||||
; FNATTR: Function Attrs: nofree noinline nounwind uwtable
|
||||
; FNATTR-NEXT: define void @scc2(i32* %0)
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: nosync
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline noreturn nosync nounwind uwtable
|
||||
; ATTRIBUTOR-NEXT: define void @scc2(i32* %0)
|
||||
define void @scc2(i32* %0) noinline nounwind uwtable {
|
||||
tail call i32 @scc1(i32* %0);
|
||||
|
|
|
@ -13,7 +13,7 @@ define i32 @foo1() {
|
|||
; TEST 2
|
||||
; CHECK: Function Attrs: nounwind readnone
|
||||
; CHECK-NEXT: define i32 @scc1_foo()
|
||||
; ATTRIBUTOR: Function Attrs: nofree nosync nounwind
|
||||
; ATTRIBUTOR: Function Attrs: nofree noreturn nosync nounwind
|
||||
; ATTRIBUTOR-NEXT: define i32 @scc1_foo()
|
||||
define i32 @scc1_foo() {
|
||||
%1 = call i32 @scc1_bar()
|
||||
|
@ -24,7 +24,7 @@ define i32 @scc1_foo() {
|
|||
; TEST 3
|
||||
; CHECK: Function Attrs: nounwind readnone
|
||||
; CHECK-NEXT: define i32 @scc1_bar()
|
||||
; ATTRIBUTOR: Function Attrs: nofree nosync nounwind
|
||||
; ATTRIBUTOR: Function Attrs: nofree noreturn nosync nounwind
|
||||
; ATTRIBUTOR-NEXT: define i32 @scc1_bar()
|
||||
define i32 @scc1_bar() {
|
||||
%1 = call i32 @scc1_foo()
|
||||
|
|
|
@ -125,24 +125,28 @@ define i32 @fact_loop(i32 %0) local_unnamed_addr #0 {
|
|||
|
||||
; FNATTR: Function Attrs: noinline nounwind readnone uwtable
|
||||
; FNATTR-NOT: willreturn
|
||||
; FNATTR-NEXT: define void @mutual_recursion1()
|
||||
; FNATTR-NEXT: define void @mutual_recursion1(i1 %c)
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: willreturn
|
||||
; ATTRIBUTOR-NEXT: define void @mutual_recursion1()
|
||||
define void @mutual_recursion1() #0 {
|
||||
call void @mutual_recursion2()
|
||||
; ATTRIBUTOR-NEXT: define void @mutual_recursion1(i1 %c)
|
||||
define void @mutual_recursion1(i1 %c) #0 {
|
||||
br i1 %c, label %rec, label %end
|
||||
rec:
|
||||
call void @mutual_recursion2(i1 %c)
|
||||
br label %end
|
||||
end:
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
; FNATTR: Function Attrs: noinline nounwind readnone uwtable
|
||||
; FNATTR-NOT: willreturn
|
||||
; FNATTR-NEXT: define void @mutual_recursion2()
|
||||
; FNATTR-NEXT: define void @mutual_recursion2(i1 %c)
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: willreturn
|
||||
; ATTRIBUTOR-NEXT: define void @mutual_recursion2()
|
||||
define void @mutual_recursion2() #0 {
|
||||
call void @mutual_recursion1()
|
||||
; ATTRIBUTOR-NEXT: define void @mutual_recursion2(i1 %c)
|
||||
define void @mutual_recursion2(i1 %c) #0 {
|
||||
call void @mutual_recursion1(i1 %c)
|
||||
ret void
|
||||
}
|
||||
|
||||
|
@ -158,7 +162,7 @@ declare void @exit(i32 %0) local_unnamed_addr noreturn
|
|||
; FNATTR: Function Attrs: noinline nounwind uwtable
|
||||
; FNATTR-NOT: willreturn
|
||||
; FNATTR-NEXT: define void @only_exit()
|
||||
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
|
||||
; ATTRIBUTOR: Function Attrs: noinline noreturn nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: willreturn
|
||||
; ATTRIBUTOR-NEXT: define void @only_exit() local_unnamed_addr
|
||||
define void @only_exit() local_unnamed_addr #0 {
|
||||
|
@ -283,7 +287,7 @@ define void @f2() #0 {
|
|||
; FNATTR: Function Attrs: noinline nounwind uwtable
|
||||
; FNATTR-NOT: willreturn
|
||||
; FNATTR-NEXT: define void @call_will_return_but_has_loop()
|
||||
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
|
||||
; ATTRIBUTOR: Function Attrs: noinline noreturn nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: willreturn
|
||||
; ATTRIBUTOR-NEXT: define void @call_will_return_but_has_loop()
|
||||
define void @call_will_return_but_has_loop() #0 {
|
||||
|
@ -499,7 +503,7 @@ unreachable_label:
|
|||
; FNATTR: Function Attrs: noinline nounwind uwtable
|
||||
; FNATTR-NOT: willreturn
|
||||
; FNATTR-NEXT: define void @unreachable_exit_negative1()
|
||||
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
|
||||
; ATTRIBUTOR: Function Attrs: noinline noreturn nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: willreturn
|
||||
; ATTRIBUTOR-NEXT: define void @unreachable_exit_negative1()
|
||||
define void @unreachable_exit_negative1() #0 {
|
||||
|
@ -514,7 +518,7 @@ unreachable_label:
|
|||
; FNATTR: Function Attrs: noinline nounwind uwtable
|
||||
; FNATTR-NOT: willreturn
|
||||
; FNATTR-NEXT: define void @unreachable_exit_negative2()
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable
|
||||
; ATTRIBUTOR: Function Attrs: nofree noinline noreturn nosync nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: willreturn
|
||||
; ATTRIBUTOR-NEXT: define void @unreachable_exit_negative2()
|
||||
define void @unreachable_exit_negative2() #0 {
|
||||
|
@ -539,7 +543,7 @@ declare void @llvm.eh.sjlj.longjmp(i8*)
|
|||
; FNATTR: Function Attrs: noinline nounwind uwtable
|
||||
; FNATTR-NOT: willreturn
|
||||
; FNATTR-NEXT: define void @call_longjmp(i8* nocapture readnone %0) local_unnamed_addr #3 {
|
||||
; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable
|
||||
; ATTRIBUTOR: Function Attrs: noinline noreturn nounwind uwtable
|
||||
; ATTRIBUTOR-NOT: willreturn
|
||||
; ATTRIBUTOR-NEXT: define void @call_longjmp(i8* nocapture readnone %0) local_unnamed_addr
|
||||
define void @call_longjmp(i8* nocapture readnone %0) local_unnamed_addr #0 {
|
||||
|
|
Loading…
Reference in New Issue