#![allow(non_snake_case, clippy::cast_sign_loss)]
use crate::sys::traphandlers::wasmtime_longjmp;
use crate::traphandlers::tls;
use mach2::exc::*;
use mach2::exception_types::*;
use mach2::kern_return::*;
use mach2::mach_init::*;
use mach2::mach_port::*;
use mach2::message::*;
use mach2::ndr::*;
use mach2::port::*;
use mach2::thread_act::*;
use mach2::thread_status::*;
use mach2::traps::*;
use std::mem;
use std::ptr::addr_of_mut;
use std::thread;
use wasmtime_environ::Trap;
static mut WASMTIME_PORT: mach_port_name_t = MACH_PORT_NULL;
static mut CHILD_OF_FORKED_PROCESS: bool = false;
pub unsafe fn platform_init() {
unsafe extern "C" fn child() {
CHILD_OF_FORKED_PROCESS = true;
}
let rc = libc::pthread_atfork(None, None, Some(child));
assert_eq!(rc, 0, "failed to configure `pthread_atfork` handler");
let me = mach_task_self();
let kret = mach_port_allocate(me, MACH_PORT_RIGHT_RECEIVE, addr_of_mut!(WASMTIME_PORT));
assert_eq!(kret, KERN_SUCCESS, "failed to allocate port");
let kret = mach_port_insert_right(me, WASMTIME_PORT, WASMTIME_PORT, MACH_MSG_TYPE_MAKE_SEND);
assert_eq!(kret, KERN_SUCCESS, "failed to insert right");
thread::spawn(|| handler_thread());
}
#[repr(C, packed(4))]
#[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
struct __Request__exception_raise_t {
Head: mach_msg_header_t,
msgh_body: mach_msg_body_t,
thread: mach_msg_port_descriptor_t,
task: mach_msg_port_descriptor_t,
NDR: NDR_record_t,
exception: exception_type_t,
codeCnt: mach_msg_type_number_t,
code: [i64; 2],
}
#[repr(C)]
#[allow(dead_code)]
#[derive(Debug)]
struct ExceptionRequest {
body: __Request__exception_raise_t,
trailer: mach_msg_trailer_t,
}
unsafe fn handler_thread() {
const EXCEPTION_MSG_ID: mach_msg_id_t = 2405;
loop {
let mut request: ExceptionRequest = mem::zeroed();
let kret = mach_msg(
&mut request.body.Head,
MACH_RCV_MSG,
0,
mem::size_of_val(&request) as u32,
WASMTIME_PORT,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL,
);
if kret != KERN_SUCCESS {
eprintln!("mach_msg failed with {} ({0:x})", kret);
libc::abort();
}
if request.body.Head.msgh_id != EXCEPTION_MSG_ID {
eprintln!("unexpected msg header id {}", request.body.Head.msgh_id);
libc::abort();
}
let reply_code = if handle_exception(&mut request) {
KERN_SUCCESS
} else {
KERN_FAILURE
};
let mut reply: __Reply__exception_raise_t = mem::zeroed();
reply.Head.msgh_bits =
MACH_MSGH_BITS(request.body.Head.msgh_bits & MACH_MSGH_BITS_REMOTE_MASK, 0);
reply.Head.msgh_size = mem::size_of_val(&reply) as u32;
reply.Head.msgh_remote_port = request.body.Head.msgh_remote_port;
reply.Head.msgh_local_port = MACH_PORT_NULL;
reply.Head.msgh_id = request.body.Head.msgh_id + 100;
reply.NDR = NDR_record;
reply.RetCode = reply_code;
mach_msg(
&mut reply.Head,
MACH_SEND_MSG,
mem::size_of_val(&reply) as u32,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL,
);
}
}
unsafe fn handle_exception(request: &mut ExceptionRequest) -> bool {
match request.body.exception as u32 {
EXC_BAD_ACCESS | EXC_BAD_INSTRUCTION | EXC_ARITHMETIC => {}
_ => return false,
}
let faulting_addr = if request.body.exception as u32 == EXC_BAD_ACCESS {
Some(request.body.code[1] as usize)
} else {
None
};
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
use mach2::structs::x86_thread_state64_t;
use mach2::thread_status::x86_THREAD_STATE64;
type ThreadState = x86_thread_state64_t;
let thread_state_flavor = x86_THREAD_STATE64;
let get_pc_and_fp = |state: &ThreadState| (
state.__rip as *const u8,
state.__rbp as usize,
);
let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
if state.__rsp % 16 == 0 {
state.__rsp -= 8;
*(state.__rsp as *mut u64) = state.__rip;
}
state.__rip = unwind as u64;
state.__rdi = pc as u64;
state.__rsi = fp as u64;
state.__rdx = fault1 as u64;
state.__rcx = fault2 as u64;
state.__r8 = trap as u64;
};
let mut thread_state = ThreadState::new();
} else if #[cfg(target_arch = "aarch64")] {
type ThreadState = mach2::structs::arm_thread_state64_t;
let thread_state_flavor = ARM_THREAD_STATE64;
let get_pc_and_fp = |state: &ThreadState| (
state.__pc as *const u8,
state.__fp as usize,
);
let resume = |state: &mut ThreadState, pc: usize, fp: usize, fault1: usize, fault2: usize, trap: Trap| {
state.__lr = pc as u64;
state.__x[0] = pc as u64;
state.__x[1] = fp as u64;
state.__x[2] = fault1 as u64;
state.__x[3] = fault2 as u64;
state.__x[4] = trap as u64;
state.__pc = unwind as u64;
};
let mut thread_state = mem::zeroed::<ThreadState>();
} else {
compile_error!("unsupported target architecture");
}
}
let origin_thread = request.body.thread.name;
let mut thread_state_count = ThreadState::count();
let kret = thread_get_state(
origin_thread,
thread_state_flavor,
&mut thread_state as *mut ThreadState as *mut u32,
&mut thread_state_count,
);
if kret != KERN_SUCCESS {
return false;
}
let (pc, fp) = get_pc_and_fp(&thread_state);
let trap = match crate::traphandlers::GET_WASM_TRAP(pc as usize) {
Some(trap) => trap,
None => return false,
};
let (fault1, fault2) = match faulting_addr {
None => (0, 0),
Some(addr) => (1, addr),
};
resume(&mut thread_state, pc as usize, fp, fault1, fault2, trap);
let kret = thread_set_state(
origin_thread,
thread_state_flavor,
&mut thread_state as *mut ThreadState as *mut u32,
thread_state_count,
);
kret == KERN_SUCCESS
}
unsafe extern "C" fn unwind(
wasm_pc: *const u8,
wasm_fp: usize,
fault1: usize,
fault2: usize,
trap: u8,
) -> ! {
let jmp_buf = tls::with(|state| {
let state = state.unwrap();
let faulting_addr = match fault1 {
0 => None,
_ => Some(fault2),
};
let trap = Trap::from_u8(trap).unwrap();
state.set_jit_trap(wasm_pc, wasm_fp, faulting_addr, trap);
state.take_jmp_buf()
});
debug_assert!(!jmp_buf.is_null());
wasmtime_longjmp(jmp_buf);
}
#[cold]
pub fn lazy_per_thread_init() {
unsafe {
assert!(
!CHILD_OF_FORKED_PROCESS,
"cannot use Wasmtime in a forked process when mach ports are \
configured, see `Config::macos_use_mach_ports`"
);
assert!(WASMTIME_PORT != MACH_PORT_NULL);
let this_thread = mach_thread_self();
let kret = thread_set_exception_ports(
this_thread,
EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC,
WASMTIME_PORT,
(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES) as exception_behavior_t,
THREAD_STATE_NONE,
);
mach_port_deallocate(mach_task_self(), this_thread);
assert_eq!(kret, KERN_SUCCESS, "failed to set thread exception port");
}
}