Stage 0: trap-frame parity across all 3 arches (FABRIC-3.md §XXVIII)
First stage of the preemptive context-switching plan (see ~/.claude/plans/logical-snuggling-bear.md). Pure foundation work -- every arch's ISR now saves the full register set on interrupt entry, so a trap frame is in principle sufficient to resume execution anywhere it was taken. No FORTH-visible behavior changes. amd64: added FXSAVE/FXRSTOR, closing a genuine pre-existing correctness gap (not just future-preemption prep) -- confirmed live double-precision FP code reachable from ordinary interpreter dispatch (vm_runtime.c Loop #5/#6), and the ISR previously saved zero FP/SSE state. rbp repurposed as a fixed anchor so the 16-byte-aligned FXSAVE area can be carved out of an unpredictably-aligned rsp without disturbing existing argument reads. aarch64: extended the trap frame 672->800 bytes, adding v8-v15 (AAPCS64 callee-saved, previously excluded on call-site-only reasoning that doesn't hold for an async trap). riscv64: extended the trap frame 320->512 bytes, adding s0-s11 and fs0-fs11 (the latter still correctly gated behind sstatus.FS != Off). All 3 architectures re-verified clean boot to ok> under the new frames -- amd64 through hundreds of timer ticks with FXSAVE/FXRSTOR live on every interrupt, aarch64 through 987 ticks, riscv64 clean on the now-larger FS-conditional block. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2a30212bd3
commit
15672ce17c
@@ -148,7 +148,7 @@ aarch64_install_vectors:
|
||||
/* ---------------------------------------------------------------------------
|
||||
* irq_spx_trampoline — save / call C handler / restore / ERET.
|
||||
*
|
||||
* Frame layout (672 bytes, 16-byte aligned; SP is guaranteed 16-aligned at
|
||||
* Frame layout (800 bytes, 16-byte aligned; SP is guaranteed 16-aligned at
|
||||
* any point AAPCS64 code can be asynchronously interrupted, so no extra
|
||||
* alignment handling is needed around the initial SUB):
|
||||
*
|
||||
@@ -160,11 +160,20 @@ aarch64_install_vectors:
|
||||
* 272 FPCR
|
||||
* 280 (8 bytes unused -- keeps the V-register block 16-aligned)
|
||||
* 288..415 v0-v7 (4 STP Q-pairs) -- AAPCS64 caller-saved
|
||||
* 416..671 v16-v31 (8 STP Q-pairs) -- AAPCS64 caller-saved
|
||||
* 416..543 v8-v15 (4 STP Q-pairs) -- AAPCS64 callee-saved
|
||||
* 544..799 v16-v31 (8 STP Q-pairs) -- AAPCS64 caller-saved
|
||||
*
|
||||
* v8-v15 are AAPCS64 callee-saved and are deliberately not here: the C
|
||||
* handler below is ordinary compiled code and preserves those itself if it
|
||||
* touches them, per the same ABI the save list is drawn from.
|
||||
* FABRIC-3.md SXXVIII (Stage 0, preemptive-context-switch trap-frame parity,
|
||||
* 2026-09-13): v8-v15 now saved too. They are AAPCS64 callee-saved, so
|
||||
* ordinary compiled code (the C handler below, or anything else called
|
||||
* through a normal ABI boundary) already preserves them across a *call* --
|
||||
* but an interrupt is not a call the interrupted code's own compiled
|
||||
* instructions know about. A value the interrupted function is genuinely
|
||||
* live-using in v8-v15 at the moment of interrupt has no ABI guarantee of
|
||||
* surviving an async trap at all; only a frame that saves everything makes
|
||||
* "resume execution anywhere it was taken" actually true. This trap frame
|
||||
* is not yet copied into any per-VM saved context (that's Stage 2) -- this
|
||||
* stage only makes the frame itself complete.
|
||||
* ------------------------------------------------------------------------- */
|
||||
.equ TF_X0, 0
|
||||
.equ TF_X2, 16
|
||||
@@ -190,15 +199,19 @@ aarch64_install_vectors:
|
||||
.equ TF_V2, 320
|
||||
.equ TF_V4, 352
|
||||
.equ TF_V6, 384
|
||||
.equ TF_V16, 416
|
||||
.equ TF_V18, 448
|
||||
.equ TF_V20, 480
|
||||
.equ TF_V22, 512
|
||||
.equ TF_V24, 544
|
||||
.equ TF_V26, 576
|
||||
.equ TF_V28, 608
|
||||
.equ TF_V30, 640
|
||||
.equ TF_SIZE, 672
|
||||
.equ TF_V8, 416
|
||||
.equ TF_V10, 448
|
||||
.equ TF_V12, 480
|
||||
.equ TF_V14, 512
|
||||
.equ TF_V16, 544
|
||||
.equ TF_V18, 576
|
||||
.equ TF_V20, 608
|
||||
.equ TF_V22, 640
|
||||
.equ TF_V24, 672
|
||||
.equ TF_V26, 704
|
||||
.equ TF_V28, 736
|
||||
.equ TF_V30, 768
|
||||
.equ TF_SIZE, 800
|
||||
|
||||
.balign 4
|
||||
irq_spx_trampoline:
|
||||
@@ -247,6 +260,10 @@ irq_spx_trampoline:
|
||||
stp q2, q3, [sp, #TF_V2]
|
||||
stp q4, q5, [sp, #TF_V4]
|
||||
stp q6, q7, [sp, #TF_V6]
|
||||
stp q8, q9, [sp, #TF_V8]
|
||||
stp q10, q11, [sp, #TF_V10]
|
||||
stp q12, q13, [sp, #TF_V12]
|
||||
stp q14, q15, [sp, #TF_V14]
|
||||
stp q16, q17, [sp, #TF_V16]
|
||||
stp q18, q19, [sp, #TF_V18]
|
||||
stp q20, q21, [sp, #TF_V20]
|
||||
@@ -262,6 +279,10 @@ irq_spx_trampoline:
|
||||
ldp q2, q3, [sp, #TF_V2]
|
||||
ldp q4, q5, [sp, #TF_V4]
|
||||
ldp q6, q7, [sp, #TF_V6]
|
||||
ldp q8, q9, [sp, #TF_V8]
|
||||
ldp q10, q11, [sp, #TF_V10]
|
||||
ldp q12, q13, [sp, #TF_V12]
|
||||
ldp q14, q15, [sp, #TF_V14]
|
||||
ldp q16, q17, [sp, #TF_V16]
|
||||
ldp q18, q19, [sp, #TF_V18]
|
||||
ldp q20, q21, [sp, #TF_V20]
|
||||
|
||||
@@ -75,20 +75,48 @@ isr_common_entry:
|
||||
* [rsp+152] = rflags
|
||||
*/
|
||||
|
||||
/* FABRIC-3.md SXXVIII (Stage 0, preemptive-context-switch trap-frame
|
||||
* parity, 2026-09-13): FXSAVE/FXRSTOR added here to close a real,
|
||||
* pre-existing gap, not just a future-preemption nicety -- confirmed
|
||||
* live double-precision FP code reachable from ordinary interpreter
|
||||
* dispatch (vm_runtime.c's Loop #5/#6 physics inference, called from
|
||||
* vm_tick() during word execution, not just at boot). The GPR pushes
|
||||
* above only cover integer state; on x86-64 SysV all XMM registers are
|
||||
* caller-saved, meaning the compiler is free to leave a live value in
|
||||
* one between any two instructions of straight-line code -- an
|
||||
* asynchronous interrupt is not a call site the interrupted code's own
|
||||
* instructions know about, so nothing here previously guaranteed FP
|
||||
* state actually survived a timer tick landing mid-computation.
|
||||
*
|
||||
* rbp is repurposed below as a fixed anchor pointing at "rsp right after
|
||||
* the 15 GPR pushes" (rbp's own true pushed value already lives safely
|
||||
* on the stack above and is restored by the ordinary `pop rbp` later) --
|
||||
* FXSAVE requires a 16-byte-aligned address and rsp's alignment here is
|
||||
* unpredictable (the interrupt could have landed anywhere), so the
|
||||
* FXSAVE area is carved out and aligned separately, with rbp used to
|
||||
* keep addressing everything else at its original, fixed offsets
|
||||
* regardless of that adjustment. */
|
||||
mov rbp, rsp
|
||||
sub rsp, 528 /* 512-byte FXSAVE area + 16 slack for alignment */
|
||||
and rsp, -16
|
||||
fxsave [rsp]
|
||||
|
||||
/* Set up arguments for isr_common_handler(vector, error, rip, cs, rflags, cr2) */
|
||||
mov rdi, [rsp + 120] /* vector */
|
||||
mov rsi, [rsp + 128] /* error */
|
||||
mov rdx, [rsp + 136] /* rip */
|
||||
mov rcx, [rsp + 144] /* cs */
|
||||
mov r8, [rsp + 152] /* rflags */
|
||||
mov rdi, [rbp + 120] /* vector */
|
||||
mov rsi, [rbp + 128] /* error */
|
||||
mov rdx, [rbp + 136] /* rip */
|
||||
mov rcx, [rbp + 144] /* cs */
|
||||
mov r8, [rbp + 152] /* rflags */
|
||||
mov r9, cr2 /* cr2 */
|
||||
|
||||
/* Pass stack frame pointer (after pushes) as 7th argument */
|
||||
mov rax, rsp
|
||||
push rax
|
||||
push rbp
|
||||
call isr_common_handler
|
||||
add rsp, 8
|
||||
|
||||
fxrstor [rsp]
|
||||
mov rsp, rbp /* collapse back to right after the GPR pushes */
|
||||
|
||||
/* Restore general-purpose registers */
|
||||
pop r15
|
||||
pop r14
|
||||
|
||||
@@ -16,22 +16,33 @@
|
||||
.hidden riscv64_install_vectors
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Trap frame layout (offsets from sp), 320 bytes, 16-byte aligned.
|
||||
* Trap frame layout (offsets from sp), 512 bytes, 16-byte aligned.
|
||||
*
|
||||
* Only the LP64D psABI's *caller-saved* registers are saved: a C handler
|
||||
* preserves callee-saved registers itself, and the interrupted code already
|
||||
* expects caller-saved registers to be clobbered across a call.
|
||||
* FABRIC-3.md SXXVIII (Stage 0, preemptive-context-switch trap-frame parity,
|
||||
* 2026-09-13): originally only the LP64D psABI's *caller-saved* registers
|
||||
* were saved here, on the reasoning that a C handler preserves callee-saved
|
||||
* registers itself. That reasoning holds for an ordinary function call, but
|
||||
* not for an asynchronous trap: the interrupted code's own compiled
|
||||
* instructions have no call site to justify losing state at, so a value
|
||||
* genuinely live in a callee-saved register at the moment of interrupt has
|
||||
* no ABI guarantee of surviving. s0-s11/fs0-fs11 are now saved too, so this
|
||||
* frame is, in principle, sufficient to resume execution anywhere it was
|
||||
* taken. Not yet copied into any per-VM saved context (that's Stage 2) --
|
||||
* this stage only makes the frame itself complete.
|
||||
*
|
||||
* integer caller-saved (16): ra, t0-t6, a0-a7
|
||||
* integer callee-saved (12): s0-s11
|
||||
* FP caller-saved (20): ft0-ft11, fa0-fa7 + fcsr
|
||||
* FP callee-saved (12): fs0-fs11
|
||||
* supervisor CSRs (2): sepc, sstatus
|
||||
*
|
||||
* The FP half is saved only when sstatus.FS != Off. Nothing in boot.S or
|
||||
* kernel_entry.S programs FS, so its state is whatever firmware left; touching
|
||||
* an f-register while FS == Off raises an illegal-instruction trap, which
|
||||
* inside the trap handler would be unrecoverable. The kernel image does
|
||||
* contain real FP code (fld/fmul.d/fcvt on the Q48.16 and statistics paths),
|
||||
* so the save cannot simply be omitted -- it has to be conditional.
|
||||
* The FP half (caller- and callee-saved alike) is saved only when
|
||||
* sstatus.FS != Off. Nothing in boot.S or kernel_entry.S programs FS, so its
|
||||
* state is whatever firmware left; touching an f-register while FS == Off
|
||||
* raises an illegal-instruction trap, which inside the trap handler would be
|
||||
* unrecoverable. The kernel image does contain real FP code (fld/fmul.d/fcvt
|
||||
* on the Q48.16 and statistics paths), so the save cannot simply be omitted
|
||||
* -- it has to be conditional.
|
||||
* ------------------------------------------------------------------------- */
|
||||
.equ TF_RA, 0
|
||||
.equ TF_T0, 8
|
||||
@@ -49,13 +60,15 @@
|
||||
.equ TF_A5, 104
|
||||
.equ TF_A6, 112
|
||||
.equ TF_A7, 120
|
||||
.equ TF_SEPC, 128
|
||||
.equ TF_SSTATUS,136
|
||||
.equ TF_FCSR, 144
|
||||
.equ TF_F0, 152 /* ft0-ft7 : 152 .. 208 */
|
||||
.equ TF_FA0, 216 /* fa0-fa7 : 216 .. 272 */
|
||||
.equ TF_FT8, 280 /* ft8-ft11 : 280 .. 304 */
|
||||
.equ TF_SIZE, 320
|
||||
.equ TF_S0, 128 /* s0-s11 : 128 .. 216 */
|
||||
.equ TF_SEPC, 224
|
||||
.equ TF_SSTATUS,232
|
||||
.equ TF_FCSR, 240
|
||||
.equ TF_F0, 248 /* ft0-ft7 : 248 .. 304 */
|
||||
.equ TF_FA0, 312 /* fa0-fa7 : 312 .. 368 */
|
||||
.equ TF_FT8, 376 /* ft8-ft11 : 376 .. 400 */
|
||||
.equ TF_FS0, 408 /* fs0-fs11 : 408 .. 496 */
|
||||
.equ TF_SIZE, 512
|
||||
|
||||
/* sstatus.FS occupies bits [14:13]; 0 = Off */
|
||||
.equ SSTATUS_FS_SHIFT, 13
|
||||
@@ -85,6 +98,19 @@ riscv64_trap_entry:
|
||||
sd a6, TF_A6(sp)
|
||||
sd a7, TF_A7(sp)
|
||||
|
||||
sd s0, TF_S0+0(sp)
|
||||
sd s1, TF_S0+8(sp)
|
||||
sd s2, TF_S0+16(sp)
|
||||
sd s3, TF_S0+24(sp)
|
||||
sd s4, TF_S0+32(sp)
|
||||
sd s5, TF_S0+40(sp)
|
||||
sd s6, TF_S0+48(sp)
|
||||
sd s7, TF_S0+56(sp)
|
||||
sd s8, TF_S0+64(sp)
|
||||
sd s9, TF_S0+72(sp)
|
||||
sd s10, TF_S0+80(sp)
|
||||
sd s11, TF_S0+88(sp)
|
||||
|
||||
csrr t0, sepc
|
||||
sd t0, TF_SEPC(sp)
|
||||
csrr t0, sstatus
|
||||
@@ -117,6 +143,18 @@ riscv64_trap_entry:
|
||||
fsd ft9, TF_FT8+8(sp)
|
||||
fsd ft10, TF_FT8+16(sp)
|
||||
fsd ft11, TF_FT8+24(sp)
|
||||
fsd fs0, TF_FS0+0(sp)
|
||||
fsd fs1, TF_FS0+8(sp)
|
||||
fsd fs2, TF_FS0+16(sp)
|
||||
fsd fs3, TF_FS0+24(sp)
|
||||
fsd fs4, TF_FS0+32(sp)
|
||||
fsd fs5, TF_FS0+40(sp)
|
||||
fsd fs6, TF_FS0+48(sp)
|
||||
fsd fs7, TF_FS0+56(sp)
|
||||
fsd fs8, TF_FS0+64(sp)
|
||||
fsd fs9, TF_FS0+72(sp)
|
||||
fsd fs10, TF_FS0+80(sp)
|
||||
fsd fs11, TF_FS0+88(sp)
|
||||
1:
|
||||
|
||||
/* scause bit 63 set => interrupt (negative when read as signed). */
|
||||
@@ -161,6 +199,18 @@ riscv64_trap_entry:
|
||||
fld ft9, TF_FT8+8(sp)
|
||||
fld ft10, TF_FT8+16(sp)
|
||||
fld ft11, TF_FT8+24(sp)
|
||||
fld fs0, TF_FS0+0(sp)
|
||||
fld fs1, TF_FS0+8(sp)
|
||||
fld fs2, TF_FS0+16(sp)
|
||||
fld fs3, TF_FS0+24(sp)
|
||||
fld fs4, TF_FS0+32(sp)
|
||||
fld fs5, TF_FS0+40(sp)
|
||||
fld fs6, TF_FS0+48(sp)
|
||||
fld fs7, TF_FS0+56(sp)
|
||||
fld fs8, TF_FS0+64(sp)
|
||||
fld fs9, TF_FS0+72(sp)
|
||||
fld fs10, TF_FS0+80(sp)
|
||||
fld fs11, TF_FS0+88(sp)
|
||||
3:
|
||||
csrw sstatus, t0
|
||||
ld t0, TF_SEPC(sp)
|
||||
@@ -183,6 +233,19 @@ riscv64_trap_entry:
|
||||
ld a6, TF_A6(sp)
|
||||
ld a7, TF_A7(sp)
|
||||
|
||||
ld s0, TF_S0+0(sp)
|
||||
ld s1, TF_S0+8(sp)
|
||||
ld s2, TF_S0+16(sp)
|
||||
ld s3, TF_S0+24(sp)
|
||||
ld s4, TF_S0+32(sp)
|
||||
ld s5, TF_S0+40(sp)
|
||||
ld s6, TF_S0+48(sp)
|
||||
ld s7, TF_S0+56(sp)
|
||||
ld s8, TF_S0+64(sp)
|
||||
ld s9, TF_S0+72(sp)
|
||||
ld s10, TF_S0+80(sp)
|
||||
ld s11, TF_S0+88(sp)
|
||||
|
||||
addi sp, sp, TF_SIZE
|
||||
sret
|
||||
|
||||
|
||||
Reference in New Issue
Block a user