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
76pub 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 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(®istry_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 ®istry_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 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 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 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 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}