use std::sync::{Arc, OnceLock, RwLock};
use std::task::{Context, Poll, Wake, Waker};
use crate::builtins::BuiltinFunction;
use super::*;
pub type HostOpId = u64;
#[derive(Clone, Debug, Default, PartialEq)]
pub enum CallReturn {
#[default]
None,
One(Value),
}
impl CallReturn {
pub fn none() -> Self {
Self::None
}
pub fn one(value: Value) -> Self {
Self::One(value)
}
pub fn from_values(values: Vec<Value>) -> Self {
match values.len() {
0 => Self::None,
1 => Self::One(
values
.into_iter()
.next()
.expect("single-value return should contain one value"),
),
_ => Self::One(Value::array(values)),
}
}
pub fn is_empty(&self) -> bool {
matches!(self, Self::None)
}
pub fn as_slice(&self) -> &[Value] {
match self {
Self::None => &[],
Self::One(value) => std::slice::from_ref(value),
}
}
pub(crate) fn push_onto_stack(self, stack: &mut Vec<Value>) {
match self {
Self::None => {}
Self::One(value) => stack.push(value),
}
}
}
impl From<Vec<Value>> for CallReturn {
fn from(values: Vec<Value>) -> Self {
Self::from_values(values)
}
}
#[derive(Debug, PartialEq)]
pub enum CallOutcome {
Return(CallReturn),
Halt,
Yield,
Pending(HostOpId),
}
pub trait HostFunction: Send {
fn call(&mut self, vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome>;
}
pub trait HostStackFunction: Send {
fn call(&mut self, vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome>;
}
pub trait HostArgsFunction: Send {
fn call(&mut self, args: &[Value]) -> VmResult<CallOutcome>;
}
pub trait HostAsyncBridge: Send {
fn poll_op(&mut self, op_id: HostOpId, cx: &mut Context<'_>) -> Poll<VmResult<CallReturn>>;
}
pub type StaticHostFunction = fn(&mut Vm, &[Value]) -> VmResult<CallOutcome>;
pub type StaticHostStackFunction = fn(&mut Vm, &[Value]) -> VmResult<CallOutcome>;
pub type StaticHostArgsFunction = fn(&[Value]) -> VmResult<CallOutcome>;
type HostFactory = dyn Fn() -> Box<dyn HostFunction> + Send + Sync;
type HostStackFactory = dyn Fn() -> Box<dyn HostStackFunction> + Send + Sync;
type HostArgsFactory = dyn Fn() -> Box<dyn HostArgsFunction> + Send + Sync;
#[derive(Clone)]
enum RegistryEntryKind {
Factory(Arc<HostFactory>),
Static(StaticHostFunction),
StackFactory(Arc<HostStackFactory>),
StackStatic(StaticHostStackFunction),
ArgsFactory(Arc<HostArgsFactory>),
ArgsStatic(StaticHostArgsFunction),
}
#[derive(Clone)]
struct RegistryEntry {
arity: u8,
kind: RegistryEntryKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HostBindingPlan {
import_signature: Vec<HostImport>,
registry_slots: Vec<u16>,
resolved_calls: Vec<u16>,
}
#[derive(Clone)]
pub struct HostFunctionRegistry {
entries: Arc<Vec<RegistryEntry>>,
by_name: Arc<HashMap<String, u16>>,
plan_cache: Arc<RwLock<HashMap<Vec<HostImport>, Arc<HostBindingPlan>>>>,
}
impl Default for HostFunctionRegistry {
fn default() -> Self {
Self::new()
}
}
impl HostFunctionRegistry {
fn empty() -> Self {
Self {
entries: Arc::new(Vec::new()),
by_name: Arc::new(HashMap::new()),
plan_cache: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn new() -> Self {
static DEFAULT_REGISTRY: OnceLock<HostFunctionRegistry> = OnceLock::new();
DEFAULT_REGISTRY
.get_or_init(|| {
let mut registry = Self::empty();
crate::builtins::runtime::register_default_host_functions(&mut registry);
registry
})
.clone()
}
fn invalidate_plan_cache(&mut self) {
self.plan_cache = Arc::new(RwLock::new(HashMap::new()));
}
pub fn register<F>(&mut self, name: impl Into<String>, arity: u8, factory: F)
where
F: Fn() -> Box<dyn HostFunction> + Send + Sync + 'static,
{
let name = name.into();
if let Some(&slot) = self.by_name.get(&name)
&& let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
{
entry.arity = arity;
entry.kind = RegistryEntryKind::Factory(Arc::new(factory));
self.invalidate_plan_cache();
return;
}
let entries = Arc::make_mut(&mut self.entries);
let slot = entries.len() as u16;
entries.push(RegistryEntry {
arity,
kind: RegistryEntryKind::Factory(Arc::new(factory)),
});
Arc::make_mut(&mut self.by_name).insert(name, slot);
self.invalidate_plan_cache();
}
pub fn register_static(
&mut self,
name: impl Into<String>,
arity: u8,
function: StaticHostFunction,
) {
let name = name.into();
if let Some(&slot) = self.by_name.get(&name)
&& let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
{
entry.arity = arity;
entry.kind = RegistryEntryKind::Static(function);
self.invalidate_plan_cache();
return;
}
let entries = Arc::make_mut(&mut self.entries);
let slot = entries.len() as u16;
entries.push(RegistryEntry {
arity,
kind: RegistryEntryKind::Static(function),
});
Arc::make_mut(&mut self.by_name).insert(name, slot);
self.invalidate_plan_cache();
}
pub fn register_stack<F>(&mut self, name: impl Into<String>, arity: u8, factory: F)
where
F: Fn() -> Box<dyn HostStackFunction> + Send + Sync + 'static,
{
let name = name.into();
if let Some(&slot) = self.by_name.get(&name)
&& let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
{
entry.arity = arity;
entry.kind = RegistryEntryKind::StackFactory(Arc::new(factory));
self.invalidate_plan_cache();
return;
}
let entries = Arc::make_mut(&mut self.entries);
let slot = entries.len() as u16;
entries.push(RegistryEntry {
arity,
kind: RegistryEntryKind::StackFactory(Arc::new(factory)),
});
Arc::make_mut(&mut self.by_name).insert(name, slot);
self.invalidate_plan_cache();
}
pub fn register_static_stack(
&mut self,
name: impl Into<String>,
arity: u8,
function: StaticHostStackFunction,
) {
let name = name.into();
if let Some(&slot) = self.by_name.get(&name)
&& let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
{
entry.arity = arity;
entry.kind = RegistryEntryKind::StackStatic(function);
self.invalidate_plan_cache();
return;
}
let entries = Arc::make_mut(&mut self.entries);
let slot = entries.len() as u16;
entries.push(RegistryEntry {
arity,
kind: RegistryEntryKind::StackStatic(function),
});
Arc::make_mut(&mut self.by_name).insert(name, slot);
self.invalidate_plan_cache();
}
pub fn register_args<F>(&mut self, name: impl Into<String>, arity: u8, factory: F)
where
F: Fn() -> Box<dyn HostArgsFunction> + Send + Sync + 'static,
{
let name = name.into();
if let Some(&slot) = self.by_name.get(&name)
&& let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
{
entry.arity = arity;
entry.kind = RegistryEntryKind::ArgsFactory(Arc::new(factory));
self.invalidate_plan_cache();
return;
}
let entries = Arc::make_mut(&mut self.entries);
let slot = entries.len() as u16;
entries.push(RegistryEntry {
arity,
kind: RegistryEntryKind::ArgsFactory(Arc::new(factory)),
});
Arc::make_mut(&mut self.by_name).insert(name, slot);
self.invalidate_plan_cache();
}
pub fn register_static_args(
&mut self,
name: impl Into<String>,
arity: u8,
function: StaticHostArgsFunction,
) {
let name = name.into();
if let Some(&slot) = self.by_name.get(&name)
&& let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
{
entry.arity = arity;
entry.kind = RegistryEntryKind::ArgsStatic(function);
self.invalidate_plan_cache();
return;
}
let entries = Arc::make_mut(&mut self.entries);
let slot = entries.len() as u16;
entries.push(RegistryEntry {
arity,
kind: RegistryEntryKind::ArgsStatic(function),
});
Arc::make_mut(&mut self.by_name).insert(name, slot);
self.invalidate_plan_cache();
}
pub fn bind_vm_cached(&self, vm: &mut Vm) -> VmResult<()> {
let plan = self.prepare_shared_plan(&vm.program.imports)?;
self.bind_vm_with_plan(vm, &plan)
}
pub fn prepare_plan(&self, imports: &[HostImport]) -> VmResult<HostBindingPlan> {
Ok(self.prepare_shared_plan(imports)?.as_ref().clone())
}
pub fn prepare_shared_plan(&self, imports: &[HostImport]) -> VmResult<Arc<HostBindingPlan>> {
self.plan_for_imports(imports)
}
fn plan_for_imports(&self, imports: &[HostImport]) -> VmResult<Arc<HostBindingPlan>> {
if let Some(plan) = self
.plan_cache
.read()
.expect("host binding plan cache read lock should not be poisoned")
.get(imports)
.cloned()
{
return Ok(plan);
}
let mut registry_slot_to_vm_slot: HashMap<u16, u16> = HashMap::new();
let mut registry_slots = Vec::new();
let mut resolved_calls = Vec::with_capacity(imports.len());
for import in imports {
let registry_slot = self
.by_name
.get(&import.name)
.copied()
.ok_or_else(|| VmError::UnboundImport(import.name.clone()))?;
let entry = self
.entries
.get(registry_slot as usize)
.ok_or(VmError::InvalidCall(registry_slot))?;
if entry.arity != import.arity {
return Err(VmError::InvalidCallArity {
import: import.name.clone(),
expected: entry.arity,
got: import.arity,
});
}
let vm_slot = if let Some(&existing) = registry_slot_to_vm_slot.get(®istry_slot) {
existing
} else {
let slot = registry_slots.len() as u16;
registry_slots.push(registry_slot);
registry_slot_to_vm_slot.insert(registry_slot, slot);
slot
};
resolved_calls.push(vm_slot);
}
let import_key = imports.to_vec();
let computed = Arc::new(HostBindingPlan {
import_signature: import_key.clone(),
registry_slots,
resolved_calls,
});
let mut cache = self
.plan_cache
.write()
.expect("host binding plan cache write lock should not be poisoned");
Ok(cache.entry(import_key).or_insert_with(|| computed).clone())
}
pub fn bind_vm_with_plan(&self, vm: &mut Vm, plan: &HostBindingPlan) -> VmResult<()> {
if vm.program.imports != plan.import_signature {
return Err(VmError::HostError(
"host binding plan does not match vm import signature".to_string(),
));
}
if !vm.host_functions.is_empty() || !vm.host_function_symbols.is_empty() {
return Err(VmError::HostError(
"host binding cache requires an unbound vm".to_string(),
));
}
vm.host_functions.reserve(plan.registry_slots.len());
for ®istry_slot in &plan.registry_slots {
let entry = self
.entries
.get(registry_slot as usize)
.ok_or(VmError::InvalidCall(registry_slot))?;
match &entry.kind {
RegistryEntryKind::Factory(factory) => {
vm.register_function(factory());
}
RegistryEntryKind::Static(function) => {
vm.register_static_function(*function);
}
RegistryEntryKind::StackFactory(factory) => {
vm.register_stack_function(factory());
}
RegistryEntryKind::StackStatic(function) => {
vm.register_static_stack_function(*function);
}
RegistryEntryKind::ArgsFactory(factory) => {
vm.register_args_function(factory());
}
RegistryEntryKind::ArgsStatic(function) => {
vm.register_static_args_function(*function);
}
}
}
vm.install_resolved_calls(plan.resolved_calls.clone())?;
Ok(())
}
}
pub(super) enum VmHostFunction {
Dynamic(Box<dyn HostFunction>),
Static(StaticHostFunction),
StackDynamic(Box<dyn HostStackFunction>),
StackStatic(StaticHostStackFunction),
ArgsDynamic(Box<dyn HostArgsFunction>),
ArgsStatic(StaticHostArgsFunction),
}
pub(super) enum HostCallExecOutcome {
Returned,
Halted,
Yielded,
Pending(HostOpId),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct WaitingHostOp {
pub(super) op_id: HostOpId,
pub(super) source: WaitingHostOpSource,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum WaitingHostOpSource {
HostBridge,
BuiltinIo,
}
struct NoopWake;
impl Wake for NoopWake {
fn wake(self: Arc<Self>) {}
}
fn noop_waker() -> Waker {
Waker::from(Arc::new(NoopWake))
}
#[inline]
fn builtin_for_binding_name(name: &str) -> Option<BuiltinFunction> {
if !name.contains("::") {
return None;
}
BuiltinFunction::from_namespaced_name(name)
}
impl Vm {
pub fn register_function(&mut self, function: Box<dyn HostFunction>) -> u16 {
let index = self.host_functions.len() as u16;
self.host_functions.push(VmHostFunction::Dynamic(function));
self.resolved_calls_dirty = true;
index
}
pub fn register_static_function(&mut self, function: StaticHostFunction) -> u16 {
let index = self.host_functions.len() as u16;
self.host_functions.push(VmHostFunction::Static(function));
self.resolved_calls_dirty = true;
index
}
pub fn register_stack_function(&mut self, function: Box<dyn HostStackFunction>) -> u16 {
let index = self.host_functions.len() as u16;
self.host_functions
.push(VmHostFunction::StackDynamic(function));
self.resolved_calls_dirty = true;
index
}
pub fn register_static_stack_function(&mut self, function: StaticHostStackFunction) -> u16 {
let index = self.host_functions.len() as u16;
self.host_functions
.push(VmHostFunction::StackStatic(function));
self.resolved_calls_dirty = true;
index
}
pub fn register_args_function(&mut self, function: Box<dyn HostArgsFunction>) -> u16 {
let index = self.host_functions.len() as u16;
self.host_functions
.push(VmHostFunction::ArgsDynamic(function));
self.resolved_calls_dirty = true;
index
}
pub fn register_static_args_function(&mut self, function: StaticHostArgsFunction) -> u16 {
let index = self.host_functions.len() as u16;
self.host_functions
.push(VmHostFunction::ArgsStatic(function));
self.resolved_calls_dirty = true;
index
}
pub fn bind_function(&mut self, name: impl Into<String>, function: Box<dyn HostFunction>) {
let name = name.into();
if let Some(builtin) = builtin_for_binding_name(&name) {
self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Dynamic(function));
return;
}
if let Some(&index) = self.host_function_symbols.get(&name)
&& let Some(slot) = self.host_functions.get_mut(index as usize)
{
*slot = VmHostFunction::Dynamic(function);
self.resolved_calls_dirty = true;
return;
}
let index = self.register_function(function);
self.host_function_symbols.insert(name, index);
self.resolved_calls_dirty = true;
}
pub fn bind_static_function(&mut self, name: impl Into<String>, function: StaticHostFunction) {
let name = name.into();
if let Some(builtin) = builtin_for_binding_name(&name) {
self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Static(function));
return;
}
if let Some(&index) = self.host_function_symbols.get(&name)
&& let Some(slot) = self.host_functions.get_mut(index as usize)
{
*slot = VmHostFunction::Static(function);
self.resolved_calls_dirty = true;
return;
}
let index = self.register_static_function(function);
self.host_function_symbols.insert(name, index);
self.resolved_calls_dirty = true;
}
pub fn bind_stack_function(
&mut self,
name: impl Into<String>,
function: Box<dyn HostStackFunction>,
) {
let name = name.into();
if let Some(&index) = self.host_function_symbols.get(&name)
&& let Some(slot) = self.host_functions.get_mut(index as usize)
{
*slot = VmHostFunction::StackDynamic(function);
self.resolved_calls_dirty = true;
return;
}
let index = self.register_stack_function(function);
self.host_function_symbols.insert(name, index);
self.resolved_calls_dirty = true;
}
pub fn bind_static_stack_function(
&mut self,
name: impl Into<String>,
function: StaticHostStackFunction,
) {
let name = name.into();
if let Some(builtin) = builtin_for_binding_name(&name) {
self.bind_builtin_overrideslot(
builtin.call_index(),
VmHostFunction::StackStatic(function),
);
return;
}
if let Some(&index) = self.host_function_symbols.get(&name)
&& let Some(slot) = self.host_functions.get_mut(index as usize)
{
*slot = VmHostFunction::StackStatic(function);
self.resolved_calls_dirty = true;
return;
}
let index = self.register_static_stack_function(function);
self.host_function_symbols.insert(name, index);
self.resolved_calls_dirty = true;
}
pub fn bind_args_function(
&mut self,
name: impl Into<String>,
function: Box<dyn HostArgsFunction>,
) {
let name = name.into();
if let Some(builtin) = builtin_for_binding_name(&name) {
self.bind_builtin_overrideslot(
builtin.call_index(),
VmHostFunction::ArgsDynamic(function),
);
return;
}
if let Some(&index) = self.host_function_symbols.get(&name)
&& let Some(slot) = self.host_functions.get_mut(index as usize)
{
*slot = VmHostFunction::ArgsDynamic(function);
self.resolved_calls_dirty = true;
return;
}
let index = self.register_args_function(function);
self.host_function_symbols.insert(name, index);
self.resolved_calls_dirty = true;
}
pub fn bind_static_args_function(
&mut self,
name: impl Into<String>,
function: StaticHostArgsFunction,
) {
let name = name.into();
if let Some(builtin) = builtin_for_binding_name(&name) {
self.bind_builtin_overrideslot(
builtin.call_index(),
VmHostFunction::ArgsStatic(function),
);
return;
}
if let Some(&index) = self.host_function_symbols.get(&name)
&& let Some(slot) = self.host_functions.get_mut(index as usize)
{
*slot = VmHostFunction::ArgsStatic(function);
self.resolved_calls_dirty = true;
return;
}
let index = self.register_static_args_function(function);
self.host_function_symbols.insert(name, index);
self.resolved_calls_dirty = true;
}
pub fn bind_builtin_override(
&mut self,
name: impl Into<String>,
function: Box<dyn HostFunction>,
) -> VmResult<()> {
let name = name.into();
let builtin = BuiltinFunction::from_namespaced_name(&name).ok_or_else(|| {
VmError::HostError(format!("unknown namespaced builtin override '{name}'"))
})?;
self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Dynamic(function));
Ok(())
}
pub fn bind_builtin_static_override(
&mut self,
name: impl Into<String>,
function: StaticHostFunction,
) -> VmResult<()> {
let name = name.into();
let builtin = BuiltinFunction::from_namespaced_name(&name).ok_or_else(|| {
VmError::HostError(format!("unknown namespaced builtin override '{name}'"))
})?;
self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Static(function));
Ok(())
}
fn bind_builtin_overrideslot(&mut self, builtin_call_index: u16, function: VmHostFunction) {
if let Some(&host_slot) = self.builtin_overrides.get(&builtin_call_index)
&& let Some(slot) = self.host_functions.get_mut(host_slot as usize)
{
*slot = function;
return;
}
let host_slot = self.host_functions.len() as u16;
self.host_functions.push(function);
self.builtin_overrides.insert(builtin_call_index, host_slot);
}
pub fn set_async_bridge(&mut self, bridge: Box<dyn HostAsyncBridge>) {
self.async_bridge = Some(bridge);
}
pub fn clear_async_bridge(&mut self) {
self.async_bridge = None;
}
pub fn set_runtime_print_sink<F>(&mut self, sink: F)
where
F: FnMut(String) + Send + 'static,
{
self.runtime_print_sink = Some(Box::new(sink));
}
pub fn clear_runtime_print_sink(&mut self) {
self.runtime_print_sink = None;
}
pub(crate) fn write_runtime_print(&mut self, rendered: String) -> VmResult<()> {
let Some(sink) = self.runtime_print_sink.as_mut() else {
return Err(VmError::HostError(
"runtime print sink is not configured".to_string(),
));
};
sink(rendered);
Ok(())
}
pub fn allocate_host_op_id(&mut self) -> HostOpId {
let op_id = self.next_host_op_id;
self.next_host_op_id = self.next_host_op_id.wrapping_add(1).max(1);
op_id
}
pub fn waiting_host_op_id(&self) -> Option<HostOpId> {
self.waiting_host_op.map(|op| op.op_id)
}
pub fn complete_host_op(
&mut self,
op_id: HostOpId,
values: impl Into<CallReturn>,
) -> VmResult<()> {
self.complete_waiting_host_op(op_id, values.into())
}
pub fn poll_waiting_host_op(&mut self, cx: &mut Context<'_>) -> Poll<VmResult<()>> {
let Some(waiting) = self.waiting_host_op else {
return Poll::Ready(Ok(()));
};
let poll_result = match waiting.source {
WaitingHostOpSource::HostBridge => {
let bridge_ptr = match self.async_bridge.as_mut() {
Some(bridge) => bridge.as_mut() as *mut dyn HostAsyncBridge,
None => {
return Poll::Ready(Err(VmError::HostError(format!(
"vm waiting on host op {} without an async bridge",
waiting.op_id
))));
}
};
unsafe { (&mut *bridge_ptr).poll_op(waiting.op_id, cx) }
}
WaitingHostOpSource::BuiltinIo => {
crate::builtins::runtime::poll_builtin_io_op(self, waiting.op_id, cx)
}
};
match poll_result {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(values)) => {
self.complete_waiting_host_op(waiting.op_id, values)?;
Poll::Ready(Ok(()))
}
Poll::Ready(Err(err)) => {
self.waiting_host_op = None;
Poll::Ready(Err(err))
}
}
}
pub async fn await_waiting_host_op(&mut self) -> VmResult<()> {
std::future::poll_fn(|cx| self.poll_waiting_host_op(cx)).await
}
pub fn wait_for_host_op_blocking(&mut self) -> VmResult<()> {
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
loop {
match self.poll_waiting_host_op(&mut cx) {
Poll::Ready(result) => return result,
Poll::Pending => {
#[cfg(not(target_arch = "wasm32"))]
{
std::thread::sleep(std::time::Duration::from_millis(1));
}
#[cfg(target_arch = "wasm32")]
{
return Err(VmError::HostError(
"blocking host-op wait is unsupported on wasm32 runtime".to_string(),
));
}
}
}
}
}
pub(super) fn execute_host_call(
&mut self,
index: u16,
argc_u8: u8,
call_ip: usize,
) -> VmResult<HostCallExecOutcome> {
let argc = argc_u8 as usize;
if let Some(builtin) = BuiltinFunction::from_call_index(index) {
if !builtin.accepts_arity(argc_u8) {
return Err(VmError::InvalidCallArity {
import: builtin.name().to_string(),
expected: builtin.arity(),
got: argc_u8,
});
}
if self.builtin_overrides.contains_key(&index) {
return self.execute_builtin_override_call(index, argc_u8, call_ip);
}
if let Some(outcome) =
self.try_execute_typed_builtin_fast_path(builtin, argc, call_ip)?
{
return Ok(outcome);
}
if let Some(outcome) = self.try_execute_builtin_projection_fast_path(builtin, argc)? {
return Ok(outcome);
}
self.record_generic_builtin_call();
return self.execute_builtin_call_from_stack(builtin, argc, call_ip);
}
let resolved_index = self.resolve_call_target(index, argc_u8)?;
if self.bound_host_function_uses_args_slice(resolved_index)? {
self.execute_bound_args_host_function(resolved_index, argc, call_ip)
} else if self.bound_host_function_uses_stack_borrow(resolved_index)? {
self.execute_bound_stack_host_function(resolved_index, argc, call_ip)
} else {
self.execute_bound_host_function_from_stack(resolved_index, argc, call_ip)
}
}
pub(super) fn execute_builtin_override_call(
&mut self,
builtin_call_index: u16,
argc_u8: u8,
call_ip: usize,
) -> VmResult<HostCallExecOutcome> {
let resolved_index = self
.builtin_overrides
.get(&builtin_call_index)
.copied()
.ok_or_else(|| {
VmError::HostError(format!(
"missing builtin override slot for call index {builtin_call_index}"
))
})?;
let argc = argc_u8 as usize;
if self.bound_host_function_uses_args_slice(resolved_index)? {
self.execute_bound_args_host_function(resolved_index, argc, call_ip)
} else if self.bound_host_function_uses_stack_borrow(resolved_index)? {
self.execute_bound_stack_host_function(resolved_index, argc, call_ip)
} else {
self.execute_bound_host_function_from_stack(resolved_index, argc, call_ip)
}
}
fn execute_builtin_call_from_stack(
&mut self,
builtin: BuiltinFunction,
argc: usize,
call_ip: usize,
) -> VmResult<HostCallExecOutcome> {
let arg_start = self
.stack
.len()
.checked_sub(argc)
.ok_or(VmError::StackUnderflow)?;
let outcome = unsafe {
let args = std::slice::from_raw_parts_mut(self.stack.as_mut_ptr().add(arg_start), argc);
crate::builtins::runtime::execute_builtin_call(self, builtin, args)
}?;
match outcome {
crate::builtins::runtime::BuiltinCallOutcome::Return(values) => {
self.stack.truncate(arg_start);
values.push_onto_stack(&mut self.stack);
Ok(HostCallExecOutcome::Returned)
}
crate::builtins::runtime::BuiltinCallOutcome::Halt => {
self.stack.truncate(arg_start);
Ok(HostCallExecOutcome::Halted)
}
crate::builtins::runtime::BuiltinCallOutcome::Pending(op_id) => {
self.stack.truncate(arg_start);
let resume_ip = self.call_resume_ip(call_ip)?;
self.set_waiting_host_op(op_id, WaitingHostOpSource::BuiltinIo)?;
self.ip = resume_ip;
Ok(HostCallExecOutcome::Pending(op_id))
}
}
}
fn try_execute_typed_builtin_fast_path(
&mut self,
builtin: BuiltinFunction,
argc: usize,
call_ip: usize,
) -> VmResult<Option<HostCallExecOutcome>> {
let arg_start = self
.stack
.len()
.checked_sub(argc)
.ok_or(VmError::StackUnderflow)?;
let (lhs, rhs) = self.operand_value_types(call_ip);
let result = {
let args = &self.stack[arg_start..];
match builtin {
BuiltinFunction::Len => match (lhs, args) {
(
ValueType::String | ValueType::Bytes | ValueType::Array | ValueType::Map,
[value],
) => Self::fast_path_len_result(value),
_ => None,
},
BuiltinFunction::Slice => match (lhs, rhs, args) {
(ValueType::String, ValueType::Int, [source, start, length]) => {
Some(Self::fast_path_slice_string_result(source, start, length)?)
}
(ValueType::Array, ValueType::Int, [source, start, length]) => {
Some(Self::fast_path_slice_array_result(source, start, length)?)
}
(ValueType::Bytes, ValueType::Int, [source, start, length]) => {
Some(Self::fast_path_slice_bytes_result(source, start, length)?)
}
_ => None,
},
BuiltinFunction::Get => match (lhs, args) {
(
ValueType::String | ValueType::Bytes | ValueType::Array | ValueType::Map,
[container, key],
) => Self::fast_path_get_result(container, key)?,
_ => None,
},
BuiltinFunction::Has => match (lhs, args) {
(ValueType::Bytes | ValueType::Array | ValueType::Map, [container, key]) => {
Self::fast_path_has_result(container, key)?
}
_ => None,
},
BuiltinFunction::BytesFromArrayU8 => match (lhs, args) {
(ValueType::Array, [value]) => {
Some(Self::fast_path_bytes_from_array_u8_result(value)?)
}
_ => None,
},
BuiltinFunction::BytesToArrayU8 => match (lhs, args) {
(ValueType::Bytes, [value]) => {
Some(Self::fast_path_bytes_to_array_u8_result(value)?)
}
_ => None,
},
_ => None,
}
};
let Some(value) = result else {
return Ok(None);
};
self.stack.truncate(arg_start);
self.stack.push(value);
self.record_typed_builtin_fast_path();
Ok(Some(HostCallExecOutcome::Returned))
}
fn try_execute_builtin_projection_fast_path(
&mut self,
builtin: BuiltinFunction,
argc: usize,
) -> VmResult<Option<HostCallExecOutcome>> {
let arg_start = self
.stack
.len()
.checked_sub(argc)
.ok_or(VmError::StackUnderflow)?;
let result = {
let args = &self.stack[arg_start..];
match (builtin, args) {
(BuiltinFunction::Len, [value]) => Self::fast_path_len_result(value),
(BuiltinFunction::Get, [container, key]) => {
Self::fast_path_get_result(container, key)?
}
(BuiltinFunction::Has, [container, key]) => {
Self::fast_path_has_result(container, key)?
}
_ => None,
}
};
let Some(value) = result else {
return Ok(None);
};
self.stack.truncate(arg_start);
self.stack.push(value);
self.record_projection_fast_path();
Ok(Some(HostCallExecOutcome::Returned))
}
fn fast_path_len_result(value: &Value) -> Option<Value> {
match value {
Value::String(text) => Some(Value::Int(text.chars().count() as i64)),
Value::Bytes(values) => Some(Value::Int(values.len() as i64)),
Value::Array(values) => Some(Value::Int(values.len() as i64)),
Value::Map(entries) => Some(Value::Int(entries.len() as i64)),
_ => None,
}
}
fn fast_path_get_result(container: &Value, key: &Value) -> VmResult<Option<Value>> {
match container {
Value::Array(values) => {
let index = key.as_int()?;
if index < 0 {
return Err(VmError::HostError(
"array index must be non-negative".to_string(),
));
}
let index = usize::try_from(index)
.map_err(|_| VmError::HostError("array index overflow".to_string()))?;
let value = values.get(index).cloned().ok_or_else(|| {
VmError::HostError(format!("array index {index} out of bounds"))
})?;
Ok(Some(value))
}
Value::Map(entries) => {
let value = entries
.get(key)
.cloned()
.ok_or_else(|| VmError::HostError("map key not found".to_string()))?;
Ok(Some(value))
}
Value::Bytes(values) => {
let index = key.as_int()?;
if index < 0 {
return Err(VmError::HostError(
"bytes index must be non-negative".to_string(),
));
}
let index = usize::try_from(index)
.map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
let value = values.get(index).copied().ok_or_else(|| {
VmError::HostError(format!("bytes index {index} out of bounds"))
})?;
Ok(Some(Value::Int(i64::from(value))))
}
Value::String(text) => {
let index = key.as_int()?;
if index < 0 {
return Err(VmError::HostError(
"string index must be non-negative".to_string(),
));
}
let index = usize::try_from(index)
.map_err(|_| VmError::HostError("string index overflow".to_string()))?;
let value = text
.chars()
.nth(index)
.map(|ch| Value::string(ch.to_string()))
.ok_or_else(|| {
VmError::HostError(format!("string index {index} out of bounds"))
})?;
Ok(Some(value))
}
_ => Ok(None),
}
}
fn fast_path_has_result(container: &Value, key: &Value) -> VmResult<Option<Value>> {
match container {
Value::Array(values) => {
let index = key.as_int()?;
let present = if index < 0 {
false
} else {
usize::try_from(index)
.ok()
.is_some_and(|index| index < values.len())
};
Ok(Some(Value::Bool(present)))
}
Value::Bytes(values) => {
let index = key.as_int()?;
let present = if index < 0 {
false
} else {
usize::try_from(index)
.ok()
.is_some_and(|index| index < values.len())
};
Ok(Some(Value::Bool(present)))
}
Value::Map(entries) => Ok(Some(Value::Bool(entries.get(key).is_some()))),
_ => Ok(None),
}
}
fn fast_path_slice_bounds(start: i64, length: i64) -> VmResult<Option<(usize, usize)>> {
if start < 0 || length <= 0 {
return Ok(None);
}
let start = usize::try_from(start).map_err(|_| {
VmError::HostError("slice start overflow while converting to usize".to_string())
})?;
let length = usize::try_from(length).map_err(|_| {
VmError::HostError("slice length overflow while converting to usize".to_string())
})?;
Ok(Some((start, length)))
}
fn fast_path_slice_string_result(
source: &Value,
start: &Value,
length: &Value,
) -> VmResult<Value> {
let Value::String(text) = source else {
return Err(VmError::TypeMismatch("string"));
};
let start = start.as_int()?;
let length = length.as_int()?;
let Some((start, length)) = Self::fast_path_slice_bounds(start, length)? else {
return Ok(Value::string(String::new()));
};
Ok(Value::string(
text.chars().skip(start).take(length).collect::<String>(),
))
}
fn fast_path_slice_array_result(
source: &Value,
start: &Value,
length: &Value,
) -> VmResult<Value> {
let Value::Array(values) = source else {
return Err(VmError::TypeMismatch("array"));
};
let start = start.as_int()?;
let length = length.as_int()?;
let Some((start, length)) = Self::fast_path_slice_bounds(start, length)? else {
return Ok(Value::array(Vec::new()));
};
Ok(Value::array(
values
.iter()
.skip(start)
.take(length)
.cloned()
.collect::<Vec<_>>(),
))
}
fn fast_path_slice_bytes_result(
source: &Value,
start: &Value,
length: &Value,
) -> VmResult<Value> {
let Value::Bytes(values) = source else {
return Err(VmError::TypeMismatch("bytes"));
};
let start = start.as_int()?;
let length = length.as_int()?;
let Some((start, length)) = Self::fast_path_slice_bounds(start, length)? else {
return Ok(Value::bytes(Vec::new()));
};
Ok(Value::bytes(
values
.iter()
.skip(start)
.take(length)
.copied()
.collect::<Vec<_>>(),
))
}
fn fast_path_bytes_from_array_u8_result(value: &Value) -> VmResult<Value> {
let Value::Array(values) = value else {
return Err(VmError::TypeMismatch("array"));
};
let mut out = Vec::with_capacity(values.len());
for (index, value) in values.iter().enumerate() {
let Value::Int(value) = value else {
return Err(VmError::HostError(format!(
"bytes::from_array_u8 entry {index} must be an int in 0..=255"
)));
};
let value = u8::try_from(*value).map_err(|_| {
VmError::HostError(format!(
"bytes::from_array_u8 entry {index} must be an int in 0..=255"
))
})?;
out.push(value);
}
Ok(Value::bytes(out))
}
fn fast_path_bytes_to_array_u8_result(value: &Value) -> VmResult<Value> {
let Value::Bytes(payload) = value else {
return Err(VmError::TypeMismatch("bytes"));
};
Ok(Value::array(
payload
.iter()
.copied()
.map(|byte| Value::Int(i64::from(byte)))
.collect(),
))
}
pub(super) fn execute_bound_host_function_from_stack(
&mut self,
resolved_index: u16,
argc: usize,
call_ip: usize,
) -> VmResult<HostCallExecOutcome> {
let arg_start = self
.stack
.len()
.checked_sub(argc)
.ok_or(VmError::StackUnderflow)?;
let mut saved_stack = std::mem::take(&mut self.stack);
self.call_depth += 1;
let function_ptr =
self.host_functions
.get_mut(resolved_index as usize)
.ok_or(VmError::InvalidCall(resolved_index))? as *mut VmHostFunction;
let outcome = unsafe {
let args = &saved_stack[arg_start..];
match &mut *function_ptr {
VmHostFunction::Dynamic(function) => function.call(self, args),
VmHostFunction::Static(function) => function(self, args),
VmHostFunction::StackDynamic(_)
| VmHostFunction::StackStatic(_)
| VmHostFunction::ArgsDynamic(_)
| VmHostFunction::ArgsStatic(_) => unreachable!(),
}
};
self.call_depth = self.call_depth.saturating_sub(1);
let mut host_stack = std::mem::take(&mut self.stack);
let outcome = match outcome {
Ok(outcome) => outcome,
Err(err) => {
saved_stack.truncate(arg_start);
saved_stack.append(&mut host_stack);
self.stack = saved_stack;
return Err(err);
}
};
match outcome {
CallOutcome::Return(values) => {
saved_stack.truncate(arg_start);
saved_stack.append(&mut host_stack);
values.push_onto_stack(&mut saved_stack);
self.stack = saved_stack;
Ok(HostCallExecOutcome::Returned)
}
CallOutcome::Halt => {
saved_stack.truncate(arg_start);
saved_stack.append(&mut host_stack);
self.stack = saved_stack;
Ok(HostCallExecOutcome::Halted)
}
CallOutcome::Yield => {
saved_stack.append(&mut host_stack);
self.stack = saved_stack;
self.ip = call_ip;
Ok(HostCallExecOutcome::Yielded)
}
CallOutcome::Pending(op_id) => {
saved_stack.truncate(arg_start);
saved_stack.append(&mut host_stack);
self.stack = saved_stack;
let resume_ip = self.call_resume_ip(call_ip)?;
self.set_waiting_host_op(op_id, WaitingHostOpSource::HostBridge)?;
self.ip = resume_ip;
Ok(HostCallExecOutcome::Pending(op_id))
}
}
}
fn bound_host_function_uses_args_slice(&self, resolved_index: u16) -> VmResult<bool> {
let function = self
.host_functions
.get(resolved_index as usize)
.ok_or(VmError::InvalidCall(resolved_index))?;
Ok(matches!(
function,
VmHostFunction::ArgsDynamic(_) | VmHostFunction::ArgsStatic(_)
))
}
fn bound_host_function_uses_stack_borrow(&self, resolved_index: u16) -> VmResult<bool> {
let function = self
.host_functions
.get(resolved_index as usize)
.ok_or(VmError::InvalidCall(resolved_index))?;
Ok(matches!(
function,
VmHostFunction::StackDynamic(_) | VmHostFunction::StackStatic(_)
))
}
pub(super) fn execute_bound_args_host_function(
&mut self,
resolved_index: u16,
argc: usize,
call_ip: usize,
) -> VmResult<HostCallExecOutcome> {
let arg_start = self
.stack
.len()
.checked_sub(argc)
.ok_or(VmError::StackUnderflow)?;
self.call_depth += 1;
let outcome = {
let args = &self.stack[arg_start..];
let function = self
.host_functions
.get_mut(resolved_index as usize)
.ok_or(VmError::InvalidCall(resolved_index))?;
match function {
VmHostFunction::ArgsDynamic(function) => function.call(args),
VmHostFunction::ArgsStatic(function) => function(args),
VmHostFunction::Dynamic(_)
| VmHostFunction::Static(_)
| VmHostFunction::StackDynamic(_)
| VmHostFunction::StackStatic(_) => unreachable!(),
}
};
self.call_depth = self.call_depth.saturating_sub(1);
let outcome = outcome?;
match outcome {
CallOutcome::Return(values) => {
self.stack.truncate(arg_start);
values.push_onto_stack(&mut self.stack);
Ok(HostCallExecOutcome::Returned)
}
CallOutcome::Halt => {
self.stack.truncate(arg_start);
Ok(HostCallExecOutcome::Halted)
}
CallOutcome::Yield => {
self.ip = call_ip;
Ok(HostCallExecOutcome::Yielded)
}
CallOutcome::Pending(op_id) => {
self.stack.truncate(arg_start);
let resume_ip = self.call_resume_ip(call_ip)?;
self.set_waiting_host_op(op_id, WaitingHostOpSource::HostBridge)?;
self.ip = resume_ip;
Ok(HostCallExecOutcome::Pending(op_id))
}
}
}
pub(super) fn execute_bound_stack_host_function(
&mut self,
resolved_index: u16,
argc: usize,
call_ip: usize,
) -> VmResult<HostCallExecOutcome> {
let arg_start = self
.stack
.len()
.checked_sub(argc)
.ok_or(VmError::StackUnderflow)?;
self.call_depth += 1;
let function_ptr =
self.host_functions
.get_mut(resolved_index as usize)
.ok_or(VmError::InvalidCall(resolved_index))? as *mut VmHostFunction;
let outcome = unsafe {
let args = std::slice::from_raw_parts(self.stack.as_ptr().add(arg_start), argc);
match &mut *function_ptr {
VmHostFunction::StackDynamic(function) => function.call(self, args),
VmHostFunction::StackStatic(function) => function(self, args),
VmHostFunction::Dynamic(_)
| VmHostFunction::Static(_)
| VmHostFunction::ArgsDynamic(_)
| VmHostFunction::ArgsStatic(_) => unreachable!(),
}
};
self.call_depth = self.call_depth.saturating_sub(1);
let outcome = outcome?;
match outcome {
CallOutcome::Return(values) => {
self.stack.truncate(arg_start);
values.push_onto_stack(&mut self.stack);
Ok(HostCallExecOutcome::Returned)
}
CallOutcome::Halt => {
self.stack.truncate(arg_start);
Ok(HostCallExecOutcome::Halted)
}
CallOutcome::Yield => {
self.ip = call_ip;
Ok(HostCallExecOutcome::Yielded)
}
CallOutcome::Pending(op_id) => {
self.stack.truncate(arg_start);
let resume_ip = self.call_resume_ip(call_ip)?;
self.set_waiting_host_op(op_id, WaitingHostOpSource::HostBridge)?;
self.ip = resume_ip;
Ok(HostCallExecOutcome::Pending(op_id))
}
}
}
pub(super) fn call_resume_ip(&self, call_ip: usize) -> VmResult<usize> {
let resume_ip = call_ip.checked_add(4).ok_or(VmError::BytecodeBounds)?;
if resume_ip > self.program.code.len() {
return Err(VmError::BytecodeBounds);
}
Ok(resume_ip)
}
pub(super) fn set_waiting_host_op(
&mut self,
op_id: HostOpId,
source: WaitingHostOpSource,
) -> VmResult<()> {
if let Some(active) = self.waiting_host_op
&& active.op_id != op_id
{
return Err(VmError::HostError(format!(
"vm already waiting on host op {}, cannot wait on {}",
active.op_id, op_id
)));
}
self.waiting_host_op = Some(WaitingHostOp { op_id, source });
Ok(())
}
pub(super) fn complete_waiting_host_op(
&mut self,
op_id: HostOpId,
values: CallReturn,
) -> VmResult<()> {
let waiting = self.waiting_host_op.ok_or_else(|| {
VmError::HostError(format!(
"host op {} completed but vm is not waiting on any op",
op_id
))
})?;
if waiting.op_id != op_id {
return Err(VmError::HostError(format!(
"host op {} completed while vm waits on {}",
op_id, waiting.op_id
)));
}
self.waiting_host_op = None;
values.push_onto_stack(&mut self.stack);
Ok(())
}
pub(super) fn install_resolved_calls(&mut self, resolved_calls: Vec<u16>) -> VmResult<()> {
if self.program.imports.len() != resolved_calls.len() {
return Err(VmError::HostError(format!(
"resolved call cache size mismatch: expected {}, got {}",
self.program.imports.len(),
resolved_calls.len()
)));
}
for &index in &resolved_calls {
if index as usize >= self.host_functions.len() {
return Err(VmError::InvalidCall(index));
}
}
self.resolved_calls = resolved_calls;
self.resolved_calls_dirty = false;
Ok(())
}
pub(super) fn ensure_call_bindings(&mut self) -> VmResult<()> {
if self.program.imports.is_empty() || !self.resolved_calls_dirty {
return Ok(());
}
if self.host_function_symbols.is_empty() && self.host_functions.is_empty() {
let import_names = self
.program
.imports
.iter()
.map(|import| import.name.clone())
.collect::<Vec<_>>();
for name in import_names {
let _ = crate::builtins::runtime::bind_default_host_function(self, &name);
}
}
let use_legacy_order = self.host_function_symbols.is_empty();
let mut resolved = Vec::with_capacity(self.program.imports.len());
let imports = self.program.imports.clone();
for (index, import) in imports.iter().enumerate() {
if use_legacy_order {
if index >= self.host_functions.len() {
return Err(VmError::InvalidCall(index as u16));
}
resolved.push(index as u16);
continue;
}
let bound = if let Some(bound) = self.host_function_symbols.get(&import.name).copied() {
bound
} else if crate::builtins::runtime::bind_default_host_function(self, &import.name) {
self.host_function_symbols
.get(&import.name)
.copied()
.ok_or_else(|| VmError::UnboundImport(import.name.clone()))?
} else {
return Err(VmError::UnboundImport(import.name.clone()));
};
resolved.push(bound);
}
self.resolved_calls = resolved;
self.resolved_calls_dirty = false;
Ok(())
}
pub(super) fn resolve_call_target(&mut self, index: u16, argc: u8) -> VmResult<u16> {
if self.program.imports.is_empty() {
return Ok(index);
}
self.ensure_call_bindings()?;
let import = self
.program
.imports
.get(index as usize)
.ok_or(VmError::InvalidCall(index))?;
if import.arity != argc {
return Err(VmError::InvalidCallArity {
import: import.name.clone(),
expected: import.arity,
got: argc,
});
}
self.resolved_calls
.get(index as usize)
.copied()
.ok_or(VmError::InvalidCall(index))
}
}