use crate::prelude::*;
use crate::runtime::vm::vmcontext::VMArrayCallNative;
use crate::runtime::vm::{tls, TrapRegisters, TrapTest, VMContext, VMOpaqueContext};
use crate::{Engine, ValRaw};
use core::ptr::NonNull;
use pulley_interpreter::interp::{DoneReason, RegType, TrapKind, Val, Vm, XRegVal};
use pulley_interpreter::{FReg, Reg, XReg};
use wasmtime_environ::{BuiltinFunctionIndex, HostCall, Trap};
#[repr(transparent)]
pub struct Interpreter {
pulley: Box<Vm>,
}
impl Interpreter {
pub fn new(engine: &Engine) -> Interpreter {
let ret = Interpreter {
pulley: Box::new(Vm::with_stack(engine.config().max_wasm_stack)),
};
engine.profiler().register_interpreter(&ret);
ret
}
pub fn as_interpreter_ref(&mut self) -> InterpreterRef<'_> {
InterpreterRef(&mut self.pulley)
}
pub fn pulley(&self) -> &Vm {
&self.pulley
}
}
#[repr(transparent)]
pub struct InterpreterRef<'a>(&'a mut Vm);
#[derive(Clone, Copy)]
struct Setjmp {
xregs: [u64; 16],
fregs: [f64; 16],
fp: *mut u8,
lr: *mut u8,
}
impl InterpreterRef<'_> {
pub unsafe fn call(
mut self,
mut bytecode: NonNull<u8>,
callee: NonNull<VMOpaqueContext>,
caller: NonNull<VMOpaqueContext>,
args_and_results: NonNull<[ValRaw]>,
) -> bool {
let args = [
XRegVal::new_ptr(callee.as_ptr()).into(),
XRegVal::new_ptr(caller.as_ptr()).into(),
XRegVal::new_ptr(args_and_results.cast::<u8>().as_ptr()).into(),
XRegVal::new_u64(args_and_results.len() as u64).into(),
];
let setjmp = self.setjmp();
let old_lr = self.0.call_start(&args);
let ret = loop {
match self.0.call_run(bytecode) {
DoneReason::ReturnToHost(()) => {
match self.0.call_end(old_lr, [RegType::XReg]).next().unwrap() {
#[allow(
clippy::cast_possible_truncation,
reason = "intentionally reading the lower bits only"
)]
Val::XReg(xreg) => break (xreg.get_u32() as u8) != 0,
_ => unreachable!(),
}
}
DoneReason::CallIndirectHost { id, resume } => {
if u32::from(id) == HostCall::Builtin(BuiltinFunctionIndex::raise()).index() {
self.longjmp(setjmp);
break false;
} else {
self.call_indirect_host(id);
bytecode = resume;
}
}
DoneReason::Trap { pc, kind } => {
self.trap(pc, kind, setjmp);
break false;
}
}
};
if cfg!(debug_assertions) {
for (i, reg) in callee_save_xregs() {
assert!(self.0[reg].get_u64() == setjmp.xregs[i]);
}
for (i, reg) in callee_save_fregs() {
assert!(self.0[reg].get_f64().to_bits() == setjmp.fregs[i].to_bits());
}
assert!(self.0.fp() == setjmp.fp);
assert!(self.0.lr() == setjmp.lr);
}
ret
}
fn trap(&mut self, pc: NonNull<u8>, kind: Option<TrapKind>, setjmp: Setjmp) {
let regs = TrapRegisters {
pc: pc.as_ptr() as usize,
fp: self.0.fp() as usize,
};
tls::with(|s| {
let s = s.unwrap();
match kind {
Some(kind) => {
let trap = match kind {
TrapKind::IntegerOverflow => Trap::IntegerOverflow,
TrapKind::DivideByZero => Trap::IntegerDivisionByZero,
TrapKind::BadConversionToInteger => Trap::BadConversionToInteger,
};
s.set_jit_trap(regs, None, trap);
}
None => {
match s.test_if_trap(regs, None, |_| false) {
TrapTest::NotWasm => {
panic!("pulley trap at {pc:?} without trap code registered")
}
#[cfg(has_host_compiler_backend)]
TrapTest::HandledByEmbedder => unreachable!(),
TrapTest::Trap { .. } => {}
}
}
}
});
self.longjmp(setjmp);
}
fn setjmp(&self) -> Setjmp {
let mut xregs = [0; 16];
let mut fregs = [0.0; 16];
for (i, reg) in callee_save_xregs() {
xregs[i] = self.0[reg].get_u64();
}
for (i, reg) in callee_save_fregs() {
fregs[i] = self.0[reg].get_f64();
}
Setjmp {
xregs,
fregs,
fp: self.0.fp(),
lr: self.0.lr(),
}
}
fn longjmp(&mut self, setjmp: Setjmp) {
let Setjmp {
xregs,
fregs,
fp,
lr,
} = setjmp;
unsafe {
for (i, reg) in callee_save_xregs() {
self.0[reg].set_u64(xregs[i]);
}
for (i, reg) in callee_save_fregs() {
self.0[reg].set_f64(fregs[i]);
}
self.0.set_fp(fp);
self.0.set_lr(lr);
}
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
unused_macro_rules,
reason = "macro-generated code"
)]
#[cfg_attr(
not(feature = "component-model"),
expect(unused_macro_rules, reason = "macro-code")
)]
unsafe fn call_indirect_host(&mut self, id: u8) {
let id = u32::from(id);
let fnptr = self.0[XReg::x0].get_ptr();
let mut arg_reg = 1;
macro_rules! call {
(@builtin($($param:ident),*) $(-> $result:ident)?) => {{
type T = unsafe extern "C" fn($(call!(@ty $param)),*) $(-> call!(@ty $result))?;
call!(@host T($($param),*) $(-> $result)?);
}};
(@host $ty:ident($($param:ident),*) $(-> $result:ident)?) => {{
union GetNative {
fnptr: *mut u8,
host: $ty,
}
let host = GetNative { fnptr }.host;
let ret = host($({
let reg = XReg::new(arg_reg).unwrap();
arg_reg += 1;
call!(@get $param reg)
}),*);
let _ = arg_reg;
$(
let dst = XReg::x0;
call!(@set $result dst ret);
)?
let _ = ret;
return;
}};
(@ty bool) => (bool);
(@ty u8) => (u8);
(@ty u32) => (u32);
(@ty i32) => (i32);
(@ty u64) => (u64);
(@ty i64) => (i64);
(@ty vmctx) => (*mut VMContext);
(@ty pointer) => (*mut u8);
(@ty ptr_u8) => (*mut u8);
(@ty ptr_u16) => (*mut u16);
(@ty ptr_size) => (*mut usize);
(@ty size) => (usize);
(@get u8 $reg:ident) => (self.0[$reg].get_i32() as u8);
(@get u32 $reg:ident) => (self.0[$reg].get_u32());
(@get u64 $reg:ident) => (self.0[$reg].get_u64());
(@get vmctx $reg:ident) => (self.0[$reg].get_ptr());
(@get pointer $reg:ident) => (self.0[$reg].get_ptr());
(@get ptr $reg:ident) => (self.0[$reg].get_ptr());
(@get nonnull $reg:ident) => (NonNull::new(self.0[$reg].get_ptr()).unwrap());
(@get ptr_u8 $reg:ident) => (self.0[$reg].get_ptr());
(@get ptr_u16 $reg:ident) => (self.0[$reg].get_ptr());
(@get ptr_size $reg:ident) => (self.0[$reg].get_ptr());
(@get size $reg:ident) => (self.0[$reg].get_ptr::<u8>() as usize);
(@set bool $reg:ident $val:ident) => (self.0[$reg].set_i32(i32::from($val)));
(@set u32 $reg:ident $val:ident) => (self.0[$reg].set_u32($val));
(@set u64 $reg:ident $val:ident) => (self.0[$reg].set_u64($val));
(@set pointer $reg:ident $val:ident) => (self.0[$reg].set_ptr($val));
(@set size $reg:ident $val:ident) => (self.0[$reg].set_ptr($val as *mut u8));
}
if id == const { HostCall::ArrayCall.index() } {
call!(@host VMArrayCallNative(nonnull, nonnull, nonnull, size) -> bool);
}
macro_rules! core {
(
$(
$( #[cfg($attr:meta)] )?
$name:ident($($pname:ident: $param:ident ),* ) $(-> $result:ident)?;
)*
) => {
$(
$( #[cfg($attr)] )?
if id == const { HostCall::Builtin(BuiltinFunctionIndex::$name()).index() } {
call!(@builtin($($param),*) $(-> $result)?);
}
)*
}
}
wasmtime_environ::foreach_builtin_function!(core);
#[cfg(feature = "component-model")]
{
use crate::runtime::vm::component::VMLoweringCallee;
use wasmtime_environ::component::ComponentBuiltinFunctionIndex;
if id == const { HostCall::ComponentLowerImport.index() } {
call!(@host VMLoweringCallee(nonnull, nonnull, u32, nonnull, ptr, ptr, u8, u8, nonnull, size) -> bool);
}
macro_rules! component {
(
$(
$name:ident($($pname:ident: $param:ident ),* ) $(-> $result:ident)?;
)*
) => {
$(
if id == const { HostCall::ComponentBuiltin(ComponentBuiltinFunctionIndex::$name()).index() } {
call!(@builtin($($param),*) $(-> $result)?);
}
)*
}
}
wasmtime_environ::foreach_builtin_component_function!(component);
}
unreachable!()
}
}
fn callee_save_xregs() -> impl Iterator<Item = (usize, XReg)> {
(0..16).map(|i| (i.into(), XReg::new(i + 16).unwrap()))
}
fn callee_save_fregs() -> impl Iterator<Item = (usize, FReg)> {
(0..16).map(|i| (i.into(), FReg::new(i + 16).unwrap()))
}