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}