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 // This file is modeled after x86_64.rs and comments are not copied over. For
9 // reference be sure to review the other file. Note that the pointer size is
10 // different so the reserved space at the top of the stack is 8 bytes, not 16
11 // bytes. Still two pointers though.
12 
13 use core::arch::naked_asm;
14 
15 #[inline(never)] // FIXME(rust-lang/rust#148307)
16 pub(crate) unsafe extern "C" fn wasmtime_fiber_switch(top_of_stack: *mut u8) {
17     unsafe { wasmtime_fiber_switch_(top_of_stack) }
18 }
19 
20 #[unsafe(naked)]
21 unsafe extern "C" fn wasmtime_fiber_switch_(top_of_stack: *mut u8) {
22     naked_asm!(
23         "
24         // Load our stack-to-use
25         mov eax, 0x4[esp]
26         mov ecx, -0x8[eax]
27 
28         // Save callee-saved registers
29         push ebp
30         push ebx
31         push esi
32         push edi
33 
34         // Save our current stack and jump to the stack-to-use
35         mov -0x8[eax], esp
36         mov esp, ecx
37 
38         // Restore callee-saved registers
39         pop edi
40         pop esi
41         pop ebx
42         pop ebp
43         ret
44         ",
45     )
46 }
47 
48 pub(crate) unsafe fn wasmtime_fiber_init(
49     top_of_stack: *mut u8,
50     entry_point: extern "C" fn(*mut u8, *mut u8),
51     entry_arg0: *mut u8,
52 ) {
53     // Our stack from top-to-bottom looks like:
54     //
55     //	  * 8 bytes of reserved space per unix.rs (two-pointers space)
56     //	  * 8 bytes of arguments (two arguments wasmtime_fiber_start forwards)
57     //	  * 4 bytes of return address
58     //	  * 16 bytes of saved registers
59     //
60     // Note that after the return address the stack is conveniently 16-byte
61     // aligned as required, so we just leave the arguments on the stack in
62     // `wasmtime_fiber_start` and immediately do the call.
63     #[repr(C)]
64     #[derive(Default)]
65     struct InitialStack {
66         // state that will get resumed into from a `wasmtime_fiber_switch`
67         // starting up this fiber.
68         edi: *mut u8,
69         esi: *mut u8,
70         ebx: *mut u8,
71         ebp: *mut u8,
72         return_address: *mut u8,
73 
74         // two arguments to `entry_point`
75         arg1: *mut u8,
76         arg2: *mut u8,
77 
78         // unix.rs reserved space
79         last_sp: *mut u8,
80         run_result: *mut u8,
81     }
82 
83     unsafe {
84         let initial_stack = top_of_stack.cast::<InitialStack>().sub(1);
85         initial_stack.write(InitialStack {
86             ebp: entry_point as *mut u8,
87             return_address: wasmtime_fiber_start as *mut u8,
88             arg1: entry_arg0,
89             arg2: top_of_stack,
90             last_sp: initial_stack.cast(),
91             ..InitialStack::default()
92         });
93     }
94 }
95 
96 #[unsafe(naked)]
97 unsafe extern "C" fn wasmtime_fiber_start() -> ! {
98     naked_asm!(
99         "
100         .cfi_startproc simple
101         .cfi_def_cfa_offset 0
102         .cfi_escape 0x0f, /* DW_CFA_def_cfa_expression */ \
103             5,            /* the byte length of this expression */ \
104             0x74, 0x08,   /* DW_OP_breg4 (%esp) + 8 */ \
105             0x06,         /* DW_OP_deref */ \
106             0x23, 0x14    /* DW_OP_plus_uconst 0x14 */
107 
108         .cfi_rel_offset eip, -4
109         .cfi_rel_offset ebp, -8
110         .cfi_rel_offset ebx, -12
111         .cfi_rel_offset esi, -16
112         .cfi_rel_offset edi, -20
113 
114         // Our arguments and stack alignment are all prepped by
115         // `wasmtime_fiber_init`.
116         call ebp
117         ud2
118         .cfi_endproc
119         ",
120     );
121 }
122