Files
scriptc/packages/runtime/test/test_unwind_frames.ll
T
Chris Tate d5ac31f812 Raise through cached unwind plans on x86-64 Linux
scr_unwind_raise walks the stack with plans cached per return address.
The first raise through a return address finds the function's FDE
through the .eh_frame_hdr of the object that contains it, runs the CFI
up to the call, and finds the call site's landing pad in the LSDA; the
plan records the frame's CFA rule, where its callee-saved registers are
saved, and the landing pad. Later raises look the plans up, restore the
registers, and jump to the first landing pad. Plans are inserted under
a lock and read without one.

Landing pads continue the unwind with scr_unwind_resume instead of
`resume`, so the walk goes on from their frame. A frame whose CFI,
pointer encodings or personality the plans do not express, or a return
address without an FDE, leaves the unwind to libgcc's forced unwind
from the frame the walk started at, as before. Other Linux targets keep
libgcc's unwinder; their scr_unwind_resume calls _Unwind_Resume.

bench:runtime gains layered-errors, a workload whose errors are thrown
below small functions that hold references and caught per entry in a
loop, and the corpus gains unwind-landing-pad-chains, which pins landing
pads that continue the unwind, including in frames LLVM inlines into a
caller whose handler takes the exception.
2026-10-28 00:22:04 -05:00

132 lines
4.1 KiB
LLVM

; Frames for test_unwind.c, shaped like emitted code on the unwinding
; exception ABI. Every frame takes a chain: a null-terminated array of frame
; functions, of which it calls the first with the rest. Each clobbers the
; callee-saved registers, so its prologue saves them and its CFI says where.
target triple = "x86_64-unknown-linux-gnu"
declare void @scr_unwind_raise() cold
declare void @scr_unwind_resume(ptr) cold
declare i32 @__gcc_personality_v0(...)
declare void @test_cleanup(ptr)
declare void @test_touch(ptr)
; Clobbers every callee-saved register the frame pointer leaves free.
define private void @clobber() alwaysinline {
call void asm sideeffect "movq $$0x6161616161616161, %rbx\0Amovq $$0x6262626262626262, %r12\0Amovq $$0x6363636363636363, %r13\0Amovq $$0x6464646464646464, %r14\0Amovq $$0x6565656565656565, %r15", "~{rbx},~{r12},~{r13},~{r14},~{r15}"()
ret void
}
; A local that escapes, so sanitized builds give the frame redzones.
define private void @local() alwaysinline sanitize_address {
%buf = alloca [48 x i8], align 16
call void @test_touch(ptr %buf)
ret void
}
define void @ir_raise(ptr %chain) sanitize_address {
call void @local()
call void @clobber()
call void @scr_unwind_raise()
unreachable
}
define void @ir_plain(ptr %chain) sanitize_address {
call void @local()
%fn = load ptr, ptr %chain
%rest = getelementptr ptr, ptr %chain, i64 1
call void @clobber()
call void %fn(ptr %rest)
ret void
}
define void @ir_plain_fp(ptr %chain) sanitize_address "frame-pointer"="all" {
call void @local()
%fn = load ptr, ptr %chain
%rest = getelementptr ptr, ptr %chain, i64 1
call void @clobber()
call void %fn(ptr %rest)
ret void
}
; A frame whose stack pointer moves by a runtime amount: its CFA is rbp's.
define void @ir_alloca(ptr %chain) sanitize_address {
%bits = ptrtoint ptr %chain to i64
%n = and i64 %bits, 112
%size = add i64 %n, 16
%dyn = alloca i8, i64 %size, align 16
call void @test_touch(ptr %dyn)
%fn = load ptr, ptr %chain
%rest = getelementptr ptr, ptr %chain, i64 1
call void @clobber()
call void %fn(ptr %rest)
ret void
}
; A frame with a landing pad: it records its chain position and continues
; the unwind. The chain position is live across the call.
define void @ir_pad(ptr %chain) sanitize_address personality ptr @__gcc_personality_v0 {
entry:
call void @local()
%fn = load ptr, ptr %chain
%rest = getelementptr ptr, ptr %chain, i64 1
call void @clobber()
invoke void %fn(ptr %rest) to label %ok unwind label %lp
ok:
ret void
lp:
%e = landingpad { ptr, i32 } cleanup
call void @test_cleanup(ptr %chain)
%token = extractvalue { ptr, i32 } %e, 0
call void @scr_unwind_resume(ptr %token)
unreachable
}
define void @ir_pad_fp(ptr %chain) sanitize_address "frame-pointer"="all" personality ptr @__gcc_personality_v0 {
entry:
call void @local()
%fn = load ptr, ptr %chain
%rest = getelementptr ptr, ptr %chain, i64 1
call void @clobber()
invoke void %fn(ptr %rest) to label %ok unwind label %lp
ok:
ret void
lp:
%e = landingpad { ptr, i32 } cleanup
call void @test_cleanup(ptr %chain)
%token = extractvalue { ptr, i32 } %e, 0
call void @scr_unwind_resume(ptr %token)
unreachable
}
; Takes the exception, as a pending-flag frame does: returns a checksum of
; six values live across the call, which hold the callee-saved registers,
; and whether the call raised in bit 63.
define i64 @ir_catch(ptr %chain, i64 %a, i64 %b, i64 %c, i64 %d, i64 %e, i64 %f) sanitize_address personality ptr @__gcc_personality_v0 {
entry:
call void @local()
%x1 = mul i64 %a, 3
%x2 = mul i64 %b, 5
%x3 = mul i64 %c, 7
%x4 = mul i64 %d, 11
%x5 = mul i64 %e, 13
%x6 = mul i64 %f, 17
%fn = load ptr, ptr %chain
%rest = getelementptr ptr, ptr %chain, i64 1
invoke void %fn(ptr %rest) to label %ok unwind label %lp
ok:
br label %done
lp:
%l = landingpad { ptr, i32 } cleanup
br label %done
done:
%raised = phi i64 [ 0, %ok ], [ -9223372036854775808, %lp ]
%s1 = xor i64 %x1, %x2
%s2 = add i64 %s1, %x3
%s3 = xor i64 %s2, %x4
%s4 = add i64 %s3, %x5
%s5 = xor i64 %s4, %x6
%s6 = and i64 %s5, 9223372036854775807
%r = or i64 %s6, %raised
ret i64 %r
}