mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
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.
132 lines
4.1 KiB
LLVM
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
|
|
}
|