1 // A WORD OF CAUTION 2 // 3 // This entire file basically needs to be kept in sync with itself. It's not 4 // really possible to modify just one bit of this file without understanding 5 // all the other bits. Documentation tries to reference various bits here and 6 // there but try to make sure to read over everything before tweaking things! 7 8 use core::arch::naked_asm; 9 10 #[unsafe(naked)] 11 pub(crate) unsafe extern "C" fn wasmtime_fiber_switch(top_of_stack: *mut u8 /* rdi */) { 12 naked_asm!( 13 " 14 // We're switching to arbitrary code somewhere else, so pessimistically 15 // assume that all callee-save register are clobbered. This means we need 16 // to save/restore all of them. 17 // 18 // Note that this order for saving is important since we use CFI directives 19 // below to point to where all the saved registers are. 20 push rbp 21 push rbx 22 push r12 23 push r13 24 push r14 25 push r15 26 27 // Load pointer that we're going to resume at and store where we're going 28 // to get resumed from. This is in accordance with the diagram at the top 29 // of unix.rs. 30 mov rax, -0x10[rdi] 31 mov -0x10[rdi], rsp 32 33 // Swap stacks and restore all our callee-saved registers 34 mov rsp, rax 35 pop r15 36 pop r14 37 pop r13 38 pop r12 39 pop rbx 40 pop rbp 41 ret 42 ", 43 ); 44 } 45 46 #[unsafe(naked)] 47 pub(crate) unsafe extern "C" fn wasmtime_fiber_init( 48 top_of_stack: *mut u8, // rdi 49 entry_point: extern "C" fn(*mut u8, *mut u8), // rsi 50 entry_arg0: *mut u8, // rdx 51 ) { 52 naked_asm!( 53 " 54 // Here we're going to set up a stack frame as expected by 55 // `wasmtime_fiber_switch`. The values we store here will get restored into 56 // registers by that function and the `wasmtime_fiber_start` function will 57 // take over and understands which values are in which registers. 58 // 59 // The first 16 bytes of stack are reserved for metadata, so we start 60 // storing values beneath that. 61 lea rax, {start}[rip] 62 mov -0x18[rdi], rax 63 mov -0x20[rdi], rdi // loaded into rbp during switch 64 mov -0x28[rdi], rsi // loaded into rbx during switch 65 mov -0x30[rdi], rdx // loaded into r12 during switch 66 67 // And then we specify the stack pointer resumption should begin at. Our 68 // `wasmtime_fiber_switch` function consumes 6 registers plus a return 69 // pointer, and the top 16 bytes are reserved, so that's: 70 // 71 // (6 + 1) * 16 + 16 = 0x48 72 lea rax, -0x48[rdi] 73 mov -0x10[rdi], rax 74 ret 75 ", 76 start = sym wasmtime_fiber_start, 77 ); 78 } 79 80 // This is a pretty special function that has no real signature. Its use is to 81 // be the "base" function of all fibers. This entrypoint is used in 82 // `wasmtime_fiber_init` to bootstrap the execution of a new fiber. 83 // 84 // We also use this function as a persistent frame on the stack to emit dwarf 85 // information to unwind into the caller. This allows us to unwind from the 86 // fiber's stack back to the main stack that the fiber was called from. We use 87 // special dwarf directives here to do so since this is a pretty nonstandard 88 // function. 89 // 90 // If you're curious a decent introduction to CFI things and unwinding is at 91 // https://www.imperialviolet.org/2017/01/18/cfi.html 92 #[unsafe(naked)] 93 unsafe extern "C" fn wasmtime_fiber_start() -> ! { 94 naked_asm!( 95 " 96 // Use the `simple` directive on the startproc here which indicates that 97 // some default settings for the platform are omitted, since this 98 // function is so nonstandard 99 .cfi_startproc simple 100 .cfi_def_cfa_offset 0 101 102 // This is where things get special, we're specifying a custom dwarf 103 // expression for how to calculate the CFA. The goal here is that we 104 // need to load the parent's stack pointer just before the call it made 105 // into `wasmtime_fiber_switch`. Note that the CFA value changes over 106 // time as well because a fiber may be resumed multiple times from 107 // different points on the original stack. This means that our custom 108 // CFA directive involves `DW_OP_deref`, which loads data from memory. 109 // 110 // The expression we're encoding here is that the CFA, the stack pointer 111 // of whatever called into `wasmtime_fiber_start`, is: 112 // 113 // *$rsp + 0x38 114 // 115 // $rsp is the stack pointer of `wasmtime_fiber_start` at the time the 116 // next instruction after the `.cfi_escape` is executed. Our $rsp at the 117 // start of this function is 16 bytes below the top of the stack (0xAff0 118 // in the diagram in unix.rs). The $rsp to resume at is stored at that 119 // location, so we dereference the stack pointer to load it. 120 // 121 // After dereferencing, though, we have the $rsp value for 122 // `wasmtime_fiber_switch` itself. That's a weird function which sort of 123 // and sort of doesn't exist on the stack. We want to point to the 124 // caller of `wasmtime_fiber_switch`, so to do that we need to skip the 125 // stack space reserved by `wasmtime_fiber_switch`, which is the 6 saved 126 // registers plus the return address of the caller's `call` instruction. 127 // Hence we offset another 0x38 bytes. 128 .cfi_escape 0x0f, /* DW_CFA_def_cfa_expression */ \ 129 4, /* the byte length of this expression */ \ 130 0x57, /* DW_OP_reg7 (rsp) */ \ 131 0x06, /* DW_OP_deref */ \ 132 0x23, 0x38 /* DW_OP_plus_uconst 0x38 */ 133 134 // And now after we've indicated where our CFA is for our parent 135 // function, we can define that where all of the saved registers are 136 // located. This uses standard `.cfi` directives which indicate that 137 // these registers are all stored relative to the CFA. Note that this 138 // order is kept in sync with the above register spills in 139 // `wasmtime_fiber_switch`. 140 .cfi_rel_offset rip, -8 141 .cfi_rel_offset rbp, -16 142 .cfi_rel_offset rbx, -24 143 .cfi_rel_offset r12, -32 144 .cfi_rel_offset r13, -40 145 .cfi_rel_offset r14, -48 146 .cfi_rel_offset r15, -56 147 148 // The body of this function is pretty similar. All our parameters are 149 // already loaded into registers by the switch function. The 150 // `wasmtime_fiber_init` routine arranged the various values to be 151 // materialized into the registers used here. Our job is to then move 152 // the values into the ABI-defined registers and call the entry-point. 153 // Note that `call` is used here to leave this frame on the stack so we 154 // can use the dwarf info here for unwinding. The trailing `ud2` is just 155 // for safety. 156 mov rdi, r12 157 mov rsi, rbp 158 call rbx 159 ud2 160 .cfi_endproc 161 ", 162 ); 163 } 164