blob: 74bba7717842ac909d1047fbc529a50ec3e0f973 [file] [edit]
; RUN: llc -mtriple=bpfel -mcpu=v4 -filetype=asm -o - %s | FileCheck %s
; RUN: llc -mtriple=bpfel -mcpu=v4 -filetype=obj -o %t %s
; RUN: llvm-readelf -S -s %t | FileCheck --check-prefix=OBJ %s
; A function leaves with exit or with unwind_exit, and unwind_goto after the call
; tells them apart. There are no tables and no calls into an unwinder.
; A frontend asks for that with uwtable, like rustc does with -C panic=unwind.
; OBJ-NOT: .bpf_cleanup
; OBJ-NOT: .gcc_except_table
; OBJ-NOT: _Unwind_
; OBJ-NOT: abort
; OBJ-NOT: personality
declare ptr @bpf_alloc(i64, i64) nounwind
declare void @bpf_free(ptr) nounwind
declare void @btree_insert(ptr, ptr, i64)
declare i32 @_Unwind_RaiseException(ptr)
declare void @_Unwind_Resume(ptr) noreturn
declare void @abort() noreturn nounwind
declare i32 @rust_eh_personality(i32, i64, ptr, ptr)
; invoke, cleanup pad, resume. A call of nounwind function is left alone.
define void @stat_inc(ptr %map, i64 %val) uwtable personality ptr @rust_eh_personality {
entry:
%node = call ptr @bpf_alloc(i64 256, i64 0)
invoke void @btree_insert(ptr %map, ptr %node, i64 %val)
to label %done unwind label %cleanup
done:
ret void
cleanup:
%lp = landingpad { ptr, i32 } cleanup
call void @bpf_free(ptr %node)
resume { ptr, i32 } %lp
}
; CHECK-LABEL: stat_inc:
; CHECK: call bpf_alloc
; CHECK-NOT: unwind_goto
; CHECK: call btree_insert
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK-NEXT: # %bb
; CHECK-NEXT: exit
; CHECK: [[PAD]]:
; CHECK: call bpf_free
; CHECK-NEXT: r0 = 0
; CHECK-NEXT: unwind_exit
; A plain call that may unwind: nothing to clean up, pass it on.
define i64 @passes_on(ptr %map) uwtable {
entry:
call void @btree_insert(ptr %map, ptr null, i64 1)
ret i64 7
}
; CHECK-LABEL: passes_on:
; CHECK: call btree_insert
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK-NEXT: # %bb
; CHECK-NEXT: r0 = 7
; CHECK-NEXT: exit
; CHECK: [[PAD]]:
; CHECK-NEXT: r0 = 0
; CHECK-NEXT: unwind_exit
; catch_unwind: the pad does not resume.
define i64 @catches(ptr %map) uwtable personality ptr @rust_eh_personality {
entry:
invoke void @btree_insert(ptr %map, ptr null, i64 1)
to label %ok unwind label %caught
ok:
ret i64 0
caught:
%lp = landingpad { ptr, i32 } catch ptr null
ret i64 1
}
; CHECK-LABEL: catches:
; CHECK: call btree_insert
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK-NEXT: # %bb
; CHECK-NEXT: r0 = 0
; CHECK-NEXT: exit
; CHECK: [[PAD]]:
; CHECK-NEXT: r0 = 1
; CHECK-NEXT: exit
; A pad expects the exception object in r0. There is none.
define void @looks_at_exn(ptr %map) uwtable personality ptr @rust_eh_personality {
entry:
invoke void @btree_insert(ptr %map, ptr null, i64 1)
to label %ok unwind label %caught
ok:
ret void
caught:
%lp = landingpad { ptr, i32 } catch ptr null
%exn = extractvalue { ptr, i32 } %lp, 0
call void @bpf_free(ptr %exn)
ret void
}
; CHECK-LABEL: looks_at_exn:
; CHECK: call btree_insert
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK: [[PAD]]:
; CHECK-NEXT: r0 = 0
; CHECK-NEXT: r1 = r0
; CHECK-NEXT: call bpf_free
; The same after inference: it is nounwind, so there is no unwind_goto after the
; calls of it, but it still needs one after the call of its callee.
define i64 @catches_nounwind(ptr %map) nounwind uwtable personality ptr @rust_eh_personality {
entry:
invoke void @btree_insert(ptr %map, ptr null, i64 1)
to label %ok unwind label %caught
ok:
ret i64 0
caught:
%lp = landingpad { ptr, i32 } catch ptr null
ret i64 1
}
; CHECK-LABEL: catches_nounwind:
; CHECK: call btree_insert
; CHECK-NEXT: unwind_goto
; CHECK-NOT: unwind_exit
; CHECK: .Lfunc_end
; An empty filter lets nothing through: one more way to say catch-all. That is
; how rustc guards a function that must not unwind, e.g. an extern "C" one.
define i64 @empty_filter(ptr %map) nounwind uwtable personality ptr @rust_eh_personality {
entry:
invoke void @btree_insert(ptr %map, ptr null, i64 1)
to label %ok unwind label %terminate
ok:
ret i64 0
terminate:
%lp = landingpad { ptr, i32 } filter [0 x ptr] zeroinitializer
ret i64 2
}
; CHECK-LABEL: empty_filter:
; CHECK: call btree_insert
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK: [[PAD]]:
; CHECK-NEXT: r0 = 2
; The start of an unwind, as at the end of a panic handler. It is not different
; from carrying one on. By its prototype _Unwind_RaiseException() comes back
; when it fails, so a runtime has something after it. Here it cannot fail: that
; code is dropped and the function ends with unwind_exit.
define void @fail() noreturn uwtable {
entry:
%err = call i32 @_Unwind_RaiseException(ptr null)
call void @abort()
unreachable
}
; CHECK-LABEL: fail:
; CHECK-NOT: call
; CHECK: r0 = 0
; CHECK-NEXT: unwind_exit
; CHECK-NOT: call
; CHECK: .Lfunc_end
; The same in a frame that owns something: its own pad comes first.
define void @fail_owner(ptr %node) uwtable personality ptr @rust_eh_personality {
entry:
%err = invoke i32 @_Unwind_RaiseException(ptr null)
to label %failed unwind label %cleanup
failed:
call void @abort()
unreachable
cleanup:
%lp = landingpad { ptr, i32 } cleanup
call void @bpf_free(ptr %node)
resume { ptr, i32 } %lp
}
; CHECK-LABEL: fail_owner:
; CHECK-NOT: _Unwind_
; CHECK-NOT: exit
; CHECK: call bpf_free
; CHECK-NEXT: r0 = 0
; CHECK-NEXT: unwind_exit
; CHECK-NOT: call
; CHECK: .Lfunc_end
; What is behind it can be a loop, e.g. 'loop {}' in Rust. The verifier rejects
; unreachable code, so it has to go as well.
define void @fail_then_spins() noreturn uwtable {
entry:
%err = call i32 @_Unwind_RaiseException(ptr null)
br label %spin
spin:
br label %spin
}
; CHECK-LABEL: fail_then_spins:
; CHECK: r0 = 0
; CHECK-NEXT: unwind_exit
; CHECK-NOT: goto
; CHECK: .Lfunc_end
; 'resume' spelled as the call that other targets lower it to.
define void @resume_spelled_out(ptr %exn) uwtable {
entry:
call void @_Unwind_Resume(ptr %exn)
unreachable
}
; CHECK-LABEL: resume_spelled_out:
; CHECK-NOT: call
; CHECK: r0 = 0
; CHECK-NEXT: unwind_exit
; A callee that does not return: its unwind_exit needs unwind_goto all the same.
; What follows is not reached, but the block cannot end there.
define i32 @check(i32 %i) uwtable {
entry:
%c = icmp ugt i32 %i, 7
br i1 %c, label %bad, label %good
bad:
call void @fail()
unreachable
good:
ret i32 %i
}
; CHECK-LABEL: check:
; CHECK: call fail
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK-NEXT: goto [[PAD]]
; CHECK: [[PAD]]:
; CHECK-NEXT: r0 = 0
; CHECK-NEXT: unwind_exit
; An indirect call is followed by unwind_goto whatever the callee is. A nounwind
; callee does not have to know.
define internal i32 @plain(i32 %x) nounwind uwtable {
%r = mul i32 %x, 3
ret i32 %r
}
@ops = constant [1 x ptr] [ptr @plain]
define i32 @indirect(i64 %i, i32 %x) uwtable {
entry:
%p = getelementptr [1 x ptr], ptr @ops, i64 0, i64 %i
%f = load volatile ptr, ptr %p
%r = call i32 %f(i32 %x)
ret i32 %r
}
; CHECK-LABEL: plain:
; CHECK: w0 = w1
; CHECK-NEXT: w0 *= 3
; CHECK-NEXT: exit
; CHECK-LABEL: indirect:
; CHECK: callx
; CHECK-NEXT: unwind_goto
; The same from a caller that is nounwind itself, because it catches everything.
define i32 @indirect_catches(i64 %i, i32 %x) nounwind uwtable personality ptr @rust_eh_personality {
entry:
%p = getelementptr [1 x ptr], ptr @ops, i64 0, i64 %i
%f = load volatile ptr, ptr %p
%r = invoke i32 %f(i32 %x)
to label %ok unwind label %caught
ok:
ret i32 %r
caught:
%lp = landingpad { ptr, i32 } catch ptr null
ret i32 1
}
; CHECK-LABEL: indirect_catches:
; CHECK: callx
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK-NEXT: # %bb
; CHECK-NEXT: exit
; CHECK: [[PAD]]:
; CHECK-NEXT: w0 = 1
; CHECK-NEXT: exit
; unwind_exit returns what exit returns. More than 8 bytes are in r0 and r2, and
; the kernel wants both to be set.
define i128 @returns_pair(ptr %map) uwtable {
entry:
call void @btree_insert(ptr %map, ptr null, i64 1)
ret i128 7
}
; CHECK-LABEL: returns_pair:
; CHECK: call btree_insert
; CHECK-NEXT: unwind_goto [[PAD:.LBB[0-9_]+]]
; CHECK: exit
; CHECK: [[PAD]]:
; CHECK-NEXT: r0 = 0
; CHECK-NEXT: r2 = 0
; CHECK-NEXT: unwind_exit
; nounwind: nothing changes.
define i32 @leaf(i32 %x) nounwind uwtable {
%p = call ptr @bpf_alloc(i64 8, i64 0)
call void @bpf_free(ptr %p)
ret i32 %x
}
; CHECK-LABEL: leaf:
; CHECK-NOT: unwind_
; CHECK: exit
; Not being nounwind is the default in IR. Without uwtable nothing changes.
define i32 @not_asked(ptr %map) {
call void @btree_insert(ptr %map, ptr null, i64 1)
ret i32 0
}
; CHECK-LABEL: not_asked:
; CHECK: call btree_insert
; CHECK-NEXT: {{[rw]}}0 = 0
; CHECK-NEXT: exit