use core::mem::{align_of, size_of};
use std::cell::Cell;
use std::fmt;
use std::panic::{AssertUnwindSafe, catch_unwind};
use crate::{Completion, CompletionTag, ShadowFrame, Value};
pub const HELPER_COUNT: u32 = 32;
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum NativeHelper {
LoadConstant = 0,
Unary = 1,
Binary = 2,
CreateObject = 3,
CreateArray = 4,
CreateClosure = 5,
GetProperty = 6,
SetProperty = 7,
DeleteProperty = 8,
Call = 9,
Construct = 10,
Import = 11,
Truthy = 12,
ResumeValue = 13,
DefineAccessor = 14,
LoadGlobal = 15,
StoreGlobal = 16,
TypeOfGlobal = 17,
LoadThis = 18,
LoadArguments = 19,
LoadNewTarget = 20,
ArrayPush = 21,
ArrayExtend = 22,
ObjectSpread = 23,
SetPrototype = 24,
CreatePrivateName = 25,
CreateRegExp = 26,
GetIterator = 27,
IteratorNext = 28,
Export = 29,
ConsumeFuel = 30,
CreateCell = 31,
}
impl NativeHelper {
#[must_use]
pub const fn symbol(self) -> &'static str {
match self {
NativeHelper::LoadConstant => "bamts_load_constant",
NativeHelper::Unary => "bamts_unary",
NativeHelper::Binary => "bamts_binary",
NativeHelper::CreateObject => "bamts_create_object",
NativeHelper::CreateArray => "bamts_create_array",
NativeHelper::CreateClosure => "bamts_create_closure",
NativeHelper::GetProperty => "bamts_get_property",
NativeHelper::SetProperty => "bamts_set_property",
NativeHelper::DeleteProperty => "bamts_delete_property",
NativeHelper::Call => "bamts_call",
NativeHelper::Construct => "bamts_construct",
NativeHelper::Import => "bamts_import",
NativeHelper::Truthy => "bamts_truthy",
NativeHelper::ResumeValue => "bamts_resume_value",
NativeHelper::DefineAccessor => "bamts_define_accessor",
NativeHelper::LoadGlobal => "bamts_load_global",
NativeHelper::StoreGlobal => "bamts_store_global",
NativeHelper::TypeOfGlobal => "bamts_typeof_global",
NativeHelper::LoadThis => "bamts_load_this",
NativeHelper::LoadArguments => "bamts_load_arguments",
NativeHelper::LoadNewTarget => "bamts_load_new_target",
NativeHelper::ArrayPush => "bamts_array_push",
NativeHelper::ArrayExtend => "bamts_array_extend",
NativeHelper::ObjectSpread => "bamts_object_spread",
NativeHelper::SetPrototype => "bamts_set_prototype",
NativeHelper::CreatePrivateName => "bamts_create_private_name",
NativeHelper::CreateRegExp => "bamts_create_regexp",
NativeHelper::GetIterator => "bamts_get_iterator",
NativeHelper::IteratorNext => "bamts_iterator_next",
NativeHelper::Export => "bamts_export",
NativeHelper::ConsumeFuel => "bamts_consume_fuel",
NativeHelper::CreateCell => "bamts_create_cell",
}
}
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[must_use]
pub const fn from_u32(index: u32) -> Option<NativeHelper> {
match index {
0 => Some(NativeHelper::LoadConstant),
1 => Some(NativeHelper::Unary),
2 => Some(NativeHelper::Binary),
3 => Some(NativeHelper::CreateObject),
4 => Some(NativeHelper::CreateArray),
5 => Some(NativeHelper::CreateClosure),
6 => Some(NativeHelper::GetProperty),
7 => Some(NativeHelper::SetProperty),
8 => Some(NativeHelper::DeleteProperty),
9 => Some(NativeHelper::Call),
10 => Some(NativeHelper::Construct),
11 => Some(NativeHelper::Import),
12 => Some(NativeHelper::Truthy),
13 => Some(NativeHelper::ResumeValue),
14 => Some(NativeHelper::DefineAccessor),
15 => Some(NativeHelper::LoadGlobal),
16 => Some(NativeHelper::StoreGlobal),
17 => Some(NativeHelper::TypeOfGlobal),
18 => Some(NativeHelper::LoadThis),
19 => Some(NativeHelper::LoadArguments),
20 => Some(NativeHelper::LoadNewTarget),
21 => Some(NativeHelper::ArrayPush),
22 => Some(NativeHelper::ArrayExtend),
23 => Some(NativeHelper::ObjectSpread),
24 => Some(NativeHelper::SetPrototype),
25 => Some(NativeHelper::CreatePrivateName),
26 => Some(NativeHelper::CreateRegExp),
27 => Some(NativeHelper::GetIterator),
28 => Some(NativeHelper::IteratorNext),
29 => Some(NativeHelper::Export),
30 => Some(NativeHelper::ConsumeFuel),
31 => Some(NativeHelper::CreateCell),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HelperCall {
LoadConstant { const_id: u32 },
Unary { op: u32, operand: Value },
Binary { op: u32, left: Value, right: Value },
CreateObject,
CreateArray,
CreateCell,
CreateClosure { function_id: u32, captures: Value },
GetProperty { object: Value, key: Value },
SetProperty {
object: Value,
key: Value,
value: Value,
},
DeleteProperty { object: Value, key: Value },
Call {
callee: Value,
this_value: Value,
arguments: Value,
},
Construct { callee: Value, arguments: Value },
Import { specifier: u32 },
Truthy { value: Value },
ResumeValue,
DefineAccessor {
object: Value,
key: Value,
accessor: Value,
kind: u32,
},
LoadGlobal { name: u32 },
StoreGlobal { name: u32, value: Value },
TypeOfGlobal { name: u32 },
LoadThis,
LoadArguments,
LoadNewTarget,
ArrayPush { array: Value, value: Value },
ArrayExtend { array: Value, iterable: Value },
ObjectSpread { target: Value, source: Value },
SetPrototype { object: Value, prototype: Value },
CreatePrivateName { description: u32 },
CreateRegExp { pattern: u32, flags: u32 },
GetIterator { src: Value, kind: u32 },
IteratorNext {
iterator: Value,
done_reg: u32,
value_reg: u32,
},
Export { name: u32, src: Value },
ConsumeFuel { amount: u32 },
}
impl HelperCall {
#[must_use]
pub const fn helper(&self) -> NativeHelper {
match self {
HelperCall::LoadConstant { .. } => NativeHelper::LoadConstant,
HelperCall::Unary { .. } => NativeHelper::Unary,
HelperCall::Binary { .. } => NativeHelper::Binary,
HelperCall::CreateObject => NativeHelper::CreateObject,
HelperCall::CreateArray => NativeHelper::CreateArray,
HelperCall::CreateCell => NativeHelper::CreateCell,
HelperCall::CreateClosure { .. } => NativeHelper::CreateClosure,
HelperCall::GetProperty { .. } => NativeHelper::GetProperty,
HelperCall::SetProperty { .. } => NativeHelper::SetProperty,
HelperCall::DeleteProperty { .. } => NativeHelper::DeleteProperty,
HelperCall::Call { .. } => NativeHelper::Call,
HelperCall::Construct { .. } => NativeHelper::Construct,
HelperCall::Import { .. } => NativeHelper::Import,
HelperCall::Truthy { .. } => NativeHelper::Truthy,
HelperCall::ResumeValue => NativeHelper::ResumeValue,
HelperCall::DefineAccessor { .. } => NativeHelper::DefineAccessor,
HelperCall::LoadGlobal { .. } => NativeHelper::LoadGlobal,
HelperCall::StoreGlobal { .. } => NativeHelper::StoreGlobal,
HelperCall::TypeOfGlobal { .. } => NativeHelper::TypeOfGlobal,
HelperCall::LoadThis => NativeHelper::LoadThis,
HelperCall::LoadArguments => NativeHelper::LoadArguments,
HelperCall::LoadNewTarget => NativeHelper::LoadNewTarget,
HelperCall::ArrayPush { .. } => NativeHelper::ArrayPush,
HelperCall::ArrayExtend { .. } => NativeHelper::ArrayExtend,
HelperCall::ObjectSpread { .. } => NativeHelper::ObjectSpread,
HelperCall::SetPrototype { .. } => NativeHelper::SetPrototype,
HelperCall::CreatePrivateName { .. } => NativeHelper::CreatePrivateName,
HelperCall::CreateRegExp { .. } => NativeHelper::CreateRegExp,
HelperCall::GetIterator { .. } => NativeHelper::GetIterator,
HelperCall::IteratorNext { .. } => NativeHelper::IteratorNext,
HelperCall::Export { .. } => NativeHelper::Export,
HelperCall::ConsumeFuel { .. } => NativeHelper::ConsumeFuel,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HelperResult {
pub tag: CompletionTag,
pub value: Value,
}
impl HelperResult {
#[inline]
#[must_use]
pub const fn normal(value: Value) -> HelperResult {
HelperResult {
tag: CompletionTag::Normal,
value,
}
}
#[inline]
#[must_use]
pub const fn throw(value: Value) -> HelperResult {
HelperResult {
tag: CompletionTag::Throw,
value,
}
}
}
pub const TRAP_MISSING_NATIVE_OPS: u32 = 0x1000;
pub const TRAP_INVALID_FRAME: u32 = 0x1001;
pub const TRAP_PANIC: u32 = 0x1002;
pub const TRAP_INVALID_REGISTER: u32 = 0x1003;
pub const TRAP_INVALID_COMPLETION_TAG: u32 = 0x1004;
pub struct NativeFrame<'a> {
frame: &'a mut ShadowFrame,
handles: &'a mut [Value],
}
impl<'a> NativeFrame<'a> {
#[must_use]
pub fn new(frame: &'a mut ShadowFrame, handles: &'a mut [Value]) -> Option<NativeFrame<'a>> {
let len = u16::try_from(handles.len()).ok()?;
if frame.handle_len != len {
return None;
}
if !handles.is_empty() && !core::ptr::eq(frame.handles, handles.as_mut_ptr()) {
return None;
}
Some(NativeFrame { frame, handles })
}
#[must_use]
pub unsafe fn from_raw(frame: *mut ShadowFrame) -> Option<NativeFrame<'a>> {
if frame.is_null() || !frame.addr().is_multiple_of(align_of::<ShadowFrame>()) {
return None;
}
let len = unsafe { core::ptr::addr_of!((*frame).handle_len).read() as usize };
let handles_ptr = unsafe { core::ptr::addr_of!((*frame).handles).read() };
if len != 0 {
if handles_ptr.is_null() || !handles_ptr.addr().is_multiple_of(align_of::<Value>()) {
return None;
}
let header_start = frame.addr();
let header_end = header_start.checked_add(size_of::<ShadowFrame>())?;
let handles_start = handles_ptr.addr();
let handles_end = handles_start.checked_add(len.checked_mul(size_of::<Value>())?)?;
if handles_start < header_end && header_start < handles_end {
return None;
}
}
let header: &'a mut ShadowFrame = unsafe { &mut *frame };
let handles: &'a mut [Value] = if len == 0 {
&mut []
} else {
unsafe { core::slice::from_raw_parts_mut(handles_ptr, len) }
};
Some(NativeFrame {
frame: header,
handles,
})
}
#[inline]
#[must_use]
pub fn handle_len(&self) -> u32 {
u32::from(self.frame.handle_len)
}
#[inline]
#[must_use]
pub fn module_id(&self) -> u32 {
self.frame.module_id
}
#[inline]
#[must_use]
pub fn pc(&self) -> u32 {
self.frame.bytecode_pc
}
#[inline]
pub fn set_resume(&mut self, token: u32) {
self.frame.bytecode_pc = token;
}
#[inline]
#[must_use]
pub fn registers(&self) -> &[Value] {
self.handles
}
#[inline]
#[must_use]
pub fn registers_mut(&mut self) -> &mut [Value] {
self.handles
}
#[inline]
#[must_use]
pub fn register(&self, index: u32) -> Value {
self.handles[index as usize]
}
#[inline]
pub fn set_register(&mut self, index: u32, value: Value) {
self.handles[index as usize] = value;
}
#[inline]
#[must_use]
pub fn try_register(&self, index: u32) -> Option<Value> {
self.handles.get(index as usize).copied()
}
#[inline]
pub fn try_set_register(&mut self, index: u32, value: Value) -> bool {
match self.handles.get_mut(index as usize) {
Some(slot) => {
*slot = value;
true
}
None => false,
}
}
#[inline]
#[must_use]
pub fn previous(&self) -> *mut ShadowFrame {
self.frame.previous
}
}
unsafe fn frame_from_raw<'a>(frame: *mut ShadowFrame) -> Option<NativeFrame<'a>> {
unsafe { NativeFrame::from_raw(frame) }
}
pub trait NativeOps {
fn truthy(&self, frame: &mut NativeFrame<'_>, value: Value) -> bool;
fn dispatch(&self, frame: &mut NativeFrame<'_>, call: HelperCall) -> HelperResult;
}
type ErasedOps = *const (dyn NativeOps + 'static);
thread_local! {
static CURRENT_OPS: Cell<Option<ErasedOps>> = const { Cell::new(None) };
}
pub fn with_native_ops<R>(ops: &mut dyn NativeOps, body: impl FnOnce() -> R) -> R {
let ptr: *const dyn NativeOps = ops;
let erased: ErasedOps = unsafe { core::mem::transmute::<*const dyn NativeOps, ErasedOps>(ptr) };
let previous = CURRENT_OPS.with(|slot| slot.replace(Some(erased)));
let _guard = OpsGuard { previous };
body()
}
struct OpsGuard {
previous: Option<ErasedOps>,
}
impl Drop for OpsGuard {
fn drop(&mut self) {
CURRENT_OPS.with(|slot| slot.set(self.previous));
}
}
fn with_current_ops<R>(f: impl FnOnce(&dyn NativeOps) -> R) -> Option<R> {
let ptr = CURRENT_OPS.with(|slot| slot.get())?;
let ops: &dyn NativeOps = unsafe { &*ptr };
Some(f(ops))
}
enum HelperOutcome {
Done(HelperResult),
Trap(u32),
}
fn run_completion_helper(
frame: *mut ShadowFrame,
out: *mut Completion,
build: impl FnOnce(&mut NativeFrame<'_>, &dyn NativeOps) -> HelperResult,
) -> u32 {
if out.is_null() || !out.addr().is_multiple_of(align_of::<Completion>()) {
return CompletionTag::FatalTrap.as_u32();
}
let outcome = catch_unwind(AssertUnwindSafe(|| {
let mut native_frame = match unsafe { frame_from_raw(frame) } {
Some(view) => view,
None => return HelperOutcome::Trap(TRAP_INVALID_FRAME),
};
match with_current_ops(|ops| build(&mut native_frame, ops)) {
Some(result) => HelperOutcome::Done(result),
None => HelperOutcome::Trap(TRAP_MISSING_NATIVE_OPS),
}
}));
let (tag, value) = match outcome {
Ok(HelperOutcome::Done(result)) => (result.tag, result.value),
Ok(HelperOutcome::Trap(id)) => (CompletionTag::FatalTrap, Value::int32(id)),
Err(_) => (CompletionTag::FatalTrap, Value::int32(TRAP_PANIC)),
};
unsafe { core::ptr::write(out, Completion::new(value)) };
tag.as_u32()
}
#[inline]
fn dispatch_simple(frame: *mut ShadowFrame, out: *mut Completion, call: HelperCall) -> u32 {
run_completion_helper(frame, out, |native_frame, ops| {
ops.dispatch(native_frame, call)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_load_constant(
frame: *mut ShadowFrame,
const_id: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::LoadConstant { const_id })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_unary(
frame: *mut ShadowFrame,
op: u32,
operand: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::Unary {
op,
operand: Value::from_bits(operand),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_binary(
frame: *mut ShadowFrame,
op: u32,
left: u64,
right: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::Binary {
op,
left: Value::from_bits(left),
right: Value::from_bits(right),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_create_object(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
dispatch_simple(frame, out, HelperCall::CreateObject)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_create_array(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
dispatch_simple(frame, out, HelperCall::CreateArray)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_create_cell(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
dispatch_simple(frame, out, HelperCall::CreateCell)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_create_closure(
frame: *mut ShadowFrame,
function_id: u32,
captures: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::CreateClosure {
function_id,
captures: Value::from_bits(captures),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_get_property(
frame: *mut ShadowFrame,
object: u64,
key: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::GetProperty {
object: Value::from_bits(object),
key: Value::from_bits(key),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_set_property(
frame: *mut ShadowFrame,
object: u64,
key: u64,
value: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::SetProperty {
object: Value::from_bits(object),
key: Value::from_bits(key),
value: Value::from_bits(value),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_delete_property(
frame: *mut ShadowFrame,
object: u64,
key: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::DeleteProperty {
object: Value::from_bits(object),
key: Value::from_bits(key),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_call(
frame: *mut ShadowFrame,
callee: u64,
this_value: u64,
arguments: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::Call {
callee: Value::from_bits(callee),
this_value: Value::from_bits(this_value),
arguments: Value::from_bits(arguments),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_construct(
frame: *mut ShadowFrame,
callee: u64,
arguments: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::Construct {
callee: Value::from_bits(callee),
arguments: Value::from_bits(arguments),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_import(
frame: *mut ShadowFrame,
specifier: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::Import { specifier })
}
fn truthy_from_raw(frame: *mut ShadowFrame, value: u64) -> Option<bool> {
let mut native_frame = unsafe { frame_from_raw(frame) }?;
with_current_ops(|ops| ops.truthy(&mut native_frame, Value::from_bits(value)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_truthy(frame: *mut ShadowFrame, value: u64) -> u32 {
let outcome = catch_unwind(AssertUnwindSafe(|| truthy_from_raw(frame, value)));
match outcome {
Ok(Some(true)) => 1,
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_resume_value(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
dispatch_simple(frame, out, HelperCall::ResumeValue)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_consume_fuel(
frame: *mut ShadowFrame,
amount: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::ConsumeFuel { amount })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_define_accessor(
frame: *mut ShadowFrame,
object: u64,
key: u64,
accessor: u64,
kind: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::DefineAccessor {
object: Value::from_bits(object),
key: Value::from_bits(key),
accessor: Value::from_bits(accessor),
kind,
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_load_global(
frame: *mut ShadowFrame,
name: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::LoadGlobal { name })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_store_global(
frame: *mut ShadowFrame,
name: u32,
value: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::StoreGlobal {
name,
value: Value::from_bits(value),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_typeof_global(
frame: *mut ShadowFrame,
name: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::TypeOfGlobal { name })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_load_this(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
dispatch_simple(frame, out, HelperCall::LoadThis)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_load_arguments(
frame: *mut ShadowFrame,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::LoadArguments)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_load_new_target(
frame: *mut ShadowFrame,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::LoadNewTarget)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_array_push(
frame: *mut ShadowFrame,
array: u64,
value: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::ArrayPush {
array: Value::from_bits(array),
value: Value::from_bits(value),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_array_extend(
frame: *mut ShadowFrame,
array: u64,
iterable: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::ArrayExtend {
array: Value::from_bits(array),
iterable: Value::from_bits(iterable),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_object_spread(
frame: *mut ShadowFrame,
target: u64,
source: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::ObjectSpread {
target: Value::from_bits(target),
source: Value::from_bits(source),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_set_prototype(
frame: *mut ShadowFrame,
object: u64,
prototype: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::SetPrototype {
object: Value::from_bits(object),
prototype: Value::from_bits(prototype),
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_create_private_name(
frame: *mut ShadowFrame,
description: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::CreatePrivateName { description })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_create_regexp(
frame: *mut ShadowFrame,
pattern: u32,
flags: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(frame, out, HelperCall::CreateRegExp { pattern, flags })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_get_iterator(
frame: *mut ShadowFrame,
src: u64,
kind: u32,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::GetIterator {
src: Value::from_bits(src),
kind,
},
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_iterator_next(
frame: *mut ShadowFrame,
iterator: u64,
done_reg: u32,
value_reg: u32,
out: *mut Completion,
) -> u32 {
run_completion_helper(frame, out, |native_frame, ops| {
if done_reg >= native_frame.handle_len() || value_reg >= native_frame.handle_len() {
return HelperResult {
tag: CompletionTag::FatalTrap,
value: Value::int32(TRAP_INVALID_REGISTER),
};
}
ops.dispatch(
native_frame,
HelperCall::IteratorNext {
iterator: Value::from_bits(iterator),
done_reg,
value_reg,
},
)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn bamts_export(
frame: *mut ShadowFrame,
name: u32,
src: u64,
out: *mut Completion,
) -> u32 {
dispatch_simple(
frame,
out,
HelperCall::Export {
name,
src: Value::from_bits(src),
},
)
}
const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_load_constant; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 = bamts_unary; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, u64, *mut Completion) -> u32 =
bamts_binary; const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_create_object; const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_create_array; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 =
bamts_create_closure; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
bamts_get_property; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, u64, *mut Completion) -> u32 =
bamts_set_property; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
bamts_delete_property; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, u64, *mut Completion) -> u32 = bamts_call; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 = bamts_construct; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_import; const _: unsafe extern "C" fn(*mut ShadowFrame, u64) -> u32 = bamts_truthy; const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_resume_value; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, u64, u32, *mut Completion) -> u32 =
bamts_define_accessor; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_load_global; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 =
bamts_store_global; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_typeof_global; const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_load_this; const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_load_arguments; const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_load_new_target; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
bamts_array_push; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
bamts_array_extend; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
bamts_object_spread; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
bamts_set_prototype; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 =
bamts_create_private_name; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u32, *mut Completion) -> u32 =
bamts_create_regexp; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u32, *mut Completion) -> u32 =
bamts_get_iterator; const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u32, u32, *mut Completion) -> u32 =
bamts_iterator_next; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 = bamts_export; const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_consume_fuel;
pub type NativeEntryFn = unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32;
pub trait NativeEntryTable {
fn program_bytes(&self) -> &[u8];
fn invoke(
&self,
module_id: u32,
function_id: u32,
frame: &mut ShadowFrame,
out: &mut Completion,
) -> Result<CompletionTag, AbiError>;
}
unsafe fn call_native_entry(
entry: NativeEntryFn,
frame: &mut ShadowFrame,
out: &mut Completion,
) -> CompletionTag {
let raw = unsafe { entry(frame as *mut ShadowFrame, out as *mut Completion) };
match CompletionTag::from_u32(raw) {
Some(tag) => tag,
None => {
*out = Completion::new(Value::int32(TRAP_INVALID_COMPLETION_TAG));
CompletionTag::FatalTrap
}
}
}
pub const AOT_MAGIC: u64 = u64::from_le_bytes(*b"BMTSAOT1");
pub const AOT_ABI_VERSION: u32 = 3;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct UnitDescriptor {
pub function_id: u32,
pub module_id: u32,
pub entry: NativeEntryFn,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ProgramDescriptor {
pub magic: u64,
pub abi_version: u32,
pub flags: u32,
pub bytecode: *const u8,
pub bytecode_len: usize,
pub units: *const UnitDescriptor,
pub unit_count: usize,
pub entry_function: u32,
pub entry_module: u32,
}
pub struct LinkedProgram<'a> {
bytecode: &'a [u8],
units: &'a [UnitDescriptor],
entry_module: u32,
entry_function: u32,
}
impl<'a> LinkedProgram<'a> {
pub unsafe fn from_descriptor(
descriptor: &'a ProgramDescriptor,
) -> Result<LinkedProgram<'a>, AbiError> {
if size_of::<usize>() != 8 {
return Err(AbiError::UnsupportedPointerWidth {
bits: usize::BITS as u16,
});
}
if descriptor.magic != AOT_MAGIC {
return Err(AbiError::BadMagic {
found: descriptor.magic,
});
}
if descriptor.abi_version != AOT_ABI_VERSION {
return Err(AbiError::UnsupportedAbiVersion {
found: descriptor.abi_version,
});
}
if descriptor.flags != 0 {
return Err(AbiError::NonZeroFlags {
flags: descriptor.flags,
});
}
if descriptor.bytecode_len == 0 {
return Err(AbiError::EmptyBytecode);
}
if descriptor.bytecode.is_null() {
return Err(AbiError::NullBytecode);
}
if descriptor.bytecode_len > isize::MAX as usize {
return Err(AbiError::LengthOverflow);
}
if descriptor.unit_count == 0 {
return Err(AbiError::EmptyUnits);
}
if descriptor.units.is_null() {
return Err(AbiError::NullUnits);
}
let unit_bytes = descriptor
.unit_count
.checked_mul(size_of::<UnitDescriptor>())
.ok_or(AbiError::LengthOverflow)?;
if unit_bytes > isize::MAX as usize {
return Err(AbiError::LengthOverflow);
}
let bytecode =
unsafe { core::slice::from_raw_parts(descriptor.bytecode, descriptor.bytecode_len) };
let units = unsafe { core::slice::from_raw_parts(descriptor.units, descriptor.unit_count) };
let mut entry_present = false;
let mut previous = None;
for unit in units {
let identity = (unit.module_id, unit.function_id);
if identity == (descriptor.entry_module, descriptor.entry_function) {
entry_present = true;
}
if let Some((previous_module_id, previous_function_id)) = previous {
match identity.cmp(&(previous_module_id, previous_function_id)) {
core::cmp::Ordering::Less => {
return Err(AbiError::UnsortedUnits {
previous_module_id,
previous_function_id,
module_id: unit.module_id,
function_id: unit.function_id,
});
}
core::cmp::Ordering::Equal => {
return Err(AbiError::DuplicateFunction {
module_id: unit.module_id,
function_id: unit.function_id,
});
}
core::cmp::Ordering::Greater => {}
}
}
previous = Some(identity);
}
if !entry_present {
return Err(AbiError::EntryFunctionMissing {
module_id: descriptor.entry_module,
function_id: descriptor.entry_function,
});
}
Ok(LinkedProgram {
bytecode,
units,
entry_module: descriptor.entry_module,
entry_function: descriptor.entry_function,
})
}
#[inline]
#[must_use]
pub fn bytecode(&self) -> &'a [u8] {
self.bytecode
}
#[inline]
#[must_use]
pub fn units(&self) -> &'a [UnitDescriptor] {
self.units
}
#[inline]
#[must_use]
pub fn entry_module(&self) -> u32 {
self.entry_module
}
#[inline]
#[must_use]
pub fn entry_function(&self) -> u32 {
self.entry_function
}
#[must_use]
pub fn unit(&self, module_id: u32, function_id: u32) -> Option<&'a UnitDescriptor> {
self.units
.binary_search_by_key(&(module_id, function_id), |unit| {
(unit.module_id, unit.function_id)
})
.ok()
.map(|index| &self.units[index])
}
}
impl NativeEntryTable for LinkedProgram<'_> {
fn program_bytes(&self) -> &[u8] {
self.bytecode
}
fn invoke(
&self,
module_id: u32,
function_id: u32,
frame: &mut ShadowFrame,
out: &mut Completion,
) -> Result<CompletionTag, AbiError> {
let unit = self
.unit(module_id, function_id)
.ok_or(AbiError::UnknownFunction {
module_id,
function_id,
})?;
Ok(unsafe { call_native_entry(unit.entry, frame, out) })
}
}
#[cfg(target_pointer_width = "64")]
const _: () = {
use core::mem::{align_of, offset_of, size_of};
assert!(size_of::<UnitDescriptor>() == 16);
assert!(align_of::<UnitDescriptor>() == 8);
assert!(offset_of!(UnitDescriptor, function_id) == 0);
assert!(offset_of!(UnitDescriptor, module_id) == 4);
assert!(offset_of!(UnitDescriptor, entry) == 8);
assert!(size_of::<ProgramDescriptor>() == 56);
assert!(align_of::<ProgramDescriptor>() == 8);
assert!(offset_of!(ProgramDescriptor, magic) == 0);
assert!(offset_of!(ProgramDescriptor, abi_version) == 8);
assert!(offset_of!(ProgramDescriptor, flags) == 12);
assert!(offset_of!(ProgramDescriptor, bytecode) == 16);
assert!(offset_of!(ProgramDescriptor, bytecode_len) == 24);
assert!(offset_of!(ProgramDescriptor, units) == 32);
assert!(offset_of!(ProgramDescriptor, unit_count) == 40);
assert!(offset_of!(ProgramDescriptor, entry_function) == 48);
assert!(offset_of!(ProgramDescriptor, entry_module) == 52);
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AbiError {
UnsupportedPointerWidth {
bits: u16,
},
BadMagic {
found: u64,
},
UnsupportedAbiVersion {
found: u32,
},
NonZeroFlags {
flags: u32,
},
NullBytecode,
EmptyBytecode,
NullUnits,
EmptyUnits,
LengthOverflow,
UnsortedUnits {
previous_module_id: u32,
previous_function_id: u32,
module_id: u32,
function_id: u32,
},
DuplicateFunction {
module_id: u32,
function_id: u32,
},
EntryFunctionMissing {
module_id: u32,
function_id: u32,
},
UnknownFunction {
module_id: u32,
function_id: u32,
},
}
impl fmt::Display for AbiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AbiError::UnsupportedPointerWidth { bits } => {
write!(f, "AOT image requires a 64-bit target, not {bits}-bit")
}
AbiError::BadMagic { found } => {
write!(f, "AOT image magic {found:#018x} != {AOT_MAGIC:#018x}")
}
AbiError::UnsupportedAbiVersion { found } => {
write!(f, "AOT image ABI version {found} != {AOT_ABI_VERSION}")
}
AbiError::NonZeroFlags { flags } => {
write!(f, "AOT image flags {flags:#x} must be zero")
}
AbiError::NullBytecode => f.write_str("AOT image bytecode pointer is null"),
AbiError::EmptyBytecode => f.write_str("AOT image bytecode is empty"),
AbiError::NullUnits => f.write_str("AOT image unit pointer is null"),
AbiError::EmptyUnits => f.write_str("AOT image unit table is empty"),
AbiError::LengthOverflow => f.write_str("AOT image slice extent overflows isize::MAX"),
AbiError::UnsortedUnits {
previous_module_id,
previous_function_id,
module_id,
function_id,
} => write!(
f,
"AOT unit ({module_id}, {function_id}) follows ({previous_module_id}, {previous_function_id}) out of order"
),
AbiError::DuplicateFunction {
module_id,
function_id,
} => write!(
f,
"AOT image has duplicate native function ({module_id}, {function_id})"
),
AbiError::EntryFunctionMissing {
module_id,
function_id,
} => write!(
f,
"AOT image entry function ({module_id}, {function_id}) is absent"
),
AbiError::UnknownFunction {
module_id,
function_id,
} => write!(
f,
"no native entry for function ({module_id}, {function_id})"
),
}
}
}
impl std::error::Error for AbiError {}
#[cfg(feature = "aot-image")]
pub fn linked_program() -> Result<LinkedProgram<'static>, AbiError> {
unsafe extern "C" {
static bamts_program_descriptor: ProgramDescriptor;
}
unsafe { LinkedProgram::from_descriptor(&bamts_program_descriptor) }
}
#[cfg(feature = "jit-entry")]
pub use jit::JitEntry;
#[cfg(feature = "jit-entry")]
mod jit {
use core::marker::PhantomData;
use cranelift_jit::JITModule;
use cranelift_module::FuncId;
use super::{Completion, CompletionTag, NativeEntryFn, ShadowFrame, call_native_entry};
pub struct JitEntry<'m> {
entry: NativeEntryFn,
_module: PhantomData<&'m JITModule>,
}
impl<'m> JitEntry<'m> {
#[must_use]
pub fn new(module: &'m JITModule, func: FuncId) -> JitEntry<'m> {
let ptr = module.get_finalized_function(func);
let entry: NativeEntryFn =
unsafe { core::mem::transmute::<*const u8, NativeEntryFn>(ptr) };
JitEntry {
entry,
_module: PhantomData,
}
}
#[inline]
#[must_use]
pub fn entry_fn(&self) -> NativeEntryFn {
self.entry
}
pub fn invoke(&self, frame: &mut ShadowFrame, out: &mut Completion) -> CompletionTag {
unsafe { call_native_entry(self.entry, frame, out) }
}
}
}
#[cfg(test)]
mod tests {
fn test_bamts_load_this(f: *mut ShadowFrame, o: *mut Completion) -> u32 {
unsafe { super::bamts_load_this(f, o) }
}
fn test_bamts_call(f: *mut ShadowFrame, a: u64, t: u64, x: u64, o: *mut Completion) -> u32 {
unsafe { super::bamts_call(f, a, t, x, o) }
}
fn test_bamts_binary(f: *mut ShadowFrame, op: u32, l: u64, r: u64, o: *mut Completion) -> u32 {
unsafe { super::bamts_binary(f, op, l, r, o) }
}
fn test_bamts_truthy(f: *mut ShadowFrame, v: u64) -> u32 {
unsafe { super::bamts_truthy(f, v) }
}
fn test_bamts_consume_fuel(f: *mut ShadowFrame, amount: u32, o: *mut Completion) -> u32 {
unsafe { super::bamts_consume_fuel(f, amount, o) }
}
fn test_bamts_iterator_next(
f: *mut ShadowFrame,
i: u64,
d: u32,
v: u32,
o: *mut Completion,
) -> u32 {
unsafe { super::bamts_iterator_next(f, i, d, v, o) }
}
fn test_bamts_create_object(f: *mut ShadowFrame, o: *mut Completion) -> u32 {
unsafe { super::bamts_create_object(f, o) }
}
fn test_bamts_load_global(f: *mut ShadowFrame, n: u32, o: *mut Completion) -> u32 {
unsafe { super::bamts_load_global(f, n, o) }
}
use super::*;
use crate::{Completion, CompletionTag, ShadowFrame, Value};
use std::cell::Cell;
use std::panic::{AssertUnwindSafe, catch_unwind};
const CODEGEN_HELPERS: [(u32, &str); 32] = [
(0, "bamts_load_constant"),
(1, "bamts_unary"),
(2, "bamts_binary"),
(3, "bamts_create_object"),
(4, "bamts_create_array"),
(5, "bamts_create_closure"),
(6, "bamts_get_property"),
(7, "bamts_set_property"),
(8, "bamts_delete_property"),
(9, "bamts_call"),
(10, "bamts_construct"),
(11, "bamts_import"),
(12, "bamts_truthy"),
(13, "bamts_resume_value"),
(14, "bamts_define_accessor"),
(15, "bamts_load_global"),
(16, "bamts_store_global"),
(17, "bamts_typeof_global"),
(18, "bamts_load_this"),
(19, "bamts_load_arguments"),
(20, "bamts_load_new_target"),
(21, "bamts_array_push"),
(22, "bamts_array_extend"),
(23, "bamts_object_spread"),
(24, "bamts_set_prototype"),
(25, "bamts_create_private_name"),
(26, "bamts_create_regexp"),
(27, "bamts_get_iterator"),
(28, "bamts_iterator_next"),
(29, "bamts_export"),
(30, "bamts_consume_fuel"),
(31, "bamts_create_cell"),
];
struct Recorder {
last: Cell<Option<HelperCall>>,
truthy_calls: Cell<u32>,
truthy_answer: Cell<bool>,
result: HelperResult,
}
impl Recorder {
fn normal(value: Value) -> Recorder {
Recorder {
last: Cell::new(None),
truthy_calls: Cell::new(0),
truthy_answer: Cell::new(false),
result: HelperResult::normal(value),
}
}
}
impl NativeOps for Recorder {
fn truthy(&self, _frame: &mut NativeFrame<'_>, value: Value) -> bool {
self.truthy_calls.set(self.truthy_calls.get() + 1);
self.last.set(Some(HelperCall::Truthy { value }));
self.truthy_answer.get()
}
fn dispatch(&self, frame: &mut NativeFrame<'_>, call: HelperCall) -> HelperResult {
self.last.set(Some(call));
if let HelperCall::IteratorNext {
done_reg,
value_reg,
..
} = call
{
frame.set_register(done_reg, Value::TRUE);
frame.set_register(value_reg, Value::int32(9));
}
self.result
}
}
struct Panicky;
impl NativeOps for Panicky {
fn truthy(&self, _frame: &mut NativeFrame<'_>, _value: Value) -> bool {
panic!("truthy panic")
}
fn dispatch(&self, _frame: &mut NativeFrame<'_>, _call: HelperCall) -> HelperResult {
panic!("dispatch panic")
}
}
fn quietly<R>(f: impl FnOnce() -> R + std::panic::UnwindSafe) -> std::thread::Result<R> {
catch_unwind(f)
}
fn frame_with(regs: &mut [Value]) -> ShadowFrame {
let len = u16::try_from(regs.len()).expect("register count fits u16");
ShadowFrame::new(core::ptr::null_mut(), 0, 0, regs.as_mut_ptr(), len)
}
struct Reentrant {
depth: Cell<u32>,
max_depth: Cell<u32>,
post_nested_ran: Cell<bool>,
}
impl NativeOps for Reentrant {
fn truthy(&self, _frame: &mut NativeFrame<'_>, _value: Value) -> bool {
true
}
fn dispatch(&self, _frame: &mut NativeFrame<'_>, call: HelperCall) -> HelperResult {
let depth = self.depth.get();
self.max_depth.set(self.max_depth.get().max(depth));
if depth == 0 && matches!(call, HelperCall::Call { .. }) {
self.depth.set(1);
let mut child_regs = [Value::UNINITIALIZED; 1];
let mut child_frame = frame_with(&mut child_regs);
let mut child_out = Completion::new(Value::UNDEFINED);
let nested = test_bamts_load_this(&mut child_frame, &mut child_out);
assert_eq!(nested, CompletionTag::Normal.as_u32());
self.depth.set(0);
self.post_nested_ran.set(true);
}
HelperResult::normal(Value::int32(depth as i32 as u32))
}
}
#[test]
fn same_instance_reentry_mutates_state_after_nested_call() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Reentrant {
depth: Cell::new(0),
max_depth: Cell::new(0),
post_nested_ran: Cell::new(false),
};
let tag = with_native_ops(&mut ops, || {
test_bamts_call(
&mut frame,
Value::UNDEFINED.to_bits(),
Value::UNDEFINED.to_bits(),
Value::UNDEFINED.to_bits(),
&mut completion,
)
});
assert_eq!(tag, CompletionTag::Normal.as_u32());
assert_eq!(ops.max_depth.get(), 1);
assert!(ops.post_nested_ran.get());
}
#[test]
fn helper_symbols_and_indices_match_codegen() {
assert_eq!(HELPER_COUNT as usize, CODEGEN_HELPERS.len());
for (index, symbol) in CODEGEN_HELPERS {
let helper = NativeHelper::from_u32(index).expect("dense index");
assert_eq!(helper.as_u32(), index, "index for {helper:?}");
assert_eq!(helper.symbol(), symbol, "symbol for {helper:?}");
}
assert_eq!(NativeHelper::from_u32(HELPER_COUNT), None);
}
#[test]
fn helper_call_maps_to_its_helper() {
assert_eq!(
HelperCall::Binary {
op: 0,
left: Value::UNDEFINED,
right: Value::UNDEFINED,
}
.helper(),
NativeHelper::Binary
);
assert_eq!(HelperCall::ResumeValue.helper(), NativeHelper::ResumeValue);
assert_eq!(
HelperCall::Truthy { value: Value::TRUE }.helper(),
NativeHelper::Truthy
);
assert_eq!(
HelperCall::Export {
name: 3,
src: Value::NULL,
}
.helper(),
NativeHelper::Export
);
assert_eq!(
HelperCall::ConsumeFuel { amount: 1 }.helper(),
NativeHelper::ConsumeFuel
);
assert_eq!(HelperCall::CreateCell.helper(), NativeHelper::CreateCell);
}
#[test]
fn exported_wrapper_dispatches_and_writes_completion() {
let mut regs = [Value::UNINITIALIZED; 2];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Recorder::normal(Value::int32(42));
let tag = with_native_ops(&mut ops, || {
test_bamts_binary(
&mut frame,
2,
Value::int32(3).to_bits(),
Value::int32(4).to_bits(),
&mut completion,
)
});
assert_eq!(tag, CompletionTag::Normal.as_u32());
assert_eq!(completion.value.as_int32(), Some(42));
assert_eq!(
ops.last.get(),
Some(HelperCall::Binary {
op: 2,
left: Value::int32(3),
right: Value::int32(4),
})
);
}
#[test]
fn consume_fuel_wrapper_preserves_amount() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Recorder::normal(Value::UNDEFINED);
let tag = with_native_ops(&mut ops, || {
test_bamts_consume_fuel(&mut frame, 7, &mut completion)
});
assert_eq!(tag, CompletionTag::Normal.as_u32());
assert_eq!(ops.last.get(), Some(HelperCall::ConsumeFuel { amount: 7 }));
}
#[test]
fn truthy_wrapper_routes_to_truthy_not_dispatch() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut ops = Recorder::normal(Value::UNDEFINED);
ops.truthy_answer.set(true);
let truthy = with_native_ops(&mut ops, || {
test_bamts_truthy(&mut frame, Value::int32(1).to_bits())
});
assert_eq!(truthy, 1);
assert_eq!(ops.truthy_calls.get(), 1);
ops.truthy_answer.set(false);
let falsy = with_native_ops(&mut ops, || {
test_bamts_truthy(&mut frame, Value::int32(0).to_bits())
});
assert_eq!(falsy, 0);
assert_eq!(ops.truthy_calls.get(), 2);
}
#[test]
fn iterator_next_writes_both_registers() {
let mut regs = [Value::UNINITIALIZED; 2];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Recorder::normal(Value::UNDEFINED);
let tag = with_native_ops(&mut ops, || {
test_bamts_iterator_next(&mut frame, Value::NULL.to_bits(), 0, 1, &mut completion)
});
assert_eq!(tag, CompletionTag::Normal.as_u32());
assert_eq!(regs[0], Value::TRUE);
assert_eq!(regs[1], Value::int32(9));
}
#[test]
fn missing_dispatcher_is_a_fatal_trap() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let tag = test_bamts_create_object(&mut frame, &mut completion);
assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
assert_eq!(completion.value.as_int32(), Some(TRAP_MISSING_NATIVE_OPS));
assert_eq!(test_bamts_truthy(&mut frame, Value::TRUE.to_bits()), 0);
}
#[test]
fn invalid_frame_is_a_fatal_trap() {
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Recorder::normal(Value::int32(1));
let null = with_native_ops(&mut ops, || {
test_bamts_create_object(core::ptr::null_mut(), &mut completion)
});
assert_eq!(null, CompletionTag::FatalTrap.as_u32());
assert_eq!(completion.value.as_int32(), Some(TRAP_INVALID_FRAME));
let misaligned = with_native_ops(&mut ops, || {
test_bamts_create_object(
core::ptr::null_mut::<ShadowFrame>().wrapping_byte_add(1),
&mut completion,
)
});
assert_eq!(misaligned, CompletionTag::FatalTrap.as_u32());
}
#[test]
fn dispatcher_panic_is_caught_as_fatal_trap() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Panicky;
let tag = quietly(AssertUnwindSafe(|| {
with_native_ops(&mut ops, || {
test_bamts_create_object(&mut frame, &mut completion)
})
}))
.expect("wrapper must not unwind across the boundary");
assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
assert_eq!(completion.value.as_int32(), Some(TRAP_PANIC));
}
#[test]
fn tls_nesting_restores_the_outer_dispatcher() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut outer = Recorder::normal(Value::int32(1));
let mut inner = Recorder::normal(Value::int32(2));
with_native_ops(&mut outer, || {
test_bamts_create_object(&mut frame, &mut completion);
assert_eq!(completion.value.as_int32(), Some(1));
with_native_ops(&mut inner, || {
test_bamts_create_object(&mut frame, &mut completion);
assert_eq!(completion.value.as_int32(), Some(2));
});
test_bamts_create_object(&mut frame, &mut completion);
assert_eq!(completion.value.as_int32(), Some(1));
});
let tag = test_bamts_create_object(&mut frame, &mut completion);
assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
}
#[test]
fn tls_is_restored_after_a_panicking_body() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Recorder::normal(Value::int32(1));
let result = quietly(AssertUnwindSafe(|| {
with_native_ops(&mut ops, || panic!("body panic"));
}));
assert!(result.is_err());
let tag = test_bamts_create_object(&mut frame, &mut completion);
assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
}
#[test]
fn native_frame_new_validates_metadata() {
let mut regs = [Value::int32(1), Value::int32(2)];
let base = regs.as_mut_ptr();
let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 3, base, 2);
{
let mut short = [Value::int32(1)];
assert!(NativeFrame::new(&mut frame, &mut short).is_none());
}
{
let mut other = [Value::int32(1), Value::int32(2)];
assert!(NativeFrame::new(&mut frame, &mut other).is_none());
}
{
let native = NativeFrame::new(&mut frame, &mut regs);
assert!(native.is_some());
}
}
#[test]
fn native_frame_from_raw_validates_and_addresses_registers() {
assert!(unsafe { NativeFrame::from_raw(core::ptr::null_mut()) }.is_none());
assert!(
unsafe {
NativeFrame::from_raw(core::ptr::null_mut::<ShadowFrame>().wrapping_byte_add(1))
}
.is_none()
);
let mut regs = [Value::int32(10), Value::int32(20)];
let mut frame = ShadowFrame::new(core::ptr::null_mut(), 7, 11, regs.as_mut_ptr(), 2);
{
let mut native = unsafe { NativeFrame::from_raw(&mut frame) }.expect("valid frame");
assert_eq!(native.handle_len(), 2);
assert_eq!(native.module_id(), 11);
assert_eq!(native.pc(), 7);
assert_eq!(native.register(0), Value::int32(10));
assert_eq!(native.try_register(5), None);
native.set_register(1, Value::int32(99));
assert!(native.try_set_register(1, Value::int32(99)));
assert!(!native.try_set_register(5, Value::int32(0)));
native.set_resume(3);
}
assert_eq!(frame.bytecode_pc, 3);
assert_eq!(regs[1], Value::int32(99));
}
#[test]
fn native_frame_from_raw_rejects_handles_overlapping_header() {
let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 0, core::ptr::null_mut(), 1);
frame.handles = core::ptr::addr_of_mut!(frame).cast::<Value>();
assert!(unsafe { NativeFrame::from_raw(&mut frame) }.is_none());
}
unsafe extern "C" fn entry_returns_seven(
_frame: *mut ShadowFrame,
out: *mut Completion,
) -> u32 {
unsafe { core::ptr::write(out, Completion::new(Value::int32(7))) };
CompletionTag::Normal.as_u32()
}
unsafe extern "C" fn entry_returns_invalid_tag(
_frame: *mut ShadowFrame,
out: *mut Completion,
) -> u32 {
unsafe { core::ptr::write(out, Completion::new(Value::int32(99))) };
u32::MAX
}
#[test]
fn invalid_native_completion_tag_replaces_stale_output() {
let mut regs: [Value; 0] = [];
let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 0, regs.as_mut_ptr(), 0);
let mut out = Completion::new(Value::int32(123));
let tag = unsafe { call_native_entry(entry_returns_invalid_tag, &mut frame, &mut out) };
assert_eq!(tag, CompletionTag::FatalTrap);
assert_eq!(out.value.as_int32(), Some(TRAP_INVALID_COMPLETION_TAG));
}
fn unit(module_id: u32, function_id: u32) -> UnitDescriptor {
UnitDescriptor {
function_id,
module_id,
entry: entry_returns_seven,
}
}
fn program(
bytecode: &[u8],
units: &[UnitDescriptor],
entry_module: u32,
entry_function: u32,
) -> ProgramDescriptor {
ProgramDescriptor {
magic: AOT_MAGIC,
abi_version: AOT_ABI_VERSION,
flags: 0,
bytecode: bytecode.as_ptr(),
bytecode_len: bytecode.len(),
units: units.as_ptr(),
unit_count: units.len(),
entry_function,
entry_module,
}
}
fn linked_of<'a>(
descriptor: &'a ProgramDescriptor,
bytecode: &'a [u8],
units: &'a [UnitDescriptor],
) -> Result<LinkedProgram<'a>, AbiError> {
assert_eq!(descriptor.bytecode_len, bytecode.len());
assert!(bytecode.is_empty() || core::ptr::eq(descriptor.bytecode, bytecode.as_ptr()));
assert_eq!(descriptor.unit_count, units.len());
assert!(units.is_empty() || core::ptr::eq(descriptor.units, units.as_ptr()));
unsafe { LinkedProgram::from_descriptor(descriptor) }
}
#[test]
fn linked_program_validates_and_invokes_tuple_identities() {
let bytecode = [1u8, 2, 3];
let units = [unit(2, 4), unit(2, 5), unit(3, 5)];
let descriptor = program(&bytecode, &units, 3, 5);
let linked = linked_of(&descriptor, &bytecode, &units).expect("valid image");
assert_eq!(linked.bytecode(), &[1, 2, 3]);
assert_eq!(linked.program_bytes(), &[1, 2, 3]);
assert_eq!(linked.units().len(), 3);
assert_eq!(linked.entry_module(), 3);
assert_eq!(linked.entry_function(), 5);
assert!(linked.unit(2, 5).is_some());
assert!(linked.unit(3, 5).is_some());
assert!(linked.unit(3, 4).is_none());
let mut regs: [Value; 0] = [];
let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 3, regs.as_mut_ptr(), 0);
let mut completion = Completion::new(Value::UNDEFINED);
let tag = linked
.invoke(3, 5, &mut frame, &mut completion)
.expect("entry present");
assert_eq!(tag, CompletionTag::Normal);
assert_eq!(completion.value.as_int32(), Some(7));
assert_eq!(
linked.invoke(4, 5, &mut frame, &mut completion).err(),
Some(AbiError::UnknownFunction {
module_id: 4,
function_id: 5,
})
);
}
#[test]
fn linked_program_rejects_malformed_descriptors() {
let bytecode = [1u8, 2, 3];
let units = [unit(2, 5)];
let mut bad_magic = program(&bytecode, &units, 2, 5);
bad_magic.magic = 0;
assert_eq!(
linked_of(&bad_magic, &bytecode, &units).err(),
Some(AbiError::BadMagic { found: 0 })
);
let mut bad_version = program(&bytecode, &units, 2, 5);
bad_version.abi_version = 1;
assert_eq!(
linked_of(&bad_version, &bytecode, &units).err(),
Some(AbiError::UnsupportedAbiVersion { found: 1 })
);
let mut bad_flags = program(&bytecode, &units, 2, 5);
bad_flags.flags = 1;
assert_eq!(
linked_of(&bad_flags, &bytecode, &units).err(),
Some(AbiError::NonZeroFlags { flags: 1 })
);
let empty_bytecode = program(&[], &units, 2, 5);
assert_eq!(
linked_of(&empty_bytecode, &[], &units).err(),
Some(AbiError::EmptyBytecode)
);
let empty_units = program(&bytecode, &[], 2, 5);
assert_eq!(
linked_of(&empty_units, &bytecode, &[]).err(),
Some(AbiError::EmptyUnits)
);
}
#[test]
fn linked_program_rejects_unsorted_duplicate_and_missing_tuple_entries() {
let bytecode = [1u8, 2, 3];
let unsorted = [unit(2, 5), unit(1, 9)];
let unsorted_descriptor = program(&bytecode, &unsorted, 2, 5);
assert_eq!(
linked_of(&unsorted_descriptor, &bytecode, &unsorted).err(),
Some(AbiError::UnsortedUnits {
previous_module_id: 2,
previous_function_id: 5,
module_id: 1,
function_id: 9,
})
);
let duplicate = [unit(2, 5), unit(2, 5)];
let duplicate_descriptor = program(&bytecode, &duplicate, 2, 5);
assert_eq!(
linked_of(&duplicate_descriptor, &bytecode, &duplicate).err(),
Some(AbiError::DuplicateFunction {
module_id: 2,
function_id: 5,
})
);
let units = [unit(2, 5), unit(3, 5)];
let missing_entry = program(&bytecode, &units, 4, 5);
assert_eq!(
linked_of(&missing_entry, &bytecode, &units).err(),
Some(AbiError::EntryFunctionMissing {
module_id: 4,
function_id: 5,
})
);
}
#[test]
fn null_out_is_a_fatal_trap_without_dereference() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut ops = Recorder::normal(Value::int32(1));
let tag = with_native_ops(&mut ops, || {
test_bamts_load_global(&mut frame, 0, core::ptr::null_mut())
});
assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
}
#[test]
fn iterator_next_out_of_range_register_is_fatal_trap() {
let mut regs = [Value::UNINITIALIZED; 1];
let mut frame = frame_with(&mut regs);
let mut completion = Completion::new(Value::UNDEFINED);
let mut ops = Recorder::normal(Value::UNDEFINED);
let tag = with_native_ops(&mut ops, || {
test_bamts_iterator_next(&mut frame, Value::NULL.to_bits(), 99, 0, &mut completion)
});
assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
assert_eq!(completion.value.as_int32(), Some(TRAP_INVALID_REGISTER));
assert_eq!(regs[0], Value::UNINITIALIZED);
}
}