322 lines
9.1 KiB
LLVM
322 lines
9.1 KiB
LLVM
|
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
|
||
|
; RUN: opt < %s -basic-aa -dse -S | FileCheck %s
|
||
|
|
||
|
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
|
||
|
|
||
|
declare noalias i8* @malloc(i64)
|
||
|
|
||
|
declare void @foo()
|
||
|
declare void @capture(i8*)
|
||
|
|
||
|
; Check that we do not remove the second store, as %m is returned.
|
||
|
define i8* @test_return_captures_1() {
|
||
|
; CHECK-LABEL: @test_return_captures_1(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
store i8 0, i8* %m
|
||
|
store i8 1, i8* %m
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
; Same as @test_return_captures_1, but across BBs.
|
||
|
define i8* @test_return_captures_2() {
|
||
|
; CHECK-LABEL: @test_return_captures_2(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
store i8 0, i8* %m
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
|
||
|
%S1 = type { i8 * }
|
||
|
|
||
|
; We cannot remove the last store to %m, because it escapes by storing it to %E.
|
||
|
define void @test_malloc_capture_1(%S1* %E) {
|
||
|
; CHECK-LABEL: @test_malloc_capture_1(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: [[F_PTR:%.*]] = getelementptr [[S1:%.*]], %S1* [[E:%.*]], i32 0, i32 0
|
||
|
; CHECK-NEXT: store i8* [[M]], i8** [[F_PTR]], align 4
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: ret void
|
||
|
;
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
%f.ptr = getelementptr %S1, %S1* %E, i32 0, i32 0
|
||
|
store i8* %m, i8** %f.ptr
|
||
|
store i8 1, i8* %m
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Check we do not eliminate either store. The first one cannot be eliminated,
|
||
|
; due to the call of @capture. The second one because %m escapes.
|
||
|
define i8* @test_malloc_capture_2() {
|
||
|
; CHECK-LABEL: @test_malloc_capture_2(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: store i8 0, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @capture(i8* [[M]])
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
store i8 0, i8* %m
|
||
|
call void @capture(i8* %m)
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
; We can remove the first store store i8 0, i8* %m because there are no throwing
|
||
|
; instructions between the 2 stores and also %m escapes after the killing store.
|
||
|
define i8* @test_malloc_capture_3() {
|
||
|
; CHECK-LABEL: @test_malloc_capture_3(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @capture(i8* [[M]])
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
store i8 0, i8* %m
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
call void @capture(i8* %m)
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
; TODO: We could remove the first store store i8 0, i8* %m because %m escapes
|
||
|
; after the killing store.
|
||
|
define i8* @test_malloc_capture_4() {
|
||
|
; CHECK-LABEL: @test_malloc_capture_4(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: store i8 0, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @may_throw_readnone()
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @capture(i8* [[M]])
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
store i8 0, i8* %m
|
||
|
call void @may_throw_readnone()
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
call void @capture(i8* %m)
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
|
||
|
; We cannot remove the first store store i8 0, i8* %m because %m escapes
|
||
|
; before the killing store and we may throw in between.
|
||
|
define i8* @test_malloc_capture_5() {
|
||
|
; CHECK-LABEL: @test_malloc_capture_5(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: call void @capture(i8* [[M]])
|
||
|
; CHECK-NEXT: store i8 0, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @may_throw_readnone()
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
call void @capture(i8* %m)
|
||
|
store i8 0, i8* %m
|
||
|
call void @may_throw_readnone()
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
|
||
|
; TODO: We could remove the first store 'store i8 0, i8* %m' even though there
|
||
|
; is a throwing instruction between them, because %m escapes after the killing
|
||
|
; store.
|
||
|
define i8* @test_malloc_capture_6() {
|
||
|
; CHECK-LABEL: @test_malloc_capture_6(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: store i8 0, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @may_throw_readnone()
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @capture(i8* [[M]])
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
store i8 0, i8* %m
|
||
|
call void @may_throw_readnone()
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
call void @capture(i8* %m)
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
; We *could* remove the first store 'store i8 0, i8* %m' even though there is a
|
||
|
; throwing instruction between them, because %m escapes after the killing store.
|
||
|
; But this would require using PointerMayBeCapturedBefore in
|
||
|
; isInvisibleToCallerBeforeRet, which we currently do not do to limit
|
||
|
; compile-time, as this appears to hardly ever lead to more stores eliminated
|
||
|
; in practice.
|
||
|
define i8* @test_malloc_capture_7() {
|
||
|
; CHECK-LABEL: @test_malloc_capture_7(
|
||
|
; CHECK-NEXT: [[M:%.*]] = call i8* @malloc(i64 24)
|
||
|
; CHECK-NEXT: store i8 0, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @may_throw()
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: store i8 1, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @capture(i8* [[M]])
|
||
|
; CHECK-NEXT: ret i8* [[M]]
|
||
|
;
|
||
|
|
||
|
%m = call i8* @malloc(i64 24)
|
||
|
store i8 0, i8* %m
|
||
|
call void @may_throw()
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
call void @capture(i8* %m)
|
||
|
ret i8* %m
|
||
|
}
|
||
|
|
||
|
; Stores to stack objects can be eliminated if they are not captured inside the function.
|
||
|
define void @test_alloca_nocapture_1() {
|
||
|
; CHECK-LABEL: @test_alloca_nocapture_1(
|
||
|
; CHECK-NEXT: call void @foo()
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: ret void
|
||
|
;
|
||
|
%m = alloca i8
|
||
|
store i8 0, i8* %m
|
||
|
call void @foo()
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Cannot remove first store i8 0, i8* %m, as the call to @capture captures the object.
|
||
|
define void @test_alloca_capture_1() {
|
||
|
; CHECK-LABEL: @test_alloca_capture_1(
|
||
|
; CHECK-NEXT: [[M:%.*]] = alloca i8, align 1
|
||
|
; CHECK-NEXT: store i8 0, i8* [[M]], align 1
|
||
|
; CHECK-NEXT: call void @capture(i8* [[M]])
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: ret void
|
||
|
;
|
||
|
%m = alloca i8
|
||
|
store i8 0, i8* %m
|
||
|
call void @capture(i8* %m)
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
store i8 1, i8* %m
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; We can remove the last store to %m, even though it escapes because the alloca
|
||
|
; becomes invalid after the function returns.
|
||
|
define void @test_alloca_capture_2(%S1* %E) {
|
||
|
; CHECK-LABEL: @test_alloca_capture_2(
|
||
|
; CHECK-NEXT: [[M:%.*]] = alloca i8, align 1
|
||
|
; CHECK-NEXT: br label [[EXIT:%.*]]
|
||
|
; CHECK: exit:
|
||
|
; CHECK-NEXT: [[F_PTR:%.*]] = getelementptr [[S1:%.*]], %S1* [[E:%.*]], i32 0, i32 0
|
||
|
; CHECK-NEXT: store i8* [[M]], i8** [[F_PTR]], align 4
|
||
|
; CHECK-NEXT: ret void
|
||
|
;
|
||
|
%m = alloca i8
|
||
|
br label %exit
|
||
|
|
||
|
exit:
|
||
|
%f.ptr = getelementptr %S1, %S1* %E, i32 0, i32 0
|
||
|
store i8* %m, i8** %f.ptr
|
||
|
store i8 1, i8* %m
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Readnone functions are not modeled in MemorySSA, but could throw.
|
||
|
; Make sure we do not eliminate the first store 'store i8 2, i8* %call'
|
||
|
define void @malloc_capture_throw_1() {
|
||
|
; CHECK-LABEL: @malloc_capture_throw_1(
|
||
|
; CHECK-NEXT: [[CALL:%.*]] = call i8* @malloc(i64 1)
|
||
|
; CHECK-NEXT: call void @may_capture(i8* [[CALL]])
|
||
|
; CHECK-NEXT: store i8 2, i8* [[CALL]], align 1
|
||
|
; CHECK-NEXT: call void @may_throw_readnone()
|
||
|
; CHECK-NEXT: store i8 3, i8* [[CALL]], align 1
|
||
|
; CHECK-NEXT: ret void
|
||
|
;
|
||
|
%call = call i8* @malloc(i64 1)
|
||
|
call void @may_capture(i8* %call)
|
||
|
store i8 2, i8* %call, align 1
|
||
|
call void @may_throw_readnone()
|
||
|
store i8 3, i8* %call, align 1
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
; Readnone functions are not modeled in MemorySSA, but could throw.
|
||
|
; Make sure we do not eliminate the first store 'store i8 2, i8* %call'
|
||
|
define void @malloc_capture_throw_2() {
|
||
|
; CHECK-LABEL: @malloc_capture_throw_2(
|
||
|
; CHECK-NEXT: [[CALL:%.*]] = call i8* @malloc(i64 1)
|
||
|
; CHECK-NEXT: call void @may_capture(i8* [[CALL]])
|
||
|
; CHECK-NEXT: store i8 2, i8* [[CALL]], align 1
|
||
|
; CHECK-NEXT: br label [[BB:%.*]]
|
||
|
; CHECK: bb:
|
||
|
; CHECK-NEXT: call void @may_throw_readnone()
|
||
|
; CHECK-NEXT: store i8 3, i8* [[CALL]], align 1
|
||
|
; CHECK-NEXT: ret void
|
||
|
;
|
||
|
%call = call i8* @malloc(i64 1)
|
||
|
call void @may_capture(i8* %call)
|
||
|
store i8 2, i8* %call, align 1
|
||
|
br label %bb
|
||
|
|
||
|
bb:
|
||
|
call void @may_throw_readnone()
|
||
|
store i8 3, i8* %call, align 1
|
||
|
ret void
|
||
|
}
|
||
|
|
||
|
|
||
|
declare void @may_capture(i8*)
|
||
|
declare void @may_throw_readnone() readnone
|
||
|
declare void @may_throw()
|