Path: blob/main/crates/fiber/src/stackswitch/x86_64.rs
3063 views
// A WORD OF CAUTION1//2// This entire file basically needs to be kept in sync with itself. It's not3// really possible to modify just one bit of this file without understanding4// all the other bits. Documentation tries to reference various bits here and5// there but try to make sure to read over everything before tweaking things!67use core::arch::naked_asm;89#[inline(never)] // FIXME(rust-lang/rust#148307)10pub(crate) unsafe extern "C" fn wasmtime_fiber_switch(top_of_stack: *mut u8) {11unsafe { wasmtime_fiber_switch_(top_of_stack) }12}1314#[unsafe(naked)]15unsafe extern "C" fn wasmtime_fiber_switch_(top_of_stack: *mut u8 /* rdi */) {16naked_asm!(17"18// We're switching to arbitrary code somewhere else, so pessimistically19// assume that all callee-save register are clobbered. This means we need20// to save/restore all of them.21//22// Note that this order for saving is important since we use CFI directives23// below to point to where all the saved registers are.24push rbp25push rbx26push r1227push r1328push r1429push r153031// Load pointer that we're going to resume at and store where we're going32// to get resumed from. This is in accordance with the diagram at the top33// of unix.rs.34mov rax, -0x10[rdi]35mov -0x10[rdi], rsp3637// Swap stacks and restore all our callee-saved registers38mov rsp, rax39pop r1540pop r1441pop r1342pop r1243pop rbx44pop rbp45ret46",47);48}4950pub(crate) unsafe fn wasmtime_fiber_init(51top_of_stack: *mut u8,52entry_point: extern "C" fn(*mut u8, *mut u8),53entry_arg0: *mut u8,54) {55#[repr(C)]56#[derive(Default)]57struct InitialStack {58r15: *mut u8,59r14: *mut u8,60r13: *mut u8,61r12: *mut u8,62rbx: *mut u8,63rbp: *mut u8,64return_address: *mut u8,6566// unix.rs reserved space67last_sp: *mut u8,68run_result: *mut u8,69}7071unsafe {72let initial_stack = top_of_stack.cast::<InitialStack>().sub(1);73initial_stack.write(InitialStack {74r12: entry_arg0,75rbx: entry_point as *mut u8,76rbp: top_of_stack,77return_address: wasmtime_fiber_start as *mut u8,78last_sp: initial_stack.cast(),79..InitialStack::default()80});81}82}8384// This is a pretty special function that has no real signature. Its use is to85// be the "base" function of all fibers. This entrypoint is used in86// `wasmtime_fiber_init` to bootstrap the execution of a new fiber.87//88// We also use this function as a persistent frame on the stack to emit dwarf89// information to unwind into the caller. This allows us to unwind from the90// fiber's stack back to the main stack that the fiber was called from. We use91// special dwarf directives here to do so since this is a pretty nonstandard92// function.93//94// If you're curious a decent introduction to CFI things and unwinding is at95// https://www.imperialviolet.org/2017/01/18/cfi.html96#[unsafe(naked)]97unsafe extern "C" fn wasmtime_fiber_start() -> ! {98naked_asm!(99"100// Use the `simple` directive on the startproc here which indicates that101// some default settings for the platform are omitted, since this102// function is so nonstandard103.cfi_startproc simple104.cfi_def_cfa_offset 0105106// This is where things get special, we're specifying a custom dwarf107// expression for how to calculate the CFA. The goal here is that we108// need to load the parent's stack pointer just before the call it made109// into `wasmtime_fiber_switch`. Note that the CFA value changes over110// time as well because a fiber may be resumed multiple times from111// different points on the original stack. This means that our custom112// CFA directive involves `DW_OP_deref`, which loads data from memory.113//114// The expression we're encoding here is that the CFA, the stack pointer115// of whatever called into `wasmtime_fiber_start`, is:116//117// *$rsp + 0x38118//119// $rsp is the stack pointer of `wasmtime_fiber_start` at the time the120// next instruction after the `.cfi_escape` is executed. Our $rsp at the121// start of this function is 16 bytes below the top of the stack (0xAff0122// in the diagram in unix.rs). The $rsp to resume at is stored at that123// location, so we dereference the stack pointer to load it.124//125// After dereferencing, though, we have the $rsp value for126// `wasmtime_fiber_switch` itself. That's a weird function which sort of127// and sort of doesn't exist on the stack. We want to point to the128// caller of `wasmtime_fiber_switch`, so to do that we need to skip the129// stack space reserved by `wasmtime_fiber_switch`, which is the 6 saved130// registers plus the return address of the caller's `call` instruction.131// Hence we offset another 0x38 bytes.132.cfi_escape 0x0f, /* DW_CFA_def_cfa_expression */ \1334, /* the byte length of this expression */ \1340x57, /* DW_OP_reg7 (rsp) */ \1350x06, /* DW_OP_deref */ \1360x23, 0x38 /* DW_OP_plus_uconst 0x38 */137138// And now after we've indicated where our CFA is for our parent139// function, we can define that where all of the saved registers are140// located. This uses standard `.cfi` directives which indicate that141// these registers are all stored relative to the CFA. Note that this142// order is kept in sync with the above register spills in143// `wasmtime_fiber_switch`.144.cfi_rel_offset rip, -8145.cfi_rel_offset rbp, -16146.cfi_rel_offset rbx, -24147.cfi_rel_offset r12, -32148.cfi_rel_offset r13, -40149.cfi_rel_offset r14, -48150.cfi_rel_offset r15, -56151152// The body of this function is pretty similar. All our parameters are153// already loaded into registers by the switch function. The154// `wasmtime_fiber_init` routine arranged the various values to be155// materialized into the registers used here. Our job is to then move156// the values into the ABI-defined registers and call the entry-point.157// Note that `call` is used here to leave this frame on the stack so we158// can use the dwarf info here for unwinding. The trailing `ud2` is just159// for safety.160mov rdi, r12161mov rsi, rbp162call rbx163ud2164.cfi_endproc165",166);167}168169170