lucet_runtime_internals/context/
mod.rs

1#![allow(improper_ctypes)]
2
3#[cfg(test)]
4mod tests;
5
6use crate::instance::Instance;
7use crate::val::{val_to_reg, val_to_stack, RegVal, UntypedRetVal, Val};
8use nix;
9use nix::sys::signal;
10use std::arch::x86_64::{__m128, _mm_setzero_ps};
11use std::ptr::NonNull;
12use std::{mem, ptr};
13use thiserror::Error;
14
15/// Callee-saved general-purpose registers in the AMD64 ABI.
16///
17/// # Layout
18///
19/// `repr(C)` is required to preserve the ordering of members, which are read by the assembly at
20/// hard-coded offsets.
21///
22/// # TODOs
23///
24/// - Unlike the C code, this doesn't use the `packed` repr due to warnings in the Nomicon:
25/// <https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked>. Since the members are all
26/// `u64`, this should be fine?
27#[repr(C)]
28pub(crate) struct GpRegs {
29    rbx: u64,
30    pub(crate) rsp: u64,
31    rbp: u64,
32    pub(crate) rdi: u64,
33    r12: u64,
34    r13: u64,
35    r14: u64,
36    r15: u64,
37    pub(crate) rsi: u64,
38}
39
40impl GpRegs {
41    fn new() -> Self {
42        GpRegs {
43            rbx: 0,
44            rsp: 0,
45            rbp: 0,
46            rdi: 0,
47            r12: 0,
48            r13: 0,
49            r14: 0,
50            r15: 0,
51            rsi: 0,
52        }
53    }
54}
55
56/// Floating-point argument registers in the AMD64 ABI.
57///
58/// # Layout
59///
60/// `repr(C)` is required to preserve the ordering of members, which are read by the assembly at
61/// hard-coded offsets.
62///
63/// # TODOs
64///
65/// - Unlike the C code, this doesn't use the `packed` repr due to warnings in the Nomicon:
66/// <https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked>. Since the members are all
67/// `__m128`, this should be fine?
68#[repr(C)]
69struct FpRegs {
70    xmm0: __m128,
71    xmm1: __m128,
72    xmm2: __m128,
73    xmm3: __m128,
74    xmm4: __m128,
75    xmm5: __m128,
76    xmm6: __m128,
77    xmm7: __m128,
78}
79
80impl FpRegs {
81    fn new() -> Self {
82        let zero = unsafe { _mm_setzero_ps() };
83        FpRegs {
84            xmm0: zero,
85            xmm1: zero,
86            xmm2: zero,
87            xmm3: zero,
88            xmm4: zero,
89            xmm5: zero,
90            xmm6: zero,
91            xmm7: zero,
92        }
93    }
94}
95
96/// Everything we need to make a context switch: a signal mask, and the registers and return values
97/// that are manipulated directly by assembly code.
98///
99/// A context also tracks which other context to swap back to if a child context's entrypoint function
100/// returns, and can optionally contain a callback function to be run just before that swap occurs.
101///
102/// # Layout
103///
104/// The `repr(C)` and order of fields in this struct are very important, as the assembly code reads
105/// and writes hard-coded offsets from the base of the struct. Without `repr(C)`, Rust is free to
106/// reorder the fields.
107///
108/// Contexts are also `repr(align(64))` in order to align to cache lines and minimize contention
109/// when running multiple threads.
110///
111/// # Movement
112///
113/// `Context` values must not be moved once they've been initialized. Contexts contain a pointer to
114/// their stack, which in turn contains a pointer back to the context. If the context gets moved,
115/// that pointer becomes invalid, and the behavior of returning from that context becomes undefined.
116#[repr(C, align(64))]
117pub struct Context {
118    pub(crate) gpr: GpRegs,
119    fpr: FpRegs,
120    retvals_gp: [u64; 2],
121    retval_fp: __m128,
122    parent_ctx: *mut Context,
123    // TODO ACF 2019-10-23: make Instance into a generic parameter?
124    backstop_callback: *const unsafe extern "C" fn(*mut Instance),
125    backstop_data: *mut Instance,
126    sigset: signal::SigSet,
127}
128
129impl Context {
130    /// Create an all-zeroed `Context`.
131    pub fn new() -> Self {
132        Context {
133            gpr: GpRegs::new(),
134            fpr: FpRegs::new(),
135            retvals_gp: [0; 2],
136            retval_fp: unsafe { _mm_setzero_ps() },
137            parent_ctx: ptr::null_mut(),
138            backstop_callback: Context::default_backstop_callback as *const _,
139            backstop_data: ptr::null_mut(),
140            sigset: signal::SigSet::empty(),
141        }
142    }
143}
144
145/// A wrapper around a `Context`, primarily meant for use in test code.
146///
147/// Users of this library interact with contexts implicitly via `Instance` values, but for testing
148/// the context code independently, it is helpful to use contexts directly.
149///
150/// # Movement of `ContextHandle`
151///
152/// `ContextHandle` keeps a pointer to a `Context` rather than keeping all of the data directly as
153/// fields in order to have better control over where that data lives in memory. We always want that
154/// data to be heap-allocated, and to never move once it has been initialized. The `ContextHandle`,
155/// by contrast, should be treated like a normal Rust value with no such restrictions.
156///
157/// Until the `Unpin` marker trait arrives in stable Rust, it is difficult to enforce this with the
158/// type system alone, so we use a bit of unsafety and (hopefully) clever API design to ensure that
159/// the data cannot be moved.
160///
161/// We create the `Context` within a box to allocate it on the heap, then convert it into a raw
162/// pointer to relinquish ownership. When accessing the internal structure via the `DerefMut` trait,
163/// data must not be moved out of the `Context` with functions like `mem::replace`.
164///
165/// # Layout
166///
167/// Foreign code accesses the `internal` pointer in tests, so it is important that it is the first
168/// member, and that the struct is `repr(C)`.
169#[repr(C)]
170pub struct ContextHandle {
171    internal: NonNull<Context>,
172}
173
174impl Drop for ContextHandle {
175    fn drop(&mut self) {
176        unsafe {
177            // create a box from the pointer so that it'll get dropped
178            // and we won't leak `Context`s
179            Box::from_raw(self.internal.as_ptr());
180        }
181    }
182}
183
184impl std::ops::Deref for ContextHandle {
185    type Target = Context;
186    fn deref(&self) -> &Self::Target {
187        unsafe { self.internal.as_ref() }
188    }
189}
190
191impl std::ops::DerefMut for ContextHandle {
192    fn deref_mut(&mut self) -> &mut Self::Target {
193        unsafe { self.internal.as_mut() }
194    }
195}
196
197impl ContextHandle {
198    /// Create an all-zeroed `ContextHandle`.
199    pub fn new() -> Self {
200        let internal = NonNull::new(Box::into_raw(Box::new(Context::new())))
201            .expect("Box::into_raw should never return NULL");
202        ContextHandle { internal }
203    }
204
205    pub fn create_and_init(
206        stack: &mut [u64],
207        fptr: usize,
208        args: &[Val],
209    ) -> Result<ContextHandle, Error> {
210        let mut child = ContextHandle::new();
211        Context::init(stack, &mut child, fptr, args)?;
212        Ok(child)
213    }
214}
215
216struct CallStackBuilder<'a> {
217    offset: usize,
218    stack: &'a mut [u64],
219}
220
221impl<'a> CallStackBuilder<'a> {
222    pub fn new(stack: &'a mut [u64]) -> Self {
223        CallStackBuilder { offset: 0, stack }
224    }
225
226    fn push(&mut self, val: u64) {
227        self.offset += 1;
228        self.stack[self.stack.len() - self.offset] = val;
229    }
230
231    /// Stores `args` onto the stack such that when a return address is written after, the
232    /// complete unit will be 16-byte aligned, as the x86_64 ABI requires.
233    ///
234    /// That is to say, `args` will be padded such that the current top of stack is 8-byte
235    /// aligned.
236    fn store_args(&mut self, args: &[u64]) {
237        let items_end = args.len() + self.offset;
238
239        if items_end % 2 == 1 {
240            // we need to add one entry just before the arguments so that the arguments start on an
241            // aligned address.
242            self.push(0);
243        }
244
245        for arg in args.iter().rev() {
246            self.push(*arg);
247        }
248    }
249
250    fn offset(&self) -> usize {
251        self.offset
252    }
253
254    fn into_inner(self) -> (&'a mut [u64], usize) {
255        (self.stack, self.offset)
256    }
257}
258
259impl Context {
260    /// Initialize a new child context.
261    ///
262    /// - `stack`: The stack for the child; *must be 16-byte aligned*.
263    ///
264    /// - `child`: The context for the child. The fields of this structure will be overwritten by
265    /// `init`.
266    ///
267    /// - `fptr`: A pointer to the entrypoint for the child. Note that while the type signature here
268    /// is for a void function of no arguments (equivalent to `void (*fptr)(void)` in C), the
269    /// entrypoint actually can be a function of any argument or return type that corresponds to a
270    /// `val::Val` variant.
271    ///
272    /// - `args`: A slice of arguments for the `fptr` entrypoint. These must match the number and
273    /// types of `fptr`'s actual arguments exactly, otherwise swapping to this context will cause
274    /// undefined behavior.
275    ///
276    /// # Errors
277    ///
278    /// - `Error::UnalignedStack` if the _end_ of `stack` is not 16-byte aligned.
279    ///
280    /// # Examples
281    ///
282    /// ## C entrypoint
283    ///
284    /// This example initializes a context that will start in a C function `entrypoint` when first
285    /// swapped to.
286    ///
287    /// ```c
288    /// void entrypoint(uint64_t x, float y);
289    /// ```
290    ///
291    /// ```no_run
292    /// # use lucet_runtime_internals::context::Context;
293    /// # use lucet_runtime_internals::val::Val;
294    /// extern "C" { fn entrypoint(x: u64, y: f32); }
295    /// // allocating an even number of `u64`s seems to reliably yield
296    /// // properly aligned stacks, but TODO do better
297    /// let mut stack = vec![0u64; 1024].into_boxed_slice();
298    /// let mut child = Context::new();
299    /// let res = Context::init(
300    ///     &mut *stack,
301    ///     &mut child,
302    ///     entrypoint as usize,
303    ///     &[Val::U64(120), Val::F32(3.14)],
304    /// );
305    /// assert!(res.is_ok());
306    /// ```
307    ///
308    /// ## Rust entrypoint
309    ///
310    /// This example initializes a context that will start in a Rust function `entrypoint` when
311    /// first swapped to. Note that we mark `entrypoint` as `extern "C"` to make sure it is compiled
312    /// with C calling conventions.
313    ///
314    /// ```no_run
315    /// # use lucet_runtime_internals::context::{Context, ContextHandle};
316    /// # use lucet_runtime_internals::val::Val;
317    /// extern "C" fn entrypoint(x: u64, y: f32) { }
318    /// // allocating an even number of `u64`s seems to reliably yield
319    /// // properly aligned stacks, but TODO do better
320    /// let mut stack = vec![0u64; 1024].into_boxed_slice();
321    /// let mut child = Context::new();
322    /// let res = Context::init(
323    ///     &mut *stack,
324    ///     &mut child,
325    ///     entrypoint as usize,
326    ///     &[Val::U64(120), Val::F32(3.14)],
327    /// );
328    /// assert!(res.is_ok());
329    /// ```
330    ///
331    /// # Implementation details
332    ///
333    /// This prepares a stack for the child context structured as follows, assuming an 0x1000 byte
334    /// stack:
335    /// ```text
336    /// 0x1000: +-------------------------+
337    /// 0x0ff8: | NULL                    | // Null added if necessary for alignment.
338    /// 0x0ff0: | spilled_arg_1           | // Guest arguments follow.
339    /// 0x0fe8: | spilled_arg_2           |
340    /// 0x0fe0: ~ spilled_arg_3           ~ // The three arguments here are just for show.
341    /// 0x0fd8: | lucet_context_backstop  | <-- This forms an ABI-matching call frame for fptr.
342    /// 0x0fd0: | fptr                    | <-- The actual guest code we want to run.
343    /// 0x0fc8: | lucet_context_bootstrap | <-- The guest stack pointer starts here.
344    /// 0x0fc0: |                         |
345    /// 0x0XXX: ~                         ~ // Rest of the stack needs no preparation.
346    /// 0x0000: |                         |
347    ///         +-------------------------+
348    /// ```
349    ///
350    /// This packing of data on the stack is interwoven with noteworthy constraints on what the
351    /// backstop may do:
352    /// * The backstop must not return on the guest stack.
353    ///   - The next value will be a spilled argument or NULL. Neither are an intended address.
354    /// * The backstop cannot have ABI-conforming spilled arguments.
355    ///   - No code runs between `fptr` and `lucet_context_backstop`, so nothing exists to
356    ///     clean up `fptr`'s arguments. `lucet_context_backstop` would have to adjust the
357    ///     stack pointer by a variable amount, and it does not, so `rsp` will continue to
358    ///     point to guest arguments.
359    ///   - This is why bootstrap recieves arguments via rbp, pointing elsewhere on the stack.
360    ///
361    /// The bootstrap function must be careful, but is less constrained since it can clean up
362    /// and prepare a context for `fptr`.
363    pub fn init(
364        stack: &mut [u64],
365        child: &mut Context,
366        fptr: usize,
367        args: &[Val],
368    ) -> Result<(), Error> {
369        Context::init_with_callback(
370            stack,
371            child,
372            Context::default_backstop_callback,
373            ptr::null_mut(),
374            fptr,
375            args,
376        )
377    }
378
379    /// The default backstop callback does nothing, and is just a marker.
380    extern "C" fn default_backstop_callback(_: *mut Instance) {}
381
382    /// Similar to `Context::init()`, but allows setting a callback function to be run when the
383    /// guest entrypoint returns.
384    ///
385    /// After the entrypoint function returns, but before swapping back to the parent context,
386    /// `backstop_callback` will be run with the single argument `backstop_data`.
387    pub fn init_with_callback(
388        stack: &mut [u64],
389        child: &mut Context,
390        backstop_callback: unsafe extern "C" fn(*mut Instance),
391        backstop_data: *mut Instance,
392        fptr: usize,
393        args: &[Val],
394    ) -> Result<(), Error> {
395        if !stack_is_aligned(stack) {
396            return Err(Error::UnalignedStack);
397        }
398
399        if backstop_callback != Context::default_backstop_callback {
400            child.backstop_callback = backstop_callback as *const _;
401            child.backstop_data = backstop_data;
402        }
403
404        let mut gp_args_ix = 0;
405        let mut fp_args_ix = 0;
406        let mut gp_regs_values = [0u64; 6];
407
408        let mut spilled_args = vec![];
409
410        for arg in args {
411            match val_to_reg(arg) {
412                RegVal::GpReg(v) => {
413                    if gp_args_ix >= 6 {
414                        spilled_args.push(val_to_stack(arg));
415                    } else {
416                        gp_regs_values[gp_args_ix] = v;
417                        gp_args_ix += 1;
418                    }
419                }
420                RegVal::FpReg(v) => {
421                    if fp_args_ix >= 8 {
422                        spilled_args.push(val_to_stack(arg));
423                    } else {
424                        child.bootstrap_fp_ix_arg(fp_args_ix, v);
425                        fp_args_ix += 1;
426                    }
427                }
428            }
429        }
430
431        // set up an initial call stack for guests to bootstrap into and execute
432        let mut stack_builder = CallStackBuilder::new(stack);
433
434        // we actually don't want to put an explicit pointer to these arguments anywhere. we'll
435        // line up the rest of the stack such that these are in argument position when we jump to
436        // `fptr`.
437        stack_builder.store_args(spilled_args.as_slice());
438
439        // the stack must be aligned in the environment we'll execute `fptr` from - this is an ABI
440        // requirement and can cause segfaults if not upheld.
441        assert_eq!(
442            stack_builder.offset() % 2,
443            0,
444            "incorrect alignment for guest call frame"
445        );
446
447        // we execute the guest code via returns, so we make a "call stack" of routines like:
448        // -> lucet_context_backstop()
449        //    -> fptr()
450        //       -> lucet_context_bootstrap()
451        //
452        // with each address the start of the named function, so when the inner function
453        // completes it returns to begin the next function up.
454        stack_builder.push(lucet_context_backstop as u64);
455        stack_builder.push(fptr as u64);
456
457        // add all general purpose arguments for the guest to be bootstrapped
458        for arg in gp_regs_values.iter() {
459            stack_builder.push(*arg);
460        }
461
462        stack_builder.push(lucet_context_bootstrap as u64);
463
464        let (stack, stack_start) = stack_builder.into_inner();
465
466        // RSP, RBP, and sigset still remain to be initialized.
467        // Stack pointer: this points to the return address that will be used by `swap`, in place
468        // of the original (eg, in the host) return address. The return address this points to is
469        // the address of the first function to run on `swap`: `lucet_context_bootstrap`.
470        child.gpr.rsp = &mut stack[stack.len() - stack_start] as *mut u64 as u64;
471
472        child.gpr.rbp = child as *const Context as u64;
473
474        // Read the mask to be restored if we ever need to jump out of a signal handler. If this
475        // isn't possible, die.
476        signal::pthread_sigmask(
477            signal::SigmaskHow::SIG_SETMASK,
478            None,
479            Some(&mut child.sigset),
480        )
481        .expect("pthread_sigmask could not be retrieved");
482
483        Ok(())
484    }
485
486    /// Save the current context, and swap to another context.
487    ///
488    /// - `from`: the current context is written here
489    /// - `to`: the context to read from and swap to
490    ///
491    /// The current registers, including the stack pointer, are saved to `from`. The current stack
492    /// pointer is then replaced by the value saved in `to.gpr.rsp`, so when `swap` returns, it will
493    /// return to the pointer saved in `to`'s stack.
494    ///
495    /// If `to` was freshly initialized by passing it as the `child` argument to `init`, `swap` will
496    /// return to the function that bootstraps arguments and then calls the entrypoint that was
497    /// passed to `init`.
498    ///
499    /// If `to` was previously passed as the `from` argument to another call to `swap`, the program
500    /// will return as if from that _first_ call to `swap`.
501    ///
502    /// The address of `from` will be saved as `to.parent_ctx`. If `to` was initialized by `init`,
503    /// it will swap back to the `from` context when the entrypoint function returns via
504    /// `lucet_context_backstop`.
505    ///
506    /// # Safety
507    ///
508    /// The value in `to.gpr.rsp` must be a valid pointer into the stack that was originally passed
509    /// to `init` when the `to` context was initialized, or to the original stack created implicitly
510    /// by Rust.
511    ///
512    /// The registers saved in the `to` context must match the arguments expected by the entrypoint
513    /// of the function passed to `init`, or be unaltered from when they were previously written by
514    /// `swap`.
515    ///
516    /// If `to` was initialized by `init`, the `from` context must not be moved, dropped, or
517    /// otherwise invalidated while in the `to` context unless `to`'s entrypoint function never
518    /// returns.
519    ///
520    /// If `from` is never returned to, `swap`ped to, or `set` to, resources could leak due to
521    /// implicit `drop`s never being called:
522    ///
523    /// ```no_run
524    /// # use lucet_runtime_internals::context::Context;
525    /// fn f(x: Box<u64>, child: &mut Context) {
526    ///     let mut xs = vec![187; 410757864530];
527    ///     xs[0] += *x;
528    ///
529    ///     // manually drop here to avoid leaks
530    ///     drop(x);
531    ///     drop(xs);
532    ///
533    ///     let mut parent = Context::new();
534    ///     unsafe { Context::swap(&mut parent, child); }
535    ///     // implicit `drop(x)` and `drop(xs)` here never get called unless we swap back
536    /// }
537    /// ```
538    ///
539    /// # Examples
540    ///
541    /// The typical case is to initialize a new child context, and then swap to it from a zeroed
542    /// parent context.
543    ///
544    /// ```no_run
545    /// # use lucet_runtime_internals::context::Context;
546    /// # extern "C" fn entrypoint() {}
547    /// # let mut stack = vec![0u64; 1024].into_boxed_slice();
548    /// let mut parent = Context::new();
549    /// let mut child = Context::new();
550    /// Context::init(
551    ///     &mut stack,
552    ///     &mut child,
553    ///     entrypoint as usize,
554    ///     &[],
555    /// ).unwrap();
556    ///
557    /// unsafe { Context::swap(&mut parent, &mut child); }
558    /// ```
559    #[inline]
560    pub unsafe fn swap(from: &mut Context, to: &mut Context) {
561        to.parent_ctx = from;
562        lucet_context_swap(from as *mut _, to as *mut _);
563    }
564
565    /// Swap to another context without saving the current context.
566    ///
567    /// - `to`: the context to read from and swap to
568    ///
569    /// The current registers, including the stack pointer, are discarded. The current stack pointer
570    /// is then replaced by the value saved in `to.gpr.rsp`, so when `swap` returns, it will return
571    /// to the pointer saved in `to`'s stack.
572    ///
573    /// If `to` was freshly initialized by passing it as the child to `init`, `swap` will return to
574    /// the function that bootstraps arguments and then calls the entrypoint that was passed to
575    /// `init`.
576    ///
577    /// If `to` was previously passed as the `from` argument to another call to `swap`, the program
578    /// will return as if from the call to `swap`.
579    ///
580    /// # Safety
581    ///
582    /// ## Stack and registers
583    ///
584    /// The value in `to.gpr.rsp` must be a valid pointer into the stack that was originally passed
585    /// to `init` when the context was initialized, or to the original stack created implicitly by
586    /// Rust.
587    ///
588    /// The registers saved in `to` must match the arguments expected by the entrypoint of the
589    /// function passed to `init`, or be unaltered from when they were previously written by `swap`.
590    ///
591    /// ## Returning
592    ///
593    /// If `to` is a context freshly initialized by `init` (as opposed to a context populated only
594    /// by `swap`, such as a host context), at least one of the following must be true, otherwise
595    /// the program will return to a context with uninitialized registers:
596    ///
597    /// - The `fptr` argument to `init` is a function that never returns
598    ///
599    /// - A valid context must have been passed as the `from` argument to `swap` when entering the
600    ///   current context before this call to `set`
601    ///
602    /// ## Resource leaks
603    ///
604    /// Since control flow will not return to the calling context, care must be taken to ensure that
605    /// any resources owned by the calling context are manually dropped. The implicit `drop`s
606    /// inserted by Rust at the end of the calling scope will not be reached:
607    ///
608    /// ```no_run
609    /// # use lucet_runtime_internals::context::Context;
610    /// fn f(x: Box<u64>, child: &Context) {
611    ///     let mut xs = vec![187; 410757864530];
612    ///     xs[0] += *x;
613    ///
614    ///     // manually drop here to avoid leaks
615    ///     drop(x);
616    ///     drop(xs);
617    ///
618    ///     unsafe { Context::set(child); }
619    ///     // implicit `drop(x)` and `drop(xs)` here never get called
620    /// }
621    /// ```
622    #[inline]
623    pub unsafe fn set(to: &Context) -> ! {
624        lucet_context_set(to as *const Context);
625    }
626
627    /// Like `set`, but also manages the return from a signal handler.
628    ///
629    /// TODO: the return type of this function should really be `Result<!, nix::Error>`, but using
630    /// `!` as a type like that is currently experimental.
631    #[inline]
632    pub unsafe fn set_from_signal(to: &Context) -> Result<(), nix::Error> {
633        signal::pthread_sigmask(signal::SigmaskHow::SIG_SETMASK, Some(&to.sigset), None)?;
634        Context::set(to)
635    }
636
637    /// Clear (zero) return values.
638    pub fn clear_retvals(&mut self) {
639        self.retvals_gp = [0; 2];
640        let zero = unsafe { _mm_setzero_ps() };
641        self.retval_fp = zero;
642    }
643
644    /// Get the general-purpose return value at index `idx`.
645    ///
646    /// If this method is called before the context has returned from its original entrypoint, the
647    /// result will be `0`.
648    pub fn get_retval_gp(&self, idx: usize) -> u64 {
649        self.retvals_gp[idx]
650    }
651
652    /// Get the floating point return value.
653    ///
654    /// If this method is called before the context has returned from its original entrypoint, the
655    /// result will be `0.0`.
656    pub fn get_retval_fp(&self) -> __m128 {
657        self.retval_fp
658    }
659
660    /// Get the return value as an `UntypedRetVal`.
661    ///
662    /// This combines the 0th general-purpose return value, and the single floating-point return value.
663    pub fn get_untyped_retval(&self) -> UntypedRetVal {
664        let gp = self.get_retval_gp(0);
665        let fp = self.get_retval_fp();
666        UntypedRetVal::new(gp, fp)
667    }
668
669    /// Put one of the first 8 floating-point arguments into a `Context` register.
670    ///
671    /// - `ix`: ABI floating-point argument number
672    /// - `arg`: argument value
673    fn bootstrap_fp_ix_arg(&mut self, ix: usize, arg: __m128) {
674        match ix {
675            0 => self.fpr.xmm0 = arg,
676            1 => self.fpr.xmm1 = arg,
677            2 => self.fpr.xmm2 = arg,
678            3 => self.fpr.xmm3 = arg,
679            4 => self.fpr.xmm4 = arg,
680            5 => self.fpr.xmm5 = arg,
681            6 => self.fpr.xmm6 = arg,
682            7 => self.fpr.xmm7 = arg,
683            _ => panic!("unexpected fp register index {}", ix),
684        }
685    }
686}
687
688/// Errors that may arise when working with contexts.
689#[derive(Debug, Error)]
690pub enum Error {
691    /// Raised when the bottom of the stack provided to `Context::init` is not 16-byte aligned
692    #[error("context initialized with unaligned stack")]
693    UnalignedStack,
694}
695
696/// Check whether the bottom (highest address) of the stack is 16-byte aligned, as required by the
697/// ABI.
698fn stack_is_aligned(stack: &[u64]) -> bool {
699    let size = stack.len();
700    let last_elt_addr = &stack[size - 1] as *const u64 as usize;
701    let bottom_addr = last_elt_addr + mem::size_of::<u64>();
702    bottom_addr % 16 == 0
703}
704
705extern "C" {
706    /// Bootstraps arguments and calls the entrypoint via returning; implemented in assembly.
707    ///
708    /// Loads general-purpose arguments from the callee-saved registers in a `Context` to the
709    /// appropriate argument registers for the AMD64 ABI, and then returns to the entrypoint.
710    fn lucet_context_bootstrap();
711
712    /// Stores return values into the parent context, and then swaps to it; implemented in assembly.
713    ///
714    /// This is where the entrypoint function returns to, so that we swap back to the parent on
715    /// return.
716    fn lucet_context_backstop();
717
718    /// Saves the current context and performs the context switch. Implemented in assembly.
719    fn lucet_context_swap(from: *mut Context, to: *mut Context);
720
721    /// Performs the context switch; implemented in assembly.
722    ///
723    /// Never returns because the current context is discarded.
724    fn lucet_context_set(to: *const Context) -> !;
725
726    /// Enables termination for the instance, after performing a context switch.
727    ///
728    /// Takes the guest return address as an argument as a consequence of implementation details,
729    /// see `Instance::swap_and_return` for more.
730    pub(crate) fn lucet_context_activate();
731}