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)
wasmtime_continuation_start_address() -> *const ()11 pub fn wasmtime_continuation_start_address() -> *const () {
12     wasmtime_continuation_start as *const ()
13 }
14 
15 // This is a pretty special function that has no real signature. Its use is to
16 // be the "base" function of all fibers. This entrypoint is used in
17 // `wasmtime_continuation_init` to bootstrap the execution of a new fiber.
18 //
19 // We also use this function as a persistent frame on the stack to emit dwarf
20 // information to unwind into the caller. This allows us to unwind from the
21 // fiber's stack back to the initial stack that the fiber was called from. We use
22 // special dwarf directives here to do so since this is a pretty nonstandard
23 // function.
24 //
25 // If you're curious a decent introduction to CFI things and unwinding is at
26 // https://www.imperialviolet.org/2017/01/18/cfi.html
27 //
28 // Note that this function is never called directly. It is only ever entered
29 // when a `stack_switch` instruction loads its address when switching to a stack
30 // prepared by `FiberStack::initialize`.
31 //
32 // Executing `stack_switch` on a stack prepared by `FiberStack::initialize` as
33 // described in the comment on `FiberStack::initialize` leads to the following
34 // values in various registers when execution of wasmtime_continuation_start begins:
35 //
36 // RSP: TOS - 0x40 - (16 * `args_capacity`)
37 // RBP: TOS - 0x10
38 
39 #[unsafe(naked)]
wasmtime_continuation_start()40 pub(crate) unsafe extern "C" fn wasmtime_continuation_start() {
41     naked_asm!(
42         "
43         // TODO(frank-emrich): Restore DWARF information for this function. In
44         // the meantime, debugging is possible using frame pointer walking.
45 
46 
47         //
48         // Note that the next 4 instructions amount to calling fiber_start
49         // with the following arguments:
50         // 1. func_ref
51         // 2. caller_vmctx
52         // 3. args (of type *mut ArrayRef<ValRaw>)
53         // 4. return_value_count
54         //
55 
56         pop rcx // return_value_count
57         pop rdx // args
58         pop rsi // caller_vmctx
59         pop rdi // func_ref
60         // Note that RBP already contains the right frame pointer to build a
61         // frame pointer chain including the parent continuation:
62         // The current value of RBP is where we store the parent RBP in the
63         // control context!
64         call {fiber_start}
65 
66         // Return to the parent continuation.
67         // RBP is callee-saved (no matter if it's used as a frame pointe or
68         // not), so its value is still TOS - 0x10.
69         // Use that fact to obtain saved parent RBP, RSP, and RIP from control
70         // context near TOS.
71         mov rsi,  0x08[rbp] // putting new RIP in temp register
72         mov rsp, -0x08[rbp]
73         mov rbp,      [rbp]
74 
75         // The stack_switch instruction uses register RDI for the payload.
76         // Here, the payload indicates that we are returning (value 0).
77         // See the test case below to keep this in sync with
78         // ControlEffect::return_()
79         mov rdi, 0
80 
81         jmp rsi
82         ",
83         fiber_start = sym super::fiber_start,
84     );
85 }
86 
87 #[test]
test_return_payload()88 fn test_return_payload() {
89     // The following assumption is baked into `wasmtime_continuation_start`.
90     assert_eq!(wasmtime_environ::CONTROL_EFFECT_RETURN_DISCRIMINANT, 0);
91 }
92