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