224 lines
7.4 KiB
LLVM
224 lines
7.4 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
|
|
; RUN: opt < %s -inline-threshold=0 -always-inline -S | FileCheck %s
|
|
; RUN: opt < %s -passes=always-inline -S | FileCheck %s
|
|
|
|
declare i8* @foo(i8*) nounwind willreturn
|
|
|
|
define i8* @callee(i8 *%p) alwaysinline {
|
|
; CHECK-LABEL: @callee(
|
|
; CHECK-NEXT: [[R:%.*]] = call i8* @foo(i8* noalias [[P:%.*]])
|
|
; CHECK-NEXT: ret i8* [[R]]
|
|
;
|
|
%r = call i8* @foo(i8* noalias %p)
|
|
ret i8* %r
|
|
}
|
|
|
|
define i8* @caller(i8* %ptr, i64 %x) {
|
|
; CHECK-LABEL: @caller(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: [[R_I:%.*]] = call nonnull i8* @foo(i8* noalias [[GEP]])
|
|
; CHECK-NEXT: ret i8* [[R_I]]
|
|
;
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%p = call nonnull i8* @callee(i8* %gep)
|
|
ret i8* %p
|
|
}
|
|
|
|
declare void @llvm.experimental.guard(i1,...)
|
|
; Cannot add nonnull attribute to foo
|
|
; because the guard is a throwing call
|
|
define internal i8* @callee_with_throwable(i8* %p) alwaysinline {
|
|
%r = call i8* @foo(i8* %p)
|
|
%cond = icmp ne i8* %r, null
|
|
call void (i1, ...) @llvm.experimental.guard(i1 %cond) [ "deopt"() ]
|
|
ret i8* %r
|
|
}
|
|
|
|
declare i8* @bar(i8*) readonly nounwind
|
|
; Here also we cannot add nonnull attribute to the call bar.
|
|
define internal i8* @callee_with_explicit_control_flow(i8* %p) alwaysinline {
|
|
%r = call i8* @bar(i8* %p)
|
|
%cond = icmp ne i8* %r, null
|
|
br i1 %cond, label %ret, label %orig
|
|
|
|
ret:
|
|
ret i8* %r
|
|
|
|
orig:
|
|
ret i8* %p
|
|
}
|
|
|
|
define i8* @caller2(i8* %ptr, i64 %x, i1 %cond) {
|
|
; CHECK-LABEL: @caller2(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: [[R_I:%.*]] = call i8* @foo(i8* [[GEP]])
|
|
; CHECK-NEXT: [[COND_I:%.*]] = icmp ne i8* [[R_I]], null
|
|
; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[COND_I]]) [ "deopt"() ]
|
|
; CHECK-NEXT: [[R_I1:%.*]] = call i8* @bar(i8* [[GEP]])
|
|
; CHECK-NEXT: [[COND_I2:%.*]] = icmp ne i8* [[R_I1]], null
|
|
; CHECK-NEXT: br i1 [[COND_I2]], label [[RET_I:%.*]], label [[ORIG_I:%.*]]
|
|
; CHECK: ret.i:
|
|
; CHECK-NEXT: br label [[CALLEE_WITH_EXPLICIT_CONTROL_FLOW_EXIT:%.*]]
|
|
; CHECK: orig.i:
|
|
; CHECK-NEXT: br label [[CALLEE_WITH_EXPLICIT_CONTROL_FLOW_EXIT]]
|
|
; CHECK: callee_with_explicit_control_flow.exit:
|
|
; CHECK-NEXT: [[Q3:%.*]] = phi i8* [ [[R_I1]], [[RET_I]] ], [ [[GEP]], [[ORIG_I]] ]
|
|
; CHECK-NEXT: br i1 [[COND:%.*]], label [[PRET:%.*]], label [[QRET:%.*]]
|
|
; CHECK: pret:
|
|
; CHECK-NEXT: ret i8* [[R_I]]
|
|
; CHECK: qret:
|
|
; CHECK-NEXT: ret i8* [[Q3]]
|
|
;
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%p = call nonnull i8* @callee_with_throwable(i8* %gep)
|
|
%q = call nonnull i8* @callee_with_explicit_control_flow(i8* %gep)
|
|
br i1 %cond, label %pret, label %qret
|
|
|
|
pret:
|
|
ret i8* %p
|
|
|
|
qret:
|
|
ret i8* %q
|
|
}
|
|
|
|
define internal i8* @callee3(i8 *%p) alwaysinline {
|
|
%r = call noalias i8* @foo(i8* %p)
|
|
ret i8* %r
|
|
}
|
|
|
|
; add the deref attribute to the existing attributes on foo.
|
|
define i8* @caller3(i8* %ptr, i64 %x) {
|
|
; CHECK-LABEL: @caller3(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: [[R_I:%.*]] = call noalias dereferenceable_or_null(12) i8* @foo(i8* [[GEP]])
|
|
; CHECK-NEXT: ret i8* [[R_I]]
|
|
;
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%p = call dereferenceable_or_null(12) i8* @callee3(i8* %gep)
|
|
ret i8* %p
|
|
}
|
|
|
|
declare i8* @inf_loop_call(i8*) nounwind
|
|
; We cannot propagate attributes to foo because we do not know whether inf_loop_call
|
|
; will return execution.
|
|
define internal i8* @callee_with_sideeffect_callsite(i8* %p) alwaysinline {
|
|
%r = call i8* @foo(i8* %p)
|
|
%v = call i8* @inf_loop_call(i8* %p)
|
|
ret i8* %r
|
|
}
|
|
|
|
; do not add deref attribute to foo
|
|
define i8* @test4(i8* %ptr, i64 %x) {
|
|
; CHECK-LABEL: @test4(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: [[R_I:%.*]] = call i8* @foo(i8* [[GEP]])
|
|
; CHECK-NEXT: [[V_I:%.*]] = call i8* @inf_loop_call(i8* [[GEP]])
|
|
; CHECK-NEXT: ret i8* [[R_I]]
|
|
;
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%p = call dereferenceable_or_null(12) i8* @callee_with_sideeffect_callsite(i8* %gep)
|
|
ret i8* %p
|
|
}
|
|
|
|
declare i8* @baz(i8*) nounwind willreturn
|
|
define internal i8* @callee5(i8* %p) alwaysinline {
|
|
%r = call i8* @foo(i8* %p)
|
|
%v = call i8* @baz(i8* %p)
|
|
ret i8* %r
|
|
}
|
|
|
|
; add the deref attribute to foo.
|
|
define i8* @test5(i8* %ptr, i64 %x) {
|
|
; CHECK-LABEL: @test5(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: [[R_I:%.*]] = call dereferenceable_or_null(12) i8* @foo(i8* [[GEP]])
|
|
; CHECK-NEXT: [[V_I:%.*]] = call i8* @baz(i8* [[GEP]])
|
|
; CHECK-NEXT: ret i8* [[R_I]]
|
|
;
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%s = call dereferenceable_or_null(12) i8* @callee5(i8* %gep)
|
|
ret i8* %s
|
|
}
|
|
|
|
; deref attributes have different values on the callee and the call feeding into
|
|
; the return.
|
|
; AttrBuilder chooses the already existing value and does not overwrite it.
|
|
define internal i8* @callee6(i8* %p) alwaysinline {
|
|
%r = call dereferenceable_or_null(16) i8* @foo(i8* %p)
|
|
%v = call i8* @baz(i8* %p)
|
|
ret i8* %r
|
|
}
|
|
|
|
|
|
define i8* @test6(i8* %ptr, i64 %x) {
|
|
; CHECK-LABEL: @test6(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: [[R_I:%.*]] = call dereferenceable_or_null(16) i8* @foo(i8* [[GEP]])
|
|
; CHECK-NEXT: [[V_I:%.*]] = call i8* @baz(i8* [[GEP]])
|
|
; CHECK-NEXT: ret i8* [[R_I]]
|
|
;
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%s = call dereferenceable_or_null(12) i8* @callee6(i8* %gep)
|
|
ret i8* %s
|
|
}
|
|
|
|
; We add the attributes from the callee to both the calls below.
|
|
define internal i8* @callee7(i8 *%ptr, i1 %cond) alwaysinline {
|
|
br i1 %cond, label %pass, label %fail
|
|
|
|
pass:
|
|
%r = call i8* @foo(i8* noalias %ptr)
|
|
ret i8* %r
|
|
|
|
fail:
|
|
%s = call i8* @baz(i8* %ptr)
|
|
ret i8* %s
|
|
}
|
|
|
|
define void @test7(i8* %ptr, i64 %x, i1 %cond) {
|
|
; CHECK-LABEL: @test7(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: br i1 [[COND:%.*]], label [[PASS_I:%.*]], label [[FAIL_I:%.*]]
|
|
; CHECK: pass.i:
|
|
; CHECK-NEXT: [[R_I:%.*]] = call nonnull i8* @foo(i8* noalias [[GEP]])
|
|
; CHECK-NEXT: br label [[CALLEE7_EXIT:%.*]]
|
|
; CHECK: fail.i:
|
|
; CHECK-NEXT: [[S_I:%.*]] = call nonnull i8* @baz(i8* [[GEP]])
|
|
; CHECK-NEXT: br label [[CALLEE7_EXIT]]
|
|
; CHECK: callee7.exit:
|
|
; CHECK-NEXT: [[T1:%.*]] = phi i8* [ [[R_I]], [[PASS_I]] ], [ [[S_I]], [[FAIL_I]] ]
|
|
; CHECK-NEXT: call void @snort(i8* [[T1]])
|
|
; CHECK-NEXT: ret void
|
|
;
|
|
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%t = call nonnull i8* @callee7(i8* %gep, i1 %cond)
|
|
call void @snort(i8* %t)
|
|
ret void
|
|
}
|
|
declare void @snort(i8*)
|
|
|
|
declare i32 @intrinsic(i8*) nounwind argmemonly
|
|
|
|
define internal i32 @callee8(i8* %ptr) alwaysinline {
|
|
%r = call i32 @intrinsic(i8* noalias %ptr)
|
|
ret i32 %r
|
|
}
|
|
|
|
|
|
; signext is an attribute specific to the target ABI and not the
|
|
; callee/callsite.
|
|
; We cannot propagate that attribute to another call since it can be invalid at
|
|
; that call.
|
|
define i32 @test8(i8* %ptr, i64 %x) {
|
|
; CHECK-LABEL: @test8(
|
|
; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, i8* [[PTR:%.*]], i64 [[X:%.*]]
|
|
; CHECK-NEXT: [[R_I:%.*]] = call i32 @intrinsic(i8* noalias [[GEP]])
|
|
; CHECK-NEXT: ret i32 [[R_I]]
|
|
;
|
|
|
|
%gep = getelementptr inbounds i8, i8* %ptr, i64 %x
|
|
%t = call signext i32 @callee8(i8* %gep)
|
|
ret i32 %t
|
|
}
|