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