Skip to main content

vm/vm/
host.rs

1use std::sync::{Arc, OnceLock, RwLock};
2use std::task::{Context, Poll, Wake, Waker};
3
4use crate::builtins::BuiltinFunction;
5
6use super::*;
7
8pub type HostOpId = u64;
9
10#[derive(Clone, Debug, Default, PartialEq)]
11pub enum CallReturn {
12    #[default]
13    None,
14    One(Value),
15}
16
17impl CallReturn {
18    pub fn none() -> Self {
19        Self::None
20    }
21
22    pub fn one(value: Value) -> Self {
23        Self::One(value)
24    }
25
26    pub fn from_values(values: Vec<Value>) -> Self {
27        match values.len() {
28            0 => Self::None,
29            1 => Self::One(
30                values
31                    .into_iter()
32                    .next()
33                    .expect("single-value return should contain one value"),
34            ),
35            _ => Self::One(Value::array(values)),
36        }
37    }
38
39    pub fn is_empty(&self) -> bool {
40        matches!(self, Self::None)
41    }
42
43    pub fn as_slice(&self) -> &[Value] {
44        match self {
45            Self::None => &[],
46            Self::One(value) => std::slice::from_ref(value),
47        }
48    }
49
50    pub(crate) fn push_onto_stack(self, stack: &mut Vec<Value>) {
51        match self {
52            Self::None => {}
53            Self::One(value) => stack.push(value),
54        }
55    }
56}
57
58impl From<Vec<Value>> for CallReturn {
59    fn from(values: Vec<Value>) -> Self {
60        Self::from_values(values)
61    }
62}
63
64#[derive(Debug, PartialEq)]
65pub enum CallOutcome {
66    Return(CallReturn),
67    Halt,
68    Yield,
69    Pending(HostOpId),
70}
71
72pub trait HostFunction: Send {
73    fn call(&mut self, vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome>;
74}
75
76/// VM-aware host functions that opt into borrowed stack-tail dispatch.
77///
78/// Implementations must not re-enter the VM or otherwise structurally mutate
79/// the value stack while `args` is borrowed for the duration of `call`.
80pub trait HostStackFunction: Send {
81    fn call(&mut self, vm: &mut Vm, args: &[Value]) -> VmResult<CallOutcome>;
82}
83
84pub trait HostArgsFunction: Send {
85    fn call(&mut self, args: &[Value]) -> VmResult<CallOutcome>;
86}
87
88pub trait HostAsyncBridge: Send {
89    fn poll_op(&mut self, op_id: HostOpId, cx: &mut Context<'_>) -> Poll<VmResult<CallReturn>>;
90
91    fn cancel_op(&mut self, _op_id: HostOpId) {}
92}
93
94pub type StaticHostFunction = fn(&mut Vm, &[Value]) -> VmResult<CallOutcome>;
95pub type StaticHostStackFunction = fn(&mut Vm, &[Value]) -> VmResult<CallOutcome>;
96pub type StaticHostArgsFunction = fn(&[Value]) -> VmResult<CallOutcome>;
97
98type HostFactory = dyn Fn() -> Box<dyn HostFunction> + Send + Sync;
99type HostStackFactory = dyn Fn() -> Box<dyn HostStackFunction> + Send + Sync;
100type HostArgsFactory = dyn Fn() -> Box<dyn HostArgsFunction> + Send + Sync;
101
102#[derive(Clone)]
103enum RegistryEntryKind {
104    Factory(Arc<HostFactory>),
105    Static(StaticHostFunction),
106    StackFactory(Arc<HostStackFactory>),
107    StackStatic(StaticHostStackFunction),
108    ArgsFactory(Arc<HostArgsFactory>),
109    ArgsStatic(StaticHostArgsFunction),
110    ArgsStaticNonYielding(StaticHostArgsFunction),
111}
112
113#[derive(Clone)]
114struct RegistryEntry {
115    arity: u8,
116    kind: RegistryEntryKind,
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
120pub struct HostBindingPlan {
121    import_signature: Vec<HostImport>,
122    registry_slots: Vec<u16>,
123    resolved_calls: Vec<u16>,
124}
125
126#[derive(Clone)]
127pub struct HostFunctionRegistry {
128    entries: Arc<Vec<RegistryEntry>>,
129    by_name: Arc<HashMap<String, u16>>,
130    plan_cache: Arc<RwLock<HashMap<Vec<HostImport>, Arc<HostBindingPlan>>>>,
131}
132
133impl Default for HostFunctionRegistry {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139impl HostFunctionRegistry {
140    fn empty() -> Self {
141        Self {
142            entries: Arc::new(Vec::new()),
143            by_name: Arc::new(HashMap::new()),
144            plan_cache: Arc::new(RwLock::new(HashMap::new())),
145        }
146    }
147
148    pub fn new() -> Self {
149        static DEFAULT_REGISTRY: OnceLock<HostFunctionRegistry> = OnceLock::new();
150
151        DEFAULT_REGISTRY
152            .get_or_init(|| {
153                let mut registry = Self::empty();
154                crate::builtins::runtime::register_default_host_functions(&mut registry);
155                registry
156            })
157            .clone()
158    }
159
160    fn invalidate_plan_cache(&mut self) {
161        self.plan_cache = Arc::new(RwLock::new(HashMap::new()));
162    }
163
164    pub fn register<F>(&mut self, name: impl Into<String>, arity: u8, factory: F)
165    where
166        F: Fn() -> Box<dyn HostFunction> + Send + Sync + 'static,
167    {
168        let name = name.into();
169        if let Some(&slot) = self.by_name.get(&name)
170            && let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
171        {
172            entry.arity = arity;
173            entry.kind = RegistryEntryKind::Factory(Arc::new(factory));
174            self.invalidate_plan_cache();
175            return;
176        }
177
178        let entries = Arc::make_mut(&mut self.entries);
179        let slot = entries.len() as u16;
180        entries.push(RegistryEntry {
181            arity,
182            kind: RegistryEntryKind::Factory(Arc::new(factory)),
183        });
184        Arc::make_mut(&mut self.by_name).insert(name, slot);
185        self.invalidate_plan_cache();
186    }
187
188    pub fn register_static(
189        &mut self,
190        name: impl Into<String>,
191        arity: u8,
192        function: StaticHostFunction,
193    ) {
194        let name = name.into();
195        if let Some(&slot) = self.by_name.get(&name)
196            && let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
197        {
198            entry.arity = arity;
199            entry.kind = RegistryEntryKind::Static(function);
200            self.invalidate_plan_cache();
201            return;
202        }
203
204        let entries = Arc::make_mut(&mut self.entries);
205        let slot = entries.len() as u16;
206        entries.push(RegistryEntry {
207            arity,
208            kind: RegistryEntryKind::Static(function),
209        });
210        Arc::make_mut(&mut self.by_name).insert(name, slot);
211        self.invalidate_plan_cache();
212    }
213
214    pub fn register_stack<F>(&mut self, name: impl Into<String>, arity: u8, factory: F)
215    where
216        F: Fn() -> Box<dyn HostStackFunction> + Send + Sync + 'static,
217    {
218        let name = name.into();
219        if let Some(&slot) = self.by_name.get(&name)
220            && let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
221        {
222            entry.arity = arity;
223            entry.kind = RegistryEntryKind::StackFactory(Arc::new(factory));
224            self.invalidate_plan_cache();
225            return;
226        }
227
228        let entries = Arc::make_mut(&mut self.entries);
229        let slot = entries.len() as u16;
230        entries.push(RegistryEntry {
231            arity,
232            kind: RegistryEntryKind::StackFactory(Arc::new(factory)),
233        });
234        Arc::make_mut(&mut self.by_name).insert(name, slot);
235        self.invalidate_plan_cache();
236    }
237
238    pub fn register_static_stack(
239        &mut self,
240        name: impl Into<String>,
241        arity: u8,
242        function: StaticHostStackFunction,
243    ) {
244        let name = name.into();
245        if let Some(&slot) = self.by_name.get(&name)
246            && let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
247        {
248            entry.arity = arity;
249            entry.kind = RegistryEntryKind::StackStatic(function);
250            self.invalidate_plan_cache();
251            return;
252        }
253
254        let entries = Arc::make_mut(&mut self.entries);
255        let slot = entries.len() as u16;
256        entries.push(RegistryEntry {
257            arity,
258            kind: RegistryEntryKind::StackStatic(function),
259        });
260        Arc::make_mut(&mut self.by_name).insert(name, slot);
261        self.invalidate_plan_cache();
262    }
263
264    pub fn register_args<F>(&mut self, name: impl Into<String>, arity: u8, factory: F)
265    where
266        F: Fn() -> Box<dyn HostArgsFunction> + Send + Sync + 'static,
267    {
268        let name = name.into();
269        if let Some(&slot) = self.by_name.get(&name)
270            && let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
271        {
272            entry.arity = arity;
273            entry.kind = RegistryEntryKind::ArgsFactory(Arc::new(factory));
274            self.invalidate_plan_cache();
275            return;
276        }
277
278        let entries = Arc::make_mut(&mut self.entries);
279        let slot = entries.len() as u16;
280        entries.push(RegistryEntry {
281            arity,
282            kind: RegistryEntryKind::ArgsFactory(Arc::new(factory)),
283        });
284        Arc::make_mut(&mut self.by_name).insert(name, slot);
285        self.invalidate_plan_cache();
286    }
287
288    pub fn register_static_args(
289        &mut self,
290        name: impl Into<String>,
291        arity: u8,
292        function: StaticHostArgsFunction,
293    ) {
294        let name = name.into();
295        if let Some(&slot) = self.by_name.get(&name)
296            && let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
297        {
298            entry.arity = arity;
299            entry.kind = RegistryEntryKind::ArgsStatic(function);
300            self.invalidate_plan_cache();
301            return;
302        }
303
304        let entries = Arc::make_mut(&mut self.entries);
305        let slot = entries.len() as u16;
306        entries.push(RegistryEntry {
307            arity,
308            kind: RegistryEntryKind::ArgsStatic(function),
309        });
310        Arc::make_mut(&mut self.by_name).insert(name, slot);
311        self.invalidate_plan_cache();
312    }
313
314    /// Registers a static args-only host function that always returns one value synchronously.
315    ///
316    /// The returned [`Value`] must match the return type declared by the corresponding host
317    /// import. Returning a different type is reported as [`VmError::TypeMismatch`]. Returning no
318    /// value, `Halt`, `Yield`, or `Pending` violates the contract and is reported as a host error.
319    /// When appropriate, the native JIT may keep traces active across the call boundary.
320    pub fn register_static_non_yielding_args(
321        &mut self,
322        name: impl Into<String>,
323        arity: u8,
324        function: StaticHostArgsFunction,
325    ) {
326        let name = name.into();
327        if let Some(&slot) = self.by_name.get(&name)
328            && let Some(entry) = Arc::make_mut(&mut self.entries).get_mut(slot as usize)
329        {
330            entry.arity = arity;
331            entry.kind = RegistryEntryKind::ArgsStaticNonYielding(function);
332            self.invalidate_plan_cache();
333            return;
334        }
335
336        let entries = Arc::make_mut(&mut self.entries);
337        let slot = entries.len() as u16;
338        entries.push(RegistryEntry {
339            arity,
340            kind: RegistryEntryKind::ArgsStaticNonYielding(function),
341        });
342        Arc::make_mut(&mut self.by_name).insert(name, slot);
343        self.invalidate_plan_cache();
344    }
345
346    pub fn bind_vm_cached(&self, vm: &mut Vm) -> VmResult<()> {
347        let plan = self.prepare_shared_plan(&vm.program.imports)?;
348        self.bind_vm_with_plan(vm, &plan)
349    }
350
351    pub fn prepare_plan(&self, imports: &[HostImport]) -> VmResult<HostBindingPlan> {
352        Ok(self.prepare_shared_plan(imports)?.as_ref().clone())
353    }
354
355    pub fn prepare_shared_plan(&self, imports: &[HostImport]) -> VmResult<Arc<HostBindingPlan>> {
356        self.plan_for_imports(imports)
357    }
358
359    fn plan_for_imports(&self, imports: &[HostImport]) -> VmResult<Arc<HostBindingPlan>> {
360        if let Some(plan) = self
361            .plan_cache
362            .read()
363            .expect("host binding plan cache read lock should not be poisoned")
364            .get(imports)
365            .cloned()
366        {
367            return Ok(plan);
368        }
369
370        let mut registry_slot_to_vm_slot: HashMap<u16, u16> = HashMap::new();
371        let mut registry_slots = Vec::new();
372        let mut resolved_calls = Vec::with_capacity(imports.len());
373
374        for import in imports {
375            let registry_slot = self
376                .by_name
377                .get(&import.name)
378                .copied()
379                .ok_or_else(|| VmError::UnboundImport(import.name.clone()))?;
380            let entry = self
381                .entries
382                .get(registry_slot as usize)
383                .ok_or(VmError::InvalidCall(registry_slot))?;
384            if entry.arity != import.arity {
385                return Err(VmError::InvalidCallArity {
386                    import: import.name.clone(),
387                    expected: entry.arity,
388                    got: import.arity,
389                });
390            }
391
392            let vm_slot = if let Some(&existing) = registry_slot_to_vm_slot.get(&registry_slot) {
393                existing
394            } else {
395                let slot = registry_slots.len() as u16;
396                registry_slots.push(registry_slot);
397                registry_slot_to_vm_slot.insert(registry_slot, slot);
398                slot
399            };
400            resolved_calls.push(vm_slot);
401        }
402
403        let import_key = imports.to_vec();
404        let computed = Arc::new(HostBindingPlan {
405            import_signature: import_key.clone(),
406            registry_slots,
407            resolved_calls,
408        });
409        let mut cache = self
410            .plan_cache
411            .write()
412            .expect("host binding plan cache write lock should not be poisoned");
413        Ok(cache.entry(import_key).or_insert_with(|| computed).clone())
414    }
415
416    pub fn bind_vm_with_plan(&self, vm: &mut Vm, plan: &HostBindingPlan) -> VmResult<()> {
417        if vm.program.imports != plan.import_signature {
418            return Err(VmError::HostError(
419                "host binding plan does not match vm import signature".to_string(),
420            ));
421        }
422        if !vm.host_functions.is_empty() || !vm.host_function_symbols.is_empty() {
423            return Err(VmError::HostError(
424                "host binding cache requires an unbound vm".to_string(),
425            ));
426        }
427
428        vm.host_functions.reserve(plan.registry_slots.len());
429        for &registry_slot in &plan.registry_slots {
430            let entry = self
431                .entries
432                .get(registry_slot as usize)
433                .ok_or(VmError::InvalidCall(registry_slot))?;
434            match &entry.kind {
435                RegistryEntryKind::Factory(factory) => {
436                    vm.register_function(factory());
437                }
438                RegistryEntryKind::Static(function) => {
439                    vm.register_static_function(*function);
440                }
441                RegistryEntryKind::StackFactory(factory) => {
442                    vm.register_stack_function(factory());
443                }
444                RegistryEntryKind::StackStatic(function) => {
445                    vm.register_static_stack_function(*function);
446                }
447                RegistryEntryKind::ArgsFactory(factory) => {
448                    vm.register_args_function(factory());
449                }
450                RegistryEntryKind::ArgsStatic(function) => {
451                    vm.register_static_args_function(*function);
452                }
453                RegistryEntryKind::ArgsStaticNonYielding(function) => {
454                    vm.register_static_non_yielding_args_function(*function);
455                }
456            }
457        }
458        vm.install_resolved_calls(plan.resolved_calls.clone())?;
459        Ok(())
460    }
461}
462
463pub(super) enum VmHostFunction {
464    Dynamic(Box<dyn HostFunction>),
465    Static(StaticHostFunction),
466    StackDynamic(Box<dyn HostStackFunction>),
467    StackStatic(StaticHostStackFunction),
468    ArgsDynamic(Box<dyn HostArgsFunction>),
469    ArgsStatic(StaticHostArgsFunction),
470    ArgsStaticNonYielding(StaticHostArgsFunction),
471}
472
473pub(super) enum HostCallExecOutcome {
474    Returned,
475    Halted,
476    Yielded,
477    Pending(HostOpId),
478}
479
480pub(crate) fn require_non_yielding_host_value(outcome: CallOutcome) -> VmResult<Value> {
481    match outcome {
482        CallOutcome::Return(CallReturn::One(value)) => Ok(value),
483        CallOutcome::Return(CallReturn::None) => Err(VmError::HostError(
484            "non-yielding host function returned no value".to_string(),
485        )),
486        CallOutcome::Halt => Err(VmError::HostError(
487            "non-yielding host function returned halt".to_string(),
488        )),
489        CallOutcome::Yield => Err(VmError::HostError(
490            "non-yielding host function returned yield".to_string(),
491        )),
492        CallOutcome::Pending(_) => Err(VmError::HostError(
493            "non-yielding host function returned pending".to_string(),
494        )),
495    }
496}
497
498pub(crate) fn validate_non_yielding_host_value(
499    value: Value,
500    expected: Option<ValueType>,
501) -> VmResult<Value> {
502    let valid = matches!(
503        (expected, &value),
504        (None | Some(ValueType::Unknown), _)
505            | (Some(ValueType::Null), Value::Null)
506            | (Some(ValueType::Int), Value::Int(_))
507            | (Some(ValueType::Float), Value::Float(_))
508            | (Some(ValueType::Bool), Value::Bool(_))
509            | (Some(ValueType::String), Value::String(_))
510            | (Some(ValueType::Bytes), Value::Bytes(_))
511            | (Some(ValueType::Array), Value::Array(_))
512            | (Some(ValueType::Map), Value::Map(_))
513            | (Some(ValueType::Callable), Value::Callable(_))
514    );
515    if valid {
516        return Ok(value);
517    }
518    let expected = match expected.expect("known expected host return type") {
519        ValueType::Unknown => unreachable!(),
520        ValueType::Null => "null",
521        ValueType::Int => "int",
522        ValueType::Float => "float",
523        ValueType::Bool => "bool",
524        ValueType::String => "string",
525        ValueType::Bytes => "bytes",
526        ValueType::Array => "array",
527        ValueType::Map => "map",
528        ValueType::Callable => "callable",
529    };
530    Err(VmError::TypeMismatch(expected))
531}
532
533#[derive(Clone, Copy, Debug, PartialEq, Eq)]
534pub(super) struct WaitingHostOp {
535    pub(super) op_id: HostOpId,
536    pub(super) source: WaitingHostOpSource,
537}
538
539#[derive(Clone, Copy, Debug, PartialEq, Eq)]
540pub(super) enum WaitingHostOpSource {
541    HostBridge,
542    BuiltinIo,
543}
544
545struct NoopWake;
546
547impl Wake for NoopWake {
548    fn wake(self: Arc<Self>) {}
549}
550
551fn noop_waker() -> Waker {
552    Waker::from(Arc::new(NoopWake))
553}
554
555#[inline]
556fn builtin_for_binding_name(name: &str) -> Option<BuiltinFunction> {
557    if !name.contains("::") {
558        return None;
559    }
560    BuiltinFunction::from_namespaced_name(name)
561}
562
563impl Vm {
564    pub fn register_function(&mut self, function: Box<dyn HostFunction>) -> u16 {
565        let index = self.host_functions.len() as u16;
566        self.host_functions.push(VmHostFunction::Dynamic(function));
567        self.resolved_calls_dirty = true;
568        index
569    }
570
571    pub fn register_static_function(&mut self, function: StaticHostFunction) -> u16 {
572        let index = self.host_functions.len() as u16;
573        self.host_functions.push(VmHostFunction::Static(function));
574        self.resolved_calls_dirty = true;
575        index
576    }
577
578    pub fn register_stack_function(&mut self, function: Box<dyn HostStackFunction>) -> u16 {
579        let index = self.host_functions.len() as u16;
580        self.host_functions
581            .push(VmHostFunction::StackDynamic(function));
582        self.resolved_calls_dirty = true;
583        index
584    }
585
586    pub fn register_static_stack_function(&mut self, function: StaticHostStackFunction) -> u16 {
587        let index = self.host_functions.len() as u16;
588        self.host_functions
589            .push(VmHostFunction::StackStatic(function));
590        self.resolved_calls_dirty = true;
591        index
592    }
593
594    pub fn register_args_function(&mut self, function: Box<dyn HostArgsFunction>) -> u16 {
595        let index = self.host_functions.len() as u16;
596        self.host_functions
597            .push(VmHostFunction::ArgsDynamic(function));
598        self.resolved_calls_dirty = true;
599        index
600    }
601
602    pub fn register_static_args_function(&mut self, function: StaticHostArgsFunction) -> u16 {
603        let index = self.host_functions.len() as u16;
604        self.host_functions
605            .push(VmHostFunction::ArgsStatic(function));
606        self.resolved_calls_dirty = true;
607        index
608    }
609
610    /// Registers a static args-only host function that always returns one value synchronously.
611    ///
612    /// When used to resolve a declared host import, the returned [`Value`] must match that
613    /// import's return type. Returning a different type is reported as
614    /// [`VmError::TypeMismatch`]. Returning no value, `Halt`, `Yield`, or `Pending` violates the
615    /// contract and is a host error.
616    pub fn register_static_non_yielding_args_function(
617        &mut self,
618        function: StaticHostArgsFunction,
619    ) -> u16 {
620        let index = self.host_functions.len() as u16;
621        self.host_functions
622            .push(VmHostFunction::ArgsStaticNonYielding(function));
623        self.resolved_calls_dirty = true;
624        index
625    }
626
627    pub fn bind_function(&mut self, name: impl Into<String>, function: Box<dyn HostFunction>) {
628        let name = name.into();
629        if let Some(builtin) = builtin_for_binding_name(&name) {
630            self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Dynamic(function));
631            return;
632        }
633        if let Some(&index) = self.host_function_symbols.get(&name)
634            && let Some(slot) = self.host_functions.get_mut(index as usize)
635        {
636            *slot = VmHostFunction::Dynamic(function);
637            self.resolved_calls_dirty = true;
638            return;
639        }
640
641        let index = self.register_function(function);
642        self.host_function_symbols.insert(name, index);
643        self.resolved_calls_dirty = true;
644    }
645
646    pub fn bind_static_function(&mut self, name: impl Into<String>, function: StaticHostFunction) {
647        let name = name.into();
648        if let Some(builtin) = builtin_for_binding_name(&name) {
649            self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Static(function));
650            return;
651        }
652        if let Some(&index) = self.host_function_symbols.get(&name)
653            && let Some(slot) = self.host_functions.get_mut(index as usize)
654        {
655            *slot = VmHostFunction::Static(function);
656            self.resolved_calls_dirty = true;
657            return;
658        }
659
660        let index = self.register_static_function(function);
661        self.host_function_symbols.insert(name, index);
662        self.resolved_calls_dirty = true;
663    }
664
665    pub fn bind_stack_function(
666        &mut self,
667        name: impl Into<String>,
668        function: Box<dyn HostStackFunction>,
669    ) {
670        let name = name.into();
671        if let Some(&index) = self.host_function_symbols.get(&name)
672            && let Some(slot) = self.host_functions.get_mut(index as usize)
673        {
674            *slot = VmHostFunction::StackDynamic(function);
675            self.resolved_calls_dirty = true;
676            return;
677        }
678
679        let index = self.register_stack_function(function);
680        self.host_function_symbols.insert(name, index);
681        self.resolved_calls_dirty = true;
682    }
683
684    pub fn bind_static_stack_function(
685        &mut self,
686        name: impl Into<String>,
687        function: StaticHostStackFunction,
688    ) {
689        let name = name.into();
690        if let Some(builtin) = builtin_for_binding_name(&name) {
691            self.bind_builtin_overrideslot(
692                builtin.call_index(),
693                VmHostFunction::StackStatic(function),
694            );
695            return;
696        }
697        if let Some(&index) = self.host_function_symbols.get(&name)
698            && let Some(slot) = self.host_functions.get_mut(index as usize)
699        {
700            *slot = VmHostFunction::StackStatic(function);
701            self.resolved_calls_dirty = true;
702            return;
703        }
704
705        let index = self.register_static_stack_function(function);
706        self.host_function_symbols.insert(name, index);
707        self.resolved_calls_dirty = true;
708    }
709
710    pub fn bind_args_function(
711        &mut self,
712        name: impl Into<String>,
713        function: Box<dyn HostArgsFunction>,
714    ) {
715        let name = name.into();
716        if let Some(builtin) = builtin_for_binding_name(&name) {
717            self.bind_builtin_overrideslot(
718                builtin.call_index(),
719                VmHostFunction::ArgsDynamic(function),
720            );
721            return;
722        }
723        if let Some(&index) = self.host_function_symbols.get(&name)
724            && let Some(slot) = self.host_functions.get_mut(index as usize)
725        {
726            *slot = VmHostFunction::ArgsDynamic(function);
727            self.resolved_calls_dirty = true;
728            return;
729        }
730
731        let index = self.register_args_function(function);
732        self.host_function_symbols.insert(name, index);
733        self.resolved_calls_dirty = true;
734    }
735
736    pub fn bind_static_args_function(
737        &mut self,
738        name: impl Into<String>,
739        function: StaticHostArgsFunction,
740    ) {
741        let name = name.into();
742        if let Some(builtin) = builtin_for_binding_name(&name) {
743            self.bind_builtin_overrideslot(
744                builtin.call_index(),
745                VmHostFunction::ArgsStatic(function),
746            );
747            return;
748        }
749        if let Some(&index) = self.host_function_symbols.get(&name)
750            && let Some(slot) = self.host_functions.get_mut(index as usize)
751        {
752            *slot = VmHostFunction::ArgsStatic(function);
753            self.resolved_calls_dirty = true;
754            return;
755        }
756
757        let index = self.register_static_args_function(function);
758        self.host_function_symbols.insert(name, index);
759        self.resolved_calls_dirty = true;
760    }
761
762    /// Binds a static args-only host function that always returns one value synchronously.
763    ///
764    /// This is equivalent to [`Vm::bind_static_args_function`] except that the VM may keep
765    /// native JIT traces active across the call boundary. The returned [`Value`] must match the
766    /// return type declared by the corresponding host import. Returning a different type, no
767    /// value, `Halt`, `Yield`, or `Pending` violates the contract and is reported as a host error.
768    pub fn bind_static_non_yielding_args_function(
769        &mut self,
770        name: impl Into<String>,
771        function: StaticHostArgsFunction,
772    ) {
773        let name = name.into();
774        if let Some(builtin) = builtin_for_binding_name(&name) {
775            self.bind_builtin_overrideslot(
776                builtin.call_index(),
777                VmHostFunction::ArgsStaticNonYielding(function),
778            );
779            return;
780        }
781        if let Some(&index) = self.host_function_symbols.get(&name)
782            && let Some(slot) = self.host_functions.get_mut(index as usize)
783        {
784            *slot = VmHostFunction::ArgsStaticNonYielding(function);
785            self.resolved_calls_dirty = true;
786            return;
787        }
788
789        let index = self.register_static_non_yielding_args_function(function);
790        self.host_function_symbols.insert(name, index);
791        self.resolved_calls_dirty = true;
792    }
793
794    pub fn bind_builtin_override(
795        &mut self,
796        name: impl Into<String>,
797        function: Box<dyn HostFunction>,
798    ) -> VmResult<()> {
799        let name = name.into();
800        let builtin = BuiltinFunction::from_namespaced_name(&name).ok_or_else(|| {
801            VmError::HostError(format!("unknown namespaced builtin override '{name}'"))
802        })?;
803        self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Dynamic(function));
804        Ok(())
805    }
806
807    pub fn bind_builtin_static_override(
808        &mut self,
809        name: impl Into<String>,
810        function: StaticHostFunction,
811    ) -> VmResult<()> {
812        let name = name.into();
813        let builtin = BuiltinFunction::from_namespaced_name(&name).ok_or_else(|| {
814            VmError::HostError(format!("unknown namespaced builtin override '{name}'"))
815        })?;
816        self.bind_builtin_overrideslot(builtin.call_index(), VmHostFunction::Static(function));
817        Ok(())
818    }
819
820    fn bind_builtin_overrideslot(&mut self, builtin_call_index: u16, function: VmHostFunction) {
821        if let Some(&host_slot) = self.builtin_overrides.get(&builtin_call_index)
822            && let Some(slot) = self.host_functions.get_mut(host_slot as usize)
823        {
824            *slot = function;
825            return;
826        }
827
828        let host_slot = self.host_functions.len() as u16;
829        self.host_functions.push(function);
830        self.builtin_overrides.insert(builtin_call_index, host_slot);
831    }
832
833    pub fn set_async_bridge(&mut self, bridge: Box<dyn HostAsyncBridge>) {
834        self.cancel_waiting_host_op();
835        self.async_bridge = Some(bridge);
836    }
837
838    pub fn clear_async_bridge(&mut self) {
839        self.cancel_waiting_host_op();
840        self.async_bridge = None;
841    }
842
843    pub fn set_runtime_print_sink<F>(&mut self, sink: F)
844    where
845        F: FnMut(String) + Send + 'static,
846    {
847        self.runtime_print_sink = Some(Box::new(sink));
848    }
849
850    pub fn clear_runtime_print_sink(&mut self) {
851        self.runtime_print_sink = None;
852    }
853
854    pub(crate) fn write_runtime_print(&mut self, rendered: String) -> VmResult<()> {
855        let Some(sink) = self.runtime_print_sink.as_mut() else {
856            return Err(VmError::HostError(
857                "runtime print sink is not configured".to_string(),
858            ));
859        };
860        sink(rendered);
861        Ok(())
862    }
863
864    pub fn allocate_host_op_id(&mut self) -> HostOpId {
865        let op_id = self.next_host_op_id;
866        self.next_host_op_id = self.next_host_op_id.wrapping_add(1).max(1);
867        op_id
868    }
869
870    pub fn waiting_host_op_id(&self) -> Option<HostOpId> {
871        self.waiting_host_op.map(|op| op.op_id)
872    }
873
874    pub(super) fn cancel_waiting_host_op(&mut self) {
875        let Some(waiting) = self.waiting_host_op.take() else {
876            return;
877        };
878        match waiting.source {
879            WaitingHostOpSource::HostBridge => {
880                if let Some(bridge) = self.async_bridge.as_mut() {
881                    bridge.cancel_op(waiting.op_id);
882                }
883            }
884            WaitingHostOpSource::BuiltinIo => {
885                crate::builtins::runtime::cancel_builtin_io_op(self, waiting.op_id);
886            }
887        }
888    }
889
890    pub fn complete_host_op(
891        &mut self,
892        op_id: HostOpId,
893        values: impl Into<CallReturn>,
894    ) -> VmResult<()> {
895        self.complete_waiting_host_op(op_id, values.into())
896    }
897
898    pub fn poll_waiting_host_op(&mut self, cx: &mut Context<'_>) -> Poll<VmResult<()>> {
899        let Some(waiting) = self.waiting_host_op else {
900            return Poll::Ready(Ok(()));
901        };
902
903        let poll_result = match waiting.source {
904            WaitingHostOpSource::HostBridge => {
905                let bridge_ptr = match self.async_bridge.as_mut() {
906                    Some(bridge) => bridge.as_mut() as *mut dyn HostAsyncBridge,
907                    None => {
908                        return Poll::Ready(Err(VmError::HostError(format!(
909                            "vm waiting on host op {} without an async bridge",
910                            waiting.op_id
911                        ))));
912                    }
913                };
914
915                unsafe { (&mut *bridge_ptr).poll_op(waiting.op_id, cx) }
916            }
917            WaitingHostOpSource::BuiltinIo => {
918                crate::builtins::runtime::poll_builtin_io_op(self, waiting.op_id, cx)
919            }
920        };
921
922        match poll_result {
923            Poll::Pending => Poll::Pending,
924            Poll::Ready(Ok(values)) => {
925                self.complete_waiting_host_op(waiting.op_id, values)?;
926                Poll::Ready(Ok(()))
927            }
928            Poll::Ready(Err(err)) => {
929                self.waiting_host_op = None;
930                Poll::Ready(Err(err))
931            }
932        }
933    }
934
935    pub async fn await_waiting_host_op(&mut self) -> VmResult<()> {
936        std::future::poll_fn(|cx| self.poll_waiting_host_op(cx)).await
937    }
938
939    pub fn wait_for_host_op_blocking(&mut self) -> VmResult<()> {
940        let waker = noop_waker();
941        let mut cx = Context::from_waker(&waker);
942        loop {
943            match self.poll_waiting_host_op(&mut cx) {
944                Poll::Ready(result) => return result,
945                Poll::Pending => {
946                    #[cfg(not(target_arch = "wasm32"))]
947                    {
948                        std::thread::sleep(std::time::Duration::from_millis(1));
949                    }
950                    #[cfg(target_arch = "wasm32")]
951                    {
952                        return Err(VmError::HostError(
953                            "blocking host-op wait is unsupported on wasm32 runtime".to_string(),
954                        ));
955                    }
956                }
957            }
958        }
959    }
960
961    pub(super) fn execute_host_call(
962        &mut self,
963        index: u16,
964        argc_u8: u8,
965        call_ip: usize,
966    ) -> VmResult<HostCallExecOutcome> {
967        let argc = argc_u8 as usize;
968        if let Some(builtin) = BuiltinFunction::from_call_index(index) {
969            if !builtin.accepts_arity(argc_u8) {
970                return Err(VmError::InvalidCallArity {
971                    import: builtin.name().to_string(),
972                    expected: builtin.arity(),
973                    got: argc_u8,
974                });
975            }
976            if self.builtin_overrides.contains_key(&index) {
977                return self.execute_builtin_override_call(index, argc_u8, call_ip);
978            }
979            if let Some(outcome) =
980                self.try_execute_typed_builtin_fast_path(builtin, argc, call_ip)?
981            {
982                return Ok(outcome);
983            }
984            if let Some(outcome) = self.try_execute_builtin_projection_fast_path(builtin, argc)? {
985                return Ok(outcome);
986            }
987            self.record_generic_builtin_call();
988            return self.execute_builtin_call_from_stack(builtin, argc, call_ip);
989        }
990
991        let expected_return_type = self
992            .program
993            .imports
994            .get(usize::from(index))
995            .map(|import| import.return_type);
996        let resolved_index = self.resolve_call_target(index, argc_u8)?;
997        if let Some(function) =
998            self.host_functions
999                .get(resolved_index as usize)
1000                .and_then(|function| match function {
1001                    VmHostFunction::ArgsStaticNonYielding(function) => Some(*function),
1002                    _ => None,
1003                })
1004        {
1005            return self.execute_static_non_yielding_args_host_function(
1006                function,
1007                argc,
1008                expected_return_type,
1009            );
1010        }
1011        if self.bound_host_function_uses_args_slice(resolved_index)? {
1012            self.execute_bound_args_host_function(
1013                resolved_index,
1014                argc,
1015                call_ip,
1016                expected_return_type,
1017            )
1018        } else if self.bound_host_function_uses_stack_borrow(resolved_index)? {
1019            self.execute_bound_stack_host_function(resolved_index, argc, call_ip)
1020        } else {
1021            self.execute_bound_host_function_from_stack(resolved_index, argc, call_ip)
1022        }
1023    }
1024
1025    pub(super) fn execute_builtin_override_call(
1026        &mut self,
1027        builtin_call_index: u16,
1028        argc_u8: u8,
1029        call_ip: usize,
1030    ) -> VmResult<HostCallExecOutcome> {
1031        let resolved_index = self
1032            .builtin_overrides
1033            .get(&builtin_call_index)
1034            .copied()
1035            .ok_or_else(|| {
1036                VmError::HostError(format!(
1037                    "missing builtin override slot for call index {builtin_call_index}"
1038                ))
1039            })?;
1040        let argc = argc_u8 as usize;
1041        if self.bound_host_function_uses_args_slice(resolved_index)? {
1042            self.execute_bound_args_host_function(resolved_index, argc, call_ip, None)
1043        } else if self.bound_host_function_uses_stack_borrow(resolved_index)? {
1044            self.execute_bound_stack_host_function(resolved_index, argc, call_ip)
1045        } else {
1046            self.execute_bound_host_function_from_stack(resolved_index, argc, call_ip)
1047        }
1048    }
1049
1050    fn execute_builtin_call_from_stack(
1051        &mut self,
1052        builtin: BuiltinFunction,
1053        argc: usize,
1054        call_ip: usize,
1055    ) -> VmResult<HostCallExecOutcome> {
1056        let arg_start = self
1057            .stack
1058            .len()
1059            .checked_sub(argc)
1060            .ok_or(VmError::StackUnderflow)?;
1061        // Builtin dispatch reads arguments from the current stack tail while mutating the VM.
1062        // The builtin runtime must not mutate `self.stack` until this borrowed slice is consumed.
1063        let outcome = unsafe {
1064            let args = std::slice::from_raw_parts_mut(self.stack.as_mut_ptr().add(arg_start), argc);
1065            crate::builtins::runtime::execute_builtin_call(self, builtin, args)
1066        }?;
1067
1068        match outcome {
1069            crate::builtins::runtime::BuiltinCallOutcome::Return(values) => {
1070                self.stack.truncate(arg_start);
1071                values.push_onto_stack(&mut self.stack);
1072                Ok(HostCallExecOutcome::Returned)
1073            }
1074            crate::builtins::runtime::BuiltinCallOutcome::Halt => {
1075                self.stack.truncate(arg_start);
1076                Ok(HostCallExecOutcome::Halted)
1077            }
1078            crate::builtins::runtime::BuiltinCallOutcome::Pending(op_id) => {
1079                self.stack.truncate(arg_start);
1080                let resume_ip = self.call_resume_ip(call_ip)?;
1081                self.set_waiting_host_op(op_id, WaitingHostOpSource::BuiltinIo)?;
1082                self.ip = resume_ip;
1083                Ok(HostCallExecOutcome::Pending(op_id))
1084            }
1085        }
1086    }
1087
1088    fn try_execute_typed_builtin_fast_path(
1089        &mut self,
1090        builtin: BuiltinFunction,
1091        argc: usize,
1092        call_ip: usize,
1093    ) -> VmResult<Option<HostCallExecOutcome>> {
1094        let arg_start = self
1095            .stack
1096            .len()
1097            .checked_sub(argc)
1098            .ok_or(VmError::StackUnderflow)?;
1099        let (lhs, rhs) = self.operand_value_types(call_ip);
1100        let result = {
1101            let args = &self.stack[arg_start..];
1102            match builtin {
1103                BuiltinFunction::Len => match (lhs, args) {
1104                    (
1105                        ValueType::String | ValueType::Bytes | ValueType::Array | ValueType::Map,
1106                        [value],
1107                    ) => Self::fast_path_len_result(value),
1108                    _ => None,
1109                },
1110                BuiltinFunction::Slice => match (lhs, rhs, args) {
1111                    (ValueType::String, ValueType::Int, [source, start, length]) => {
1112                        Some(Self::fast_path_slice_string_result(source, start, length)?)
1113                    }
1114                    (ValueType::Array, ValueType::Int, [source, start, length]) => {
1115                        Some(Self::fast_path_slice_array_result(source, start, length)?)
1116                    }
1117                    (ValueType::Bytes, ValueType::Int, [source, start, length]) => {
1118                        Some(Self::fast_path_slice_bytes_result(source, start, length)?)
1119                    }
1120                    _ => None,
1121                },
1122                BuiltinFunction::Get => match (lhs, args) {
1123                    (
1124                        ValueType::String | ValueType::Bytes | ValueType::Array | ValueType::Map,
1125                        [container, key],
1126                    ) => Self::fast_path_get_result(container, key)?,
1127                    _ => None,
1128                },
1129                BuiltinFunction::Has => match (lhs, args) {
1130                    (ValueType::Bytes | ValueType::Array | ValueType::Map, [container, key]) => {
1131                        Self::fast_path_has_result(container, key)?
1132                    }
1133                    _ => None,
1134                },
1135                BuiltinFunction::StringContains => match (lhs, rhs, args) {
1136                    (ValueType::String, ValueType::String, [text, needle]) => {
1137                        Self::fast_path_string_contains_result(text, needle)
1138                    }
1139                    _ => None,
1140                },
1141                BuiltinFunction::StringReplaceLiteral => match (lhs, rhs, args) {
1142                    (ValueType::String, ValueType::String, [text, needle, replacement]) => {
1143                        Self::fast_path_string_replace_literal_result(text, needle, replacement)
1144                    }
1145                    _ => None,
1146                },
1147                BuiltinFunction::StringLowerAscii => match (lhs, args) {
1148                    (ValueType::String, [text]) => Self::fast_path_string_lower_ascii_result(text),
1149                    _ => None,
1150                },
1151                BuiltinFunction::BytesFromArrayU8 => match (lhs, args) {
1152                    (ValueType::Array, [value]) => {
1153                        Some(Self::fast_path_bytes_from_array_u8_result(value)?)
1154                    }
1155                    _ => None,
1156                },
1157                BuiltinFunction::BytesToArrayU8 => match (lhs, args) {
1158                    (ValueType::Bytes, [value]) => {
1159                        Some(Self::fast_path_bytes_to_array_u8_result(value)?)
1160                    }
1161                    _ => None,
1162                },
1163                _ => None,
1164            }
1165        };
1166        let Some(value) = result else {
1167            return Ok(None);
1168        };
1169        self.stack.truncate(arg_start);
1170        self.stack.push(value);
1171        self.record_typed_builtin_fast_path();
1172        Ok(Some(HostCallExecOutcome::Returned))
1173    }
1174
1175    fn try_execute_builtin_projection_fast_path(
1176        &mut self,
1177        builtin: BuiltinFunction,
1178        argc: usize,
1179    ) -> VmResult<Option<HostCallExecOutcome>> {
1180        let arg_start = self
1181            .stack
1182            .len()
1183            .checked_sub(argc)
1184            .ok_or(VmError::StackUnderflow)?;
1185        let result = {
1186            let args = &self.stack[arg_start..];
1187            match (builtin, args) {
1188                (BuiltinFunction::Len, [value]) => Self::fast_path_len_result(value),
1189                (BuiltinFunction::Get, [container, key]) => {
1190                    Self::fast_path_get_result(container, key)?
1191                }
1192                (BuiltinFunction::Has, [container, key]) => {
1193                    Self::fast_path_has_result(container, key)?
1194                }
1195                _ => None,
1196            }
1197        };
1198        let Some(value) = result else {
1199            return Ok(None);
1200        };
1201        self.stack.truncate(arg_start);
1202        self.stack.push(value);
1203        self.record_projection_fast_path();
1204        Ok(Some(HostCallExecOutcome::Returned))
1205    }
1206
1207    fn fast_path_len_result(value: &Value) -> Option<Value> {
1208        match value {
1209            Value::String(text) => Some(Value::Int(text.chars().count() as i64)),
1210            Value::Bytes(values) => Some(Value::Int(values.len() as i64)),
1211            Value::Array(values) => Some(Value::Int(values.len() as i64)),
1212            Value::Map(entries) => Some(Value::Int(entries.len() as i64)),
1213            _ => None,
1214        }
1215    }
1216
1217    fn fast_path_string_contains_result(text: &Value, needle: &Value) -> Option<Value> {
1218        let (Value::String(text), Value::String(needle)) = (text, needle) else {
1219            return None;
1220        };
1221        Some(Value::Bool(
1222            crate::builtins::runtime::core::builtin_string_contains_impl(
1223                text.as_str(),
1224                needle.as_str(),
1225            ),
1226        ))
1227    }
1228
1229    fn fast_path_string_replace_literal_result(
1230        text: &Value,
1231        needle: &Value,
1232        replacement: &Value,
1233    ) -> Option<Value> {
1234        let (Value::String(text), Value::String(needle), Value::String(replacement)) =
1235            (text, needle, replacement)
1236        else {
1237            return None;
1238        };
1239        Some(Value::string(
1240            crate::builtins::runtime::core::builtin_string_replace_literal_impl(
1241                text.as_str(),
1242                needle.as_str(),
1243                replacement.as_str(),
1244            ),
1245        ))
1246    }
1247
1248    fn fast_path_string_lower_ascii_result(text: &Value) -> Option<Value> {
1249        let Value::String(text) = text else {
1250            return None;
1251        };
1252        Some(Value::string(
1253            crate::builtins::runtime::core::builtin_string_lower_ascii_impl(text.as_str()),
1254        ))
1255    }
1256
1257    fn fast_path_get_result(container: &Value, key: &Value) -> VmResult<Option<Value>> {
1258        match container {
1259            Value::Array(values) => {
1260                let index = key.as_int()?;
1261                if index < 0 {
1262                    return Err(VmError::HostError(
1263                        "array index must be non-negative".to_string(),
1264                    ));
1265                }
1266                let index = usize::try_from(index)
1267                    .map_err(|_| VmError::HostError("array index overflow".to_string()))?;
1268                let value = values.get(index).cloned().ok_or_else(|| {
1269                    VmError::HostError(format!("array index {index} out of bounds"))
1270                })?;
1271                Ok(Some(value))
1272            }
1273            Value::Map(entries) => {
1274                let value = entries
1275                    .get(key)
1276                    .cloned()
1277                    .ok_or_else(|| VmError::HostError("map key not found".to_string()))?;
1278                Ok(Some(value))
1279            }
1280            Value::Bytes(values) => {
1281                let index = key.as_int()?;
1282                if index < 0 {
1283                    return Err(VmError::HostError(
1284                        "bytes index must be non-negative".to_string(),
1285                    ));
1286                }
1287                let index = usize::try_from(index)
1288                    .map_err(|_| VmError::HostError("bytes index overflow".to_string()))?;
1289                let value = values.get(index).copied().ok_or_else(|| {
1290                    VmError::HostError(format!("bytes index {index} out of bounds"))
1291                })?;
1292                Ok(Some(Value::Int(i64::from(value))))
1293            }
1294            Value::String(text) => {
1295                let index = key.as_int()?;
1296                if index < 0 {
1297                    return Err(VmError::HostError(
1298                        "string index must be non-negative".to_string(),
1299                    ));
1300                }
1301                let index = usize::try_from(index)
1302                    .map_err(|_| VmError::HostError("string index overflow".to_string()))?;
1303                let value = text
1304                    .chars()
1305                    .nth(index)
1306                    .map(|ch| Value::string(ch.to_string()))
1307                    .ok_or_else(|| {
1308                        VmError::HostError(format!("string index {index} out of bounds"))
1309                    })?;
1310                Ok(Some(value))
1311            }
1312            _ => Ok(None),
1313        }
1314    }
1315
1316    fn fast_path_has_result(container: &Value, key: &Value) -> VmResult<Option<Value>> {
1317        match container {
1318            Value::Array(values) => {
1319                let index = key.as_int()?;
1320                let present = if index < 0 {
1321                    false
1322                } else {
1323                    usize::try_from(index)
1324                        .ok()
1325                        .is_some_and(|index| index < values.len())
1326                };
1327                Ok(Some(Value::Bool(present)))
1328            }
1329            Value::Bytes(values) => {
1330                let index = key.as_int()?;
1331                let present = if index < 0 {
1332                    false
1333                } else {
1334                    usize::try_from(index)
1335                        .ok()
1336                        .is_some_and(|index| index < values.len())
1337                };
1338                Ok(Some(Value::Bool(present)))
1339            }
1340            Value::Map(entries) => Ok(Some(Value::Bool(entries.get(key).is_some()))),
1341            _ => Ok(None),
1342        }
1343    }
1344
1345    fn fast_path_slice_bounds(start: i64, length: i64) -> VmResult<Option<(usize, usize)>> {
1346        if start < 0 || length <= 0 {
1347            return Ok(None);
1348        }
1349        let start = usize::try_from(start).map_err(|_| {
1350            VmError::HostError("slice start overflow while converting to usize".to_string())
1351        })?;
1352        let length = usize::try_from(length).map_err(|_| {
1353            VmError::HostError("slice length overflow while converting to usize".to_string())
1354        })?;
1355        Ok(Some((start, length)))
1356    }
1357
1358    fn fast_path_slice_string_result(
1359        source: &Value,
1360        start: &Value,
1361        length: &Value,
1362    ) -> VmResult<Value> {
1363        let Value::String(text) = source else {
1364            return Err(VmError::TypeMismatch("string"));
1365        };
1366        let start = start.as_int()?;
1367        let length = length.as_int()?;
1368        let Some((start, length)) = Self::fast_path_slice_bounds(start, length)? else {
1369            return Ok(Value::string(String::new()));
1370        };
1371        Ok(Value::string(
1372            text.chars().skip(start).take(length).collect::<String>(),
1373        ))
1374    }
1375
1376    fn fast_path_slice_array_result(
1377        source: &Value,
1378        start: &Value,
1379        length: &Value,
1380    ) -> VmResult<Value> {
1381        let Value::Array(values) = source else {
1382            return Err(VmError::TypeMismatch("array"));
1383        };
1384        let start = start.as_int()?;
1385        let length = length.as_int()?;
1386        let Some((start, length)) = Self::fast_path_slice_bounds(start, length)? else {
1387            return Ok(Value::array(Vec::new()));
1388        };
1389        Ok(Value::array(
1390            values
1391                .iter()
1392                .skip(start)
1393                .take(length)
1394                .cloned()
1395                .collect::<Vec<_>>(),
1396        ))
1397    }
1398
1399    fn fast_path_slice_bytes_result(
1400        source: &Value,
1401        start: &Value,
1402        length: &Value,
1403    ) -> VmResult<Value> {
1404        let Value::Bytes(values) = source else {
1405            return Err(VmError::TypeMismatch("bytes"));
1406        };
1407        let start = start.as_int()?;
1408        let length = length.as_int()?;
1409        let Some((start, length)) = Self::fast_path_slice_bounds(start, length)? else {
1410            return Ok(Value::bytes(Vec::new()));
1411        };
1412        Ok(Value::bytes(
1413            values
1414                .iter()
1415                .skip(start)
1416                .take(length)
1417                .copied()
1418                .collect::<Vec<_>>(),
1419        ))
1420    }
1421
1422    fn fast_path_bytes_from_array_u8_result(value: &Value) -> VmResult<Value> {
1423        let Value::Array(values) = value else {
1424            return Err(VmError::TypeMismatch("array"));
1425        };
1426        let mut out = Vec::with_capacity(values.len());
1427        for (index, value) in values.iter().enumerate() {
1428            let Value::Int(value) = value else {
1429                return Err(VmError::HostError(format!(
1430                    "bytes::from_array_u8 entry {index} must be an int in 0..=255"
1431                )));
1432            };
1433            let value = u8::try_from(*value).map_err(|_| {
1434                VmError::HostError(format!(
1435                    "bytes::from_array_u8 entry {index} must be an int in 0..=255"
1436                ))
1437            })?;
1438            out.push(value);
1439        }
1440        Ok(Value::bytes(out))
1441    }
1442
1443    fn fast_path_bytes_to_array_u8_result(value: &Value) -> VmResult<Value> {
1444        let Value::Bytes(payload) = value else {
1445            return Err(VmError::TypeMismatch("bytes"));
1446        };
1447        Ok(Value::array(
1448            payload
1449                .iter()
1450                .copied()
1451                .map(|byte| Value::Int(i64::from(byte)))
1452                .collect(),
1453        ))
1454    }
1455
1456    pub(super) fn execute_bound_host_function_from_stack(
1457        &mut self,
1458        resolved_index: u16,
1459        argc: usize,
1460        call_ip: usize,
1461    ) -> VmResult<HostCallExecOutcome> {
1462        let arg_start = self
1463            .stack
1464            .len()
1465            .checked_sub(argc)
1466            .ok_or(VmError::StackUnderflow)?;
1467        let mut saved_stack = std::mem::take(&mut self.stack);
1468        self.call_depth += 1;
1469        let function_ptr =
1470            self.host_functions
1471                .get_mut(resolved_index as usize)
1472                .ok_or(VmError::InvalidCall(resolved_index))? as *mut VmHostFunction;
1473        let outcome = unsafe {
1474            let args = &saved_stack[arg_start..];
1475            match &mut *function_ptr {
1476                VmHostFunction::Dynamic(function) => function.call(self, args),
1477                VmHostFunction::Static(function) => function(self, args),
1478                VmHostFunction::StackDynamic(_)
1479                | VmHostFunction::StackStatic(_)
1480                | VmHostFunction::ArgsDynamic(_)
1481                | VmHostFunction::ArgsStatic(_)
1482                | VmHostFunction::ArgsStaticNonYielding(_) => unreachable!(),
1483            }
1484        };
1485        self.call_depth = self.call_depth.saturating_sub(1);
1486
1487        let mut host_stack = std::mem::take(&mut self.stack);
1488        let outcome = match outcome {
1489            Ok(outcome) => outcome,
1490            Err(err) => {
1491                saved_stack.truncate(arg_start);
1492                saved_stack.append(&mut host_stack);
1493                self.stack = saved_stack;
1494                return Err(err);
1495            }
1496        };
1497
1498        match outcome {
1499            CallOutcome::Return(values) => {
1500                saved_stack.truncate(arg_start);
1501                saved_stack.append(&mut host_stack);
1502                values.push_onto_stack(&mut saved_stack);
1503                self.stack = saved_stack;
1504                Ok(HostCallExecOutcome::Returned)
1505            }
1506            CallOutcome::Halt => {
1507                saved_stack.truncate(arg_start);
1508                saved_stack.append(&mut host_stack);
1509                self.stack = saved_stack;
1510                Ok(HostCallExecOutcome::Halted)
1511            }
1512            CallOutcome::Yield => {
1513                saved_stack.append(&mut host_stack);
1514                self.stack = saved_stack;
1515                self.ip = call_ip;
1516                Ok(HostCallExecOutcome::Yielded)
1517            }
1518            CallOutcome::Pending(op_id) => {
1519                saved_stack.truncate(arg_start);
1520                saved_stack.append(&mut host_stack);
1521                self.stack = saved_stack;
1522                let resume_ip = self.call_resume_ip(call_ip)?;
1523                self.set_waiting_host_op(op_id, WaitingHostOpSource::HostBridge)?;
1524                self.ip = resume_ip;
1525                Ok(HostCallExecOutcome::Pending(op_id))
1526            }
1527        }
1528    }
1529
1530    fn bound_host_function_uses_args_slice(&self, resolved_index: u16) -> VmResult<bool> {
1531        let function = self
1532            .host_functions
1533            .get(resolved_index as usize)
1534            .ok_or(VmError::InvalidCall(resolved_index))?;
1535        Ok(matches!(
1536            function,
1537            VmHostFunction::ArgsDynamic(_)
1538                | VmHostFunction::ArgsStatic(_)
1539                | VmHostFunction::ArgsStaticNonYielding(_)
1540        ))
1541    }
1542
1543    fn bound_host_function_uses_stack_borrow(&self, resolved_index: u16) -> VmResult<bool> {
1544        let function = self
1545            .host_functions
1546            .get(resolved_index as usize)
1547            .ok_or(VmError::InvalidCall(resolved_index))?;
1548        Ok(matches!(
1549            function,
1550            VmHostFunction::StackDynamic(_) | VmHostFunction::StackStatic(_)
1551        ))
1552    }
1553
1554    #[inline(always)]
1555    fn execute_static_non_yielding_args_host_function(
1556        &mut self,
1557        function: StaticHostArgsFunction,
1558        argc: usize,
1559        expected_return_type: Option<ValueType>,
1560    ) -> VmResult<HostCallExecOutcome> {
1561        let arg_start = self
1562            .stack
1563            .len()
1564            .checked_sub(argc)
1565            .ok_or(VmError::StackUnderflow)?;
1566        self.call_depth += 1;
1567        let outcome = function(&self.stack[arg_start..]);
1568        self.call_depth = self.call_depth.saturating_sub(1);
1569        let value = require_non_yielding_host_value(outcome?)?;
1570        let value = validate_non_yielding_host_value(value, expected_return_type)?;
1571        self.stack.truncate(arg_start);
1572        self.stack.push(value);
1573        Ok(HostCallExecOutcome::Returned)
1574    }
1575
1576    pub(super) fn execute_bound_args_host_function(
1577        &mut self,
1578        resolved_index: u16,
1579        argc: usize,
1580        call_ip: usize,
1581        expected_return_type: Option<ValueType>,
1582    ) -> VmResult<HostCallExecOutcome> {
1583        let arg_start = self
1584            .stack
1585            .len()
1586            .checked_sub(argc)
1587            .ok_or(VmError::StackUnderflow)?;
1588        self.call_depth += 1;
1589        let outcome = {
1590            let args = &self.stack[arg_start..];
1591            let function = self
1592                .host_functions
1593                .get_mut(resolved_index as usize)
1594                .ok_or(VmError::InvalidCall(resolved_index))?;
1595            match function {
1596                VmHostFunction::ArgsDynamic(function) => (function.call(args), false),
1597                VmHostFunction::ArgsStatic(function) => (function(args), false),
1598                VmHostFunction::ArgsStaticNonYielding(function) => (function(args), true),
1599                VmHostFunction::Dynamic(_)
1600                | VmHostFunction::Static(_)
1601                | VmHostFunction::StackDynamic(_)
1602                | VmHostFunction::StackStatic(_) => unreachable!(),
1603            }
1604        };
1605        self.call_depth = self.call_depth.saturating_sub(1);
1606        let (outcome, non_yielding) = outcome;
1607        let outcome = outcome?;
1608        if non_yielding {
1609            let value = require_non_yielding_host_value(outcome)?;
1610            let value = validate_non_yielding_host_value(value, expected_return_type)?;
1611            self.stack.truncate(arg_start);
1612            self.stack.push(value);
1613            return Ok(HostCallExecOutcome::Returned);
1614        }
1615
1616        match outcome {
1617            CallOutcome::Return(values) => {
1618                self.stack.truncate(arg_start);
1619                values.push_onto_stack(&mut self.stack);
1620                Ok(HostCallExecOutcome::Returned)
1621            }
1622            CallOutcome::Halt => {
1623                self.stack.truncate(arg_start);
1624                Ok(HostCallExecOutcome::Halted)
1625            }
1626            CallOutcome::Yield => {
1627                self.ip = call_ip;
1628                Ok(HostCallExecOutcome::Yielded)
1629            }
1630            CallOutcome::Pending(op_id) => {
1631                self.stack.truncate(arg_start);
1632                let resume_ip = self.call_resume_ip(call_ip)?;
1633                self.set_waiting_host_op(op_id, WaitingHostOpSource::HostBridge)?;
1634                self.ip = resume_ip;
1635                Ok(HostCallExecOutcome::Pending(op_id))
1636            }
1637        }
1638    }
1639
1640    pub(super) fn execute_bound_stack_host_function(
1641        &mut self,
1642        resolved_index: u16,
1643        argc: usize,
1644        call_ip: usize,
1645    ) -> VmResult<HostCallExecOutcome> {
1646        let arg_start = self
1647            .stack
1648            .len()
1649            .checked_sub(argc)
1650            .ok_or(VmError::StackUnderflow)?;
1651        self.call_depth += 1;
1652        let function_ptr =
1653            self.host_functions
1654                .get_mut(resolved_index as usize)
1655                .ok_or(VmError::InvalidCall(resolved_index))? as *mut VmHostFunction;
1656        // Stack-borrowed host functions opt into the same raw stack-tail borrowing model used
1657        // by builtin dispatch. They must not re-enter the VM or otherwise mutate `self.stack`
1658        // while the borrowed slice is alive.
1659        let outcome = unsafe {
1660            let args = std::slice::from_raw_parts(self.stack.as_ptr().add(arg_start), argc);
1661            match &mut *function_ptr {
1662                VmHostFunction::StackDynamic(function) => function.call(self, args),
1663                VmHostFunction::StackStatic(function) => function(self, args),
1664                VmHostFunction::Dynamic(_)
1665                | VmHostFunction::Static(_)
1666                | VmHostFunction::ArgsDynamic(_)
1667                | VmHostFunction::ArgsStatic(_)
1668                | VmHostFunction::ArgsStaticNonYielding(_) => unreachable!(),
1669            }
1670        };
1671        self.call_depth = self.call_depth.saturating_sub(1);
1672        let outcome = outcome?;
1673
1674        match outcome {
1675            CallOutcome::Return(values) => {
1676                self.stack.truncate(arg_start);
1677                values.push_onto_stack(&mut self.stack);
1678                Ok(HostCallExecOutcome::Returned)
1679            }
1680            CallOutcome::Halt => {
1681                self.stack.truncate(arg_start);
1682                Ok(HostCallExecOutcome::Halted)
1683            }
1684            CallOutcome::Yield => {
1685                self.ip = call_ip;
1686                Ok(HostCallExecOutcome::Yielded)
1687            }
1688            CallOutcome::Pending(op_id) => {
1689                self.stack.truncate(arg_start);
1690                let resume_ip = self.call_resume_ip(call_ip)?;
1691                self.set_waiting_host_op(op_id, WaitingHostOpSource::HostBridge)?;
1692                self.ip = resume_ip;
1693                Ok(HostCallExecOutcome::Pending(op_id))
1694            }
1695        }
1696    }
1697
1698    pub(super) fn call_resume_ip(&self, call_ip: usize) -> VmResult<usize> {
1699        let opcode = self
1700            .program
1701            .code
1702            .get(call_ip)
1703            .copied()
1704            .ok_or(VmError::BytecodeBounds)
1705            .and_then(|raw| OpCode::try_from(raw).map_err(|_| VmError::InvalidOpcode(raw)))?;
1706        if !matches!(opcode, OpCode::Call | OpCode::CallValue) {
1707            return Err(VmError::InvalidOpcode(opcode as u8));
1708        }
1709        let resume_ip = call_ip
1710            .checked_add(1 + opcode.operand_len())
1711            .ok_or(VmError::BytecodeBounds)?;
1712        if resume_ip > self.program.code.len() {
1713            return Err(VmError::BytecodeBounds);
1714        }
1715        Ok(resume_ip)
1716    }
1717
1718    pub(super) fn set_waiting_host_op(
1719        &mut self,
1720        op_id: HostOpId,
1721        source: WaitingHostOpSource,
1722    ) -> VmResult<()> {
1723        if let Some(active) = self.waiting_host_op
1724            && active.op_id != op_id
1725        {
1726            return Err(VmError::HostError(format!(
1727                "vm already waiting on host op {}, cannot wait on {}",
1728                active.op_id, op_id
1729            )));
1730        }
1731        self.waiting_host_op = Some(WaitingHostOp { op_id, source });
1732        Ok(())
1733    }
1734
1735    pub(super) fn complete_waiting_host_op(
1736        &mut self,
1737        op_id: HostOpId,
1738        values: CallReturn,
1739    ) -> VmResult<()> {
1740        let waiting = self.waiting_host_op.ok_or_else(|| {
1741            VmError::HostError(format!(
1742                "host op {} completed but vm is not waiting on any op",
1743                op_id
1744            ))
1745        })?;
1746        if waiting.op_id != op_id {
1747            return Err(VmError::HostError(format!(
1748                "host op {} completed while vm waits on {}",
1749                op_id, waiting.op_id
1750            )));
1751        }
1752        self.waiting_host_op = None;
1753        values.push_onto_stack(&mut self.stack);
1754        Ok(())
1755    }
1756
1757    pub(super) fn install_resolved_calls(&mut self, resolved_calls: Vec<u16>) -> VmResult<()> {
1758        if self.program.imports.len() != resolved_calls.len() {
1759            return Err(VmError::HostError(format!(
1760                "resolved call cache size mismatch: expected {}, got {}",
1761                self.program.imports.len(),
1762                resolved_calls.len()
1763            )));
1764        }
1765        for &index in &resolved_calls {
1766            if index as usize >= self.host_functions.len() {
1767                return Err(VmError::InvalidCall(index));
1768            }
1769        }
1770        self.resolved_calls = resolved_calls;
1771        self.resolved_calls_dirty = false;
1772        Ok(())
1773    }
1774
1775    pub(super) fn ensure_call_bindings(&mut self) -> VmResult<()> {
1776        if self.program.imports.is_empty() || !self.resolved_calls_dirty {
1777            return Ok(());
1778        }
1779
1780        if self.host_function_symbols.is_empty() && self.host_functions.is_empty() {
1781            let import_names = self
1782                .program
1783                .imports
1784                .iter()
1785                .map(|import| import.name.clone())
1786                .collect::<Vec<_>>();
1787            for name in import_names {
1788                let _ = crate::builtins::runtime::bind_default_host_function(self, &name);
1789            }
1790        }
1791
1792        let use_legacy_order = self.host_function_symbols.is_empty();
1793        let mut resolved = Vec::with_capacity(self.program.imports.len());
1794        let imports = self.program.imports.clone();
1795        for (index, import) in imports.iter().enumerate() {
1796            if use_legacy_order {
1797                if index >= self.host_functions.len() {
1798                    return Err(VmError::InvalidCall(index as u16));
1799                }
1800                resolved.push(index as u16);
1801                continue;
1802            }
1803
1804            let bound = if let Some(bound) = self.host_function_symbols.get(&import.name).copied() {
1805                bound
1806            } else if crate::builtins::runtime::bind_default_host_function(self, &import.name) {
1807                self.host_function_symbols
1808                    .get(&import.name)
1809                    .copied()
1810                    .ok_or_else(|| VmError::UnboundImport(import.name.clone()))?
1811            } else {
1812                return Err(VmError::UnboundImport(import.name.clone()));
1813            };
1814            resolved.push(bound);
1815        }
1816
1817        self.resolved_calls = resolved;
1818        self.resolved_calls_dirty = false;
1819        Ok(())
1820    }
1821
1822    pub(super) fn sync_jit_non_yielding_host_imports(&mut self) {
1823        let imports = self
1824            .resolved_calls
1825            .iter()
1826            .map(|&slot| {
1827                matches!(
1828                    self.host_functions.get(usize::from(slot)),
1829                    Some(VmHostFunction::ArgsStaticNonYielding(_))
1830                )
1831            })
1832            .collect();
1833        if self.jit.set_non_yielding_host_imports(imports) {
1834            self.native_traces.clear();
1835        }
1836    }
1837
1838    pub(super) fn resolve_call_target(&mut self, index: u16, argc: u8) -> VmResult<u16> {
1839        if self.program.imports.is_empty() {
1840            return Ok(index);
1841        }
1842
1843        self.ensure_call_bindings()?;
1844        let import = self
1845            .program
1846            .imports
1847            .get(index as usize)
1848            .ok_or(VmError::InvalidCall(index))?;
1849        if import.arity != argc {
1850            return Err(VmError::InvalidCallArity {
1851                import: import.name.clone(),
1852                expected: import.arity,
1853                got: argc,
1854            });
1855        }
1856
1857        self.resolved_calls
1858            .get(index as usize)
1859            .copied()
1860            .ok_or(VmError::InvalidCall(index))
1861    }
1862}