It is possible that an instruction to be changed to unreachable is in the same block with a terminator that can be constant-folded. In this case, as of now, the instruction will be changed to unreachable before the terminator is folded. But, then the whole BB becomes invalidated and so when we go ahead to fold the terminator, we trap. Change the order of these two. Differential Revision: https://reviews.llvm.org/D75780
325 lines
7.7 KiB
LLVM
325 lines
7.7 KiB
LLVM
; RUN: opt --attributor --attributor-disable=false -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
|
|
|
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
|
|
|
; Test cases specifically designed for the "undefined behavior" abstract function attribute.
|
|
; We want to verify that whenever undefined behavior is assumed, the code becomes unreachable.
|
|
; We use FIXME's to indicate problems and missing attributes.
|
|
|
|
; -- Load tests --
|
|
|
|
define void @load_wholly_unreachable() {
|
|
; ATTRIBUTOR-LABEL: @load_wholly_unreachable(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
;
|
|
%a = load i32, i32* null
|
|
ret void
|
|
}
|
|
|
|
define void @loads_wholly_unreachable() {
|
|
; ATTRIBUTOR-LABEL: @loads_wholly_unreachable(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
;
|
|
%a = load i32, i32* null
|
|
%b = load i32, i32* null
|
|
ret void
|
|
}
|
|
|
|
|
|
define void @load_single_bb_unreachable(i1 %cond) {
|
|
; ATTRIBUTOR-LABEL: @load_single_bb_unreachable(
|
|
; ATTRIBUTOR-NEXT: br i1 [[COND:%.*]], label [[T:%.*]], label [[E:%.*]]
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
%b = load i32, i32* null
|
|
br label %e
|
|
e:
|
|
ret void
|
|
}
|
|
|
|
define void @load_null_pointer_is_defined() "null-pointer-is-valid"="true" {
|
|
; ATTRIBUTOR-LABEL: @load_null_pointer_is_defined(
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
%a = load i32, i32* null
|
|
ret void
|
|
}
|
|
|
|
define internal i32* @ret_null() {
|
|
ret i32* null
|
|
}
|
|
|
|
; FIXME: null is propagated but the instruction
|
|
; is not changed to unreachable.
|
|
define i32 @load_null_propagated() {
|
|
; ATTRIBUTOR-LABEL: @load_null_propagated(
|
|
; ATTRIBUTOR-NEXT: [[A:%.*]] = load i32, i32* null
|
|
; ATTRIBUTOR-NEXT: ret i32 [[A]]
|
|
;
|
|
%ptr = call i32* @ret_null()
|
|
%a = load i32, i32* %ptr
|
|
ret i32 %a
|
|
}
|
|
|
|
; -- Store tests --
|
|
|
|
define void @store_wholly_unreachable() {
|
|
; ATTRIBUTOR-LABEL: @store_wholly_unreachable(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
;
|
|
store i32 5, i32* null
|
|
ret void
|
|
}
|
|
|
|
define void @store_single_bb_unreachable(i1 %cond) {
|
|
; ATTRIBUTOR-LABEL: @store_single_bb_unreachable(
|
|
; ATTRIBUTOR-NEXT: br i1 [[COND:%.*]], label [[T:%.*]], label [[E:%.*]]
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
store i32 5, i32* null
|
|
br label %e
|
|
e:
|
|
ret void
|
|
}
|
|
|
|
define void @store_null_pointer_is_defined() "null-pointer-is-valid"="true" {
|
|
; ATTRIBUTOR-LABEL: @store_null_pointer_is_defined(
|
|
; ATTRIBUTOR-NEXT: store i32 5, i32* null
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
store i32 5, i32* null
|
|
ret void
|
|
}
|
|
|
|
; -- AtomicRMW tests --
|
|
|
|
define void @atomicrmw_wholly_unreachable() {
|
|
; ATTRIBUTOR-LABEL: @atomicrmw_wholly_unreachable(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
;
|
|
%a = atomicrmw add i32* null, i32 1 acquire
|
|
ret void
|
|
}
|
|
|
|
define void @atomicrmw_single_bb_unreachable(i1 %cond) {
|
|
; ATTRIBUTOR-LABEL: @atomicrmw_single_bb_unreachable(
|
|
; ATTRIBUTOR-NEXT: br i1 [[COND:%.*]], label [[T:%.*]], label [[E:%.*]]
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
%a = atomicrmw add i32* null, i32 1 acquire
|
|
br label %e
|
|
e:
|
|
ret void
|
|
}
|
|
|
|
define void @atomicrmw_null_pointer_is_defined() "null-pointer-is-valid"="true" {
|
|
; ATTRIBUTOR-LABEL: @atomicrmw_null_pointer_is_defined(
|
|
; ATTRIBUTOR-NEXT: [[A:%.*]] = atomicrmw add i32* null, i32 1 acquire
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
%a = atomicrmw add i32* null, i32 1 acquire
|
|
ret void
|
|
}
|
|
|
|
; -- AtomicCmpXchg tests --
|
|
|
|
define void @atomiccmpxchg_wholly_unreachable() {
|
|
; ATTRIBUTOR-LABEL: @atomiccmpxchg_wholly_unreachable(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
;
|
|
%a = cmpxchg i32* null, i32 2, i32 3 acq_rel monotonic
|
|
ret void
|
|
}
|
|
|
|
define void @atomiccmpxchg_single_bb_unreachable(i1 %cond) {
|
|
; ATTRIBUTOR-LABEL: @atomiccmpxchg_single_bb_unreachable(
|
|
; ATTRIBUTOR-NEXT: br i1 [[COND:%.*]], label [[T:%.*]], label [[E:%.*]]
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
%a = cmpxchg i32* null, i32 2, i32 3 acq_rel monotonic
|
|
br label %e
|
|
e:
|
|
ret void
|
|
}
|
|
|
|
define void @atomiccmpxchg_null_pointer_is_defined() "null-pointer-is-valid"="true" {
|
|
; ATTRIBUTOR-LABEL: @atomiccmpxchg_null_pointer_is_defined(
|
|
; ATTRIBUTOR-NEXT: [[A:%.*]] = cmpxchg i32* null, i32 2, i32 3 acq_rel monotonic
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
%a = cmpxchg i32* null, i32 2, i32 3 acq_rel monotonic
|
|
ret void
|
|
}
|
|
|
|
; Note: The unreachable on %t and %e is _not_ from AAUndefinedBehavior
|
|
|
|
define i32 @cond_br_on_undef() {
|
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
;
|
|
|
|
br i1 undef, label %t, label %e
|
|
t:
|
|
ret i32 1
|
|
e:
|
|
ret i32 2
|
|
}
|
|
|
|
; More complicated branching
|
|
define void @cond_br_on_undef2(i1 %cond) {
|
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef2(
|
|
; ATTRIBUTOR-NEXT: br i1 [[COND:%.*]], label [[T1:%.*]], label [[E1:%.*]]
|
|
; ATTRIBUTOR: t1:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: t2:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e2:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e1:
|
|
; ATTRIBUTOR-NEXT: ret void
|
|
;
|
|
|
|
; Valid branch - verify that this is not converted
|
|
; to unreachable.
|
|
br i1 %cond, label %t1, label %e1
|
|
t1:
|
|
br i1 undef, label %t2, label %e2
|
|
t2:
|
|
ret void
|
|
e2:
|
|
ret void
|
|
e1:
|
|
ret void
|
|
}
|
|
|
|
define i1 @ret_undef() {
|
|
ret i1 undef
|
|
}
|
|
|
|
define void @cond_br_on_undef_interproc() {
|
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef_interproc(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
|
|
%cond = call i1 @ret_undef()
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
ret void
|
|
e:
|
|
ret void
|
|
}
|
|
|
|
define i1 @ret_undef2() {
|
|
br i1 true, label %t, label %e
|
|
t:
|
|
ret i1 undef
|
|
e:
|
|
ret i1 undef
|
|
}
|
|
|
|
; More complicated interproc deduction of undef
|
|
define void @cond_br_on_undef_interproc2() {
|
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef_interproc2(
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
%cond = call i1 @ret_undef2()
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
ret void
|
|
e:
|
|
ret void
|
|
}
|
|
|
|
; Branch on undef that depends on propagation of
|
|
; undef of a previous instruction.
|
|
define i32 @cond_br_on_undef3() {
|
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef3(
|
|
; ATTRIBUTOR-NEXT: br label %t
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: ret i32 1
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: unreachable
|
|
|
|
%cond = icmp ne i32 1, undef
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
ret i32 1
|
|
e:
|
|
ret i32 2
|
|
}
|
|
|
|
; Branch on undef because of uninitialized value.
|
|
; FIXME: Currently it doesn't propagate the undef.
|
|
define i32 @cond_br_on_undef_uninit() {
|
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef_uninit(
|
|
; ATTRIBUTOR-NEXT: %alloc = alloca i1
|
|
; ATTRIBUTOR-NEXT: %cond = load i1, i1* %alloc
|
|
; ATTRIBUTOR-NEXT: br i1 %cond, label %t, label %e
|
|
; ATTRIBUTOR: t:
|
|
; ATTRIBUTOR-NEXT: ret i32 1
|
|
; ATTRIBUTOR: e:
|
|
; ATTRIBUTOR-NEXT: ret i32 2
|
|
|
|
%alloc = alloca i1
|
|
%cond = load i1, i1* %alloc
|
|
br i1 %cond, label %t, label %e
|
|
t:
|
|
ret i32 1
|
|
e:
|
|
ret i32 2
|
|
}
|
|
|
|
; Note that the `load` has UB (so it will be changed to unreachable)
|
|
; and the branch is a terminator that can be constant-folded.
|
|
; We want to test that doing both won't cause a segfault.
|
|
define internal i32 @callee(i1 %C, i32* %A) {
|
|
; ATTRIBUTOR-NOT: @callee(
|
|
;
|
|
entry:
|
|
%A.0 = load i32, i32* null
|
|
br i1 %C, label %T, label %F
|
|
|
|
T:
|
|
ret i32 %A.0
|
|
|
|
F:
|
|
ret i32 1
|
|
}
|
|
|
|
define i32 @foo() {
|
|
; ATTRIBUTOR-LABEL: @foo()
|
|
; ATTRIBUTOR-NEXT: ret i32 1
|
|
%X = call i32 @callee(i1 false, i32* null)
|
|
ret i32 %X
|
|
}
|