#![allow(improper_ctypes)]
#[cfg(test)]
mod tests;
use crate::instance::Instance;
use crate::val::{val_to_reg, val_to_stack, RegVal, UntypedRetVal, Val};
use nix;
use nix::sys::signal;
use std::arch::x86_64::{__m128, _mm_setzero_ps};
use std::ptr::NonNull;
use std::{mem, ptr};
use thiserror::Error;
#[repr(C)]
pub(crate) struct GpRegs {
rbx: u64,
pub(crate) rsp: u64,
rbp: u64,
pub(crate) rdi: u64,
r12: u64,
r13: u64,
r14: u64,
r15: u64,
pub(crate) rsi: u64,
}
impl GpRegs {
fn new() -> Self {
GpRegs {
rbx: 0,
rsp: 0,
rbp: 0,
rdi: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
rsi: 0,
}
}
}
#[repr(C)]
struct FpRegs {
xmm0: __m128,
xmm1: __m128,
xmm2: __m128,
xmm3: __m128,
xmm4: __m128,
xmm5: __m128,
xmm6: __m128,
xmm7: __m128,
}
impl FpRegs {
fn new() -> Self {
let zero = unsafe { _mm_setzero_ps() };
FpRegs {
xmm0: zero,
xmm1: zero,
xmm2: zero,
xmm3: zero,
xmm4: zero,
xmm5: zero,
xmm6: zero,
xmm7: zero,
}
}
}
#[repr(C, align(64))]
pub struct Context {
pub(crate) gpr: GpRegs,
fpr: FpRegs,
retvals_gp: [u64; 2],
retval_fp: __m128,
parent_ctx: *mut Context,
backstop_callback: *const unsafe extern "C" fn(*mut Instance),
backstop_data: *mut Instance,
sigset: signal::SigSet,
}
impl Context {
pub fn new() -> Self {
Context {
gpr: GpRegs::new(),
fpr: FpRegs::new(),
retvals_gp: [0; 2],
retval_fp: unsafe { _mm_setzero_ps() },
parent_ctx: ptr::null_mut(),
backstop_callback: Context::default_backstop_callback as *const _,
backstop_data: ptr::null_mut(),
sigset: signal::SigSet::empty(),
}
}
}
#[repr(C)]
pub struct ContextHandle {
internal: NonNull<Context>,
}
impl Drop for ContextHandle {
fn drop(&mut self) {
unsafe {
Box::from_raw(self.internal.as_ptr());
}
}
}
impl std::ops::Deref for ContextHandle {
type Target = Context;
fn deref(&self) -> &Self::Target {
unsafe { self.internal.as_ref() }
}
}
impl std::ops::DerefMut for ContextHandle {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.internal.as_mut() }
}
}
impl ContextHandle {
pub fn new() -> Self {
let internal = NonNull::new(Box::into_raw(Box::new(Context::new())))
.expect("Box::into_raw should never return NULL");
ContextHandle { internal }
}
pub fn create_and_init(
stack: &mut [u64],
fptr: usize,
args: &[Val],
) -> Result<ContextHandle, Error> {
let mut child = ContextHandle::new();
Context::init(stack, &mut child, fptr, args)?;
Ok(child)
}
}
struct CallStackBuilder<'a> {
offset: usize,
stack: &'a mut [u64],
}
impl<'a> CallStackBuilder<'a> {
pub fn new(stack: &'a mut [u64]) -> Self {
CallStackBuilder { offset: 0, stack }
}
fn push(&mut self, val: u64) {
self.offset += 1;
self.stack[self.stack.len() - self.offset] = val;
}
fn store_args(&mut self, args: &[u64]) {
let items_end = args.len() + self.offset;
if items_end % 2 == 1 {
self.push(0);
}
for arg in args.iter().rev() {
self.push(*arg);
}
}
fn offset(&self) -> usize {
self.offset
}
fn into_inner(self) -> (&'a mut [u64], usize) {
(self.stack, self.offset)
}
}
impl Context {
pub fn init(
stack: &mut [u64],
child: &mut Context,
fptr: usize,
args: &[Val],
) -> Result<(), Error> {
Context::init_with_callback(
stack,
child,
Context::default_backstop_callback,
ptr::null_mut(),
fptr,
args,
)
}
extern "C" fn default_backstop_callback(_: *mut Instance) {}
pub fn init_with_callback(
stack: &mut [u64],
child: &mut Context,
backstop_callback: unsafe extern "C" fn(*mut Instance),
backstop_data: *mut Instance,
fptr: usize,
args: &[Val],
) -> Result<(), Error> {
if !stack_is_aligned(stack) {
return Err(Error::UnalignedStack);
}
if backstop_callback != Context::default_backstop_callback {
child.backstop_callback = backstop_callback as *const _;
child.backstop_data = backstop_data;
}
let mut gp_args_ix = 0;
let mut fp_args_ix = 0;
let mut gp_regs_values = [0u64; 6];
let mut spilled_args = vec![];
for arg in args {
match val_to_reg(arg) {
RegVal::GpReg(v) => {
if gp_args_ix >= 6 {
spilled_args.push(val_to_stack(arg));
} else {
gp_regs_values[gp_args_ix] = v;
gp_args_ix += 1;
}
}
RegVal::FpReg(v) => {
if fp_args_ix >= 8 {
spilled_args.push(val_to_stack(arg));
} else {
child.bootstrap_fp_ix_arg(fp_args_ix, v);
fp_args_ix += 1;
}
}
}
}
let mut stack_builder = CallStackBuilder::new(stack);
stack_builder.store_args(spilled_args.as_slice());
assert_eq!(
stack_builder.offset() % 2,
0,
"incorrect alignment for guest call frame"
);
stack_builder.push(lucet_context_backstop as u64);
stack_builder.push(fptr as u64);
for arg in gp_regs_values.iter() {
stack_builder.push(*arg);
}
stack_builder.push(lucet_context_bootstrap as u64);
let (stack, stack_start) = stack_builder.into_inner();
child.gpr.rsp = &mut stack[stack.len() - stack_start] as *mut u64 as u64;
child.gpr.rbp = child as *const Context as u64;
signal::pthread_sigmask(
signal::SigmaskHow::SIG_SETMASK,
None,
Some(&mut child.sigset),
)
.expect("pthread_sigmask could not be retrieved");
Ok(())
}
#[inline]
pub unsafe fn swap(from: &mut Context, to: &mut Context) {
to.parent_ctx = from;
lucet_context_swap(from as *mut _, to as *mut _);
}
#[inline]
pub unsafe fn set(to: &Context) -> ! {
lucet_context_set(to as *const Context);
}
#[inline]
pub unsafe fn set_from_signal(to: &Context) -> Result<(), nix::Error> {
signal::pthread_sigmask(signal::SigmaskHow::SIG_SETMASK, Some(&to.sigset), None)?;
Context::set(to)
}
pub fn clear_retvals(&mut self) {
self.retvals_gp = [0; 2];
let zero = unsafe { _mm_setzero_ps() };
self.retval_fp = zero;
}
pub fn get_retval_gp(&self, idx: usize) -> u64 {
self.retvals_gp[idx]
}
pub fn get_retval_fp(&self) -> __m128 {
self.retval_fp
}
pub fn get_untyped_retval(&self) -> UntypedRetVal {
let gp = self.get_retval_gp(0);
let fp = self.get_retval_fp();
UntypedRetVal::new(gp, fp)
}
fn bootstrap_fp_ix_arg(&mut self, ix: usize, arg: __m128) {
match ix {
0 => self.fpr.xmm0 = arg,
1 => self.fpr.xmm1 = arg,
2 => self.fpr.xmm2 = arg,
3 => self.fpr.xmm3 = arg,
4 => self.fpr.xmm4 = arg,
5 => self.fpr.xmm5 = arg,
6 => self.fpr.xmm6 = arg,
7 => self.fpr.xmm7 = arg,
_ => panic!("unexpected fp register index {}", ix),
}
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("context initialized with unaligned stack")]
UnalignedStack,
}
fn stack_is_aligned(stack: &[u64]) -> bool {
let size = stack.len();
let last_elt_addr = &stack[size - 1] as *const u64 as usize;
let bottom_addr = last_elt_addr + mem::size_of::<u64>();
bottom_addr % 16 == 0
}
extern "C" {
fn lucet_context_bootstrap();
fn lucet_context_backstop();
fn lucet_context_swap(from: *mut Context, to: *mut Context);
fn lucet_context_set(to: *const Context) -> !;
pub(crate) fn lucet_context_activate();
}