/* * switch.S (aarch64) - Cooperative VM context switch primitive. * * FABRIC-3.md §XXVIII (Stage 2, preemptive-context-switch save/restore * primitive, 2026-09-13). Deliberately NOT isr.S's interrupt trap frame * (Stage 0) -- that saves everything because an interrupt has no call * site. sk_vm_switch_to() is an ordinary `bl`, so AAPCS64 already makes * every caller-saved register (x0-x18, v0-v7, v16-v31) the caller's own * problem across the call. Only the callee-saved set needs explicit * handling: x19-x28, x29 (fp), x30 (lr), and d8-d15 (only the low 64 * bits of v8-v15 are callee-saved per AAPCS64). * * void sk_vm_switch_to(uint64_t *save_sp_out, uint64_t new_sp); * x0 = save_sp_out, x1 = new_sp * * For a VM that has never run before, new_sp must point at a synthetic * 160-byte frame built by the C glue (switch.c) to this exact layout, * with x30 (lr) set to the trampoline entry address and everything else * zeroed, so the final `ret` below (which branches to lr) jumps there. */ .section .text .balign 4 .equ SW_X19, 0 .equ SW_X21, 16 .equ SW_X23, 32 .equ SW_X25, 48 .equ SW_X27, 64 .equ SW_X29, 80 /* x29 (fp), x30 (lr) */ .equ SW_D8, 96 .equ SW_D10, 112 .equ SW_D12, 128 .equ SW_D14, 144 .equ SW_SIZE, 160 /* .hidden is an ELF-only visibility directive with no COFF equivalent; * this file is also assembled for the aarch64 loader, which clang targets * at aarch64-pc-windows-msvc (PE/COFF) -- guard on _WIN32 (defined only by * that target), matching isr.S's own precedent exactly. */ #ifndef _WIN32 .hidden sk_vm_switch_to #endif .global sk_vm_switch_to sk_vm_switch_to: sub sp, sp, #SW_SIZE stp x19, x20, [sp, #SW_X19] stp x21, x22, [sp, #SW_X21] stp x23, x24, [sp, #SW_X23] stp x25, x26, [sp, #SW_X25] stp x27, x28, [sp, #SW_X27] stp x29, x30, [sp, #SW_X29] stp d8, d9, [sp, #SW_D8] stp d10, d11, [sp, #SW_D10] stp d12, d13, [sp, #SW_D12] stp d14, d15, [sp, #SW_D14] mov x2, sp str x2, [x0] /* *save_sp_out = sp (this context's parked SP) */ mov sp, x1 /* sp = new_sp (switch stacks) */ ldp x19, x20, [sp, #SW_X19] ldp x21, x22, [sp, #SW_X21] ldp x23, x24, [sp, #SW_X23] ldp x25, x26, [sp, #SW_X25] ldp x27, x28, [sp, #SW_X27] ldp x29, x30, [sp, #SW_X29] ldp d8, d9, [sp, #SW_D8] ldp d10, d11, [sp, #SW_D10] ldp d12, d13, [sp, #SW_D12] ldp d14, d15, [sp, #SW_D14] add sp, sp, #SW_SIZE ret /* * uint64_t sk_vm_switch_prime(uint64_t stack_top, void *entry); * * Builds the synthetic frame above at the top of a fresh native stack, so * the first ever sk_vm_switch_to() into it lands at `entry` (x30/lr) via * its final `ret`. Kept in this file (not C) so the layout can never * drift from sk_vm_switch_to() above -- one set of offsets, not two. */ #ifndef _WIN32 .hidden sk_vm_switch_prime #endif .global sk_vm_switch_prime sk_vm_switch_prime: sub x2, x0, #SW_SIZE stp xzr, xzr, [x2, #SW_X19] stp xzr, xzr, [x2, #SW_X21] stp xzr, xzr, [x2, #SW_X23] stp xzr, xzr, [x2, #SW_X25] stp xzr, xzr, [x2, #SW_X27] stp xzr, x1, [x2, #SW_X29] /* x29=0, x30(lr)=entry */ stp xzr, xzr, [x2, #SW_D8] stp xzr, xzr, [x2, #SW_D10] stp xzr, xzr, [x2, #SW_D12] stp xzr, xzr, [x2, #SW_D14] mov x0, x2 ret