1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::rc::Rc;
4use std::sync::Arc;
5
6use crate::portable_builtin::PortableBuiltin;
7use crate::type_contract::{
8 manifest_signature_is_portable, matches_compiler_schema, matches_manifest_type,
9};
10use crate::{Chunk, CompiledFunction, Constant, Diagnostic, Op, ProgramArtifact};
11
12mod arithmetic;
13mod builtins;
14mod methods;
15mod ops;
16mod resource;
17mod runtime_value;
18mod snapshot;
19mod type_guard;
20mod types;
21
22#[cfg(test)]
23mod tests;
24
25use crate::value::{semantic_try_compare, semantic_values_equal};
26use arithmetic::{add, div, modulo, mul, negate, pow, sub};
27use ops::*;
28use resource::{validate_runtime_value, MAX_VALUE_BYTES};
29use runtime_value::{Closure, EnumValue, RuntimeValue};
30use snapshot::{decode_snapshot, encode_snapshot, ReplaySnapshot};
31use type_guard::validate_call;
32use types::value_kind;
33pub use types::{CapabilityRequest, CapabilityResult, DataValue, Execution, GrantSet, ValueShape};
34
35const DEFAULT_FUEL: u64 = 2_000_000;
36const MAX_FRAMES: usize = 1_024;
37const MAX_SCOPE_DEPTH: usize = 256;
38const MAX_OPERAND_STACK: usize = 16_384;
39const MAX_ITERATORS: usize = 1_024;
40pub const PORTABLE_MAX_SNAPSHOT_BYTES: usize = 1024 * 1024;
41
42pub fn start(program: &ProgramArtifact, input: DataValue, grants: &GrantSet) -> Execution {
43 run(program, input, grants, Vec::new())
44}
45
46pub fn replay(
50 program: &ProgramArtifact,
51 input: DataValue,
52 grants: &GrantSet,
53 responses: Vec<CapabilityResult>,
54) -> Execution {
55 run(program, input, grants, responses)
56}
57
58pub fn resume(
59 program: &ProgramArtifact,
60 snapshot: &[u8],
61 result: CapabilityResult,
62 grants: &GrantSet,
63) -> Execution {
64 let decoded = match decode_snapshot(snapshot, grants.snapshot_key()) {
65 Ok(value) => value,
66 Err(error) => return Execution::Failed { diagnostic: error },
67 };
68 if decoded.artifact_digest != program.digest() {
69 return failed(
70 "snapshot_program_mismatch",
71 "snapshot belongs to a different program artifact",
72 );
73 }
74 if decoded.grant_fingerprint != grants.fingerprint() {
75 return failed(
76 "snapshot_grant_mismatch",
77 "resume grants differ from the grants that created the snapshot",
78 );
79 }
80 if result.request_id() != decoded.pending_request {
81 return failed(
82 "capability_result_mismatch",
83 "capability result request ID does not match the suspended request",
84 );
85 }
86 let mut responses = decoded.responses;
87 responses.push(result);
88 run_with_fuel(
89 program,
90 decoded.input,
91 grants,
92 responses,
93 decoded.fuel_consumed,
94 )
95}
96
97fn run(
98 program: &ProgramArtifact,
99 input: DataValue,
100 grants: &GrantSet,
101 responses: Vec<CapabilityResult>,
102) -> Execution {
103 run_with_fuel(program, input, grants, responses, 0)
104}
105
106fn run_with_fuel(
107 program: &ProgramArtifact,
108 input: DataValue,
109 grants: &GrantSet,
110 responses: Vec<CapabilityResult>,
111 fuel_consumed: u64,
112) -> Execution {
113 if let Err(diagnostic) = input.validate() {
114 return Execution::Failed { diagnostic };
115 }
116 for response in &responses {
117 if let CapabilityResult::Ok { value, .. } = response {
118 if let Err(diagnostic) = value.validate() {
119 return Execution::Failed { diagnostic };
120 }
121 }
122 }
123 let root = Env::root();
124 let mut machine = Machine::new(program, root.clone(), grants, responses, fuel_consumed);
125 let bootstrap = match machine.execute(program.image().clone(), root, Vec::new()) {
126 Step::Value(value) => value,
127 Step::Suspend(request) => return machine.suspend(input, request),
128 Step::Error(error) => return Execution::Failed { diagnostic: error },
129 };
130 let RuntimeValue::Closure(closure) = bootstrap else {
131 return failed(
132 "entry_not_callable",
133 "compiled entry bootstrap did not return a callable",
134 );
135 };
136 let mut arguments = vec![RuntimeValue::from(input.clone())];
137 if program.expects_harness() {
138 arguments.insert(0, RuntimeValue::Harness("root".to_string()));
139 }
140 let Some(closure_env) = closure.env.upgrade() else {
141 return failed(
142 "closure_environment",
143 "entry closure environment is no longer available",
144 );
145 };
146 let entry_env = match machine.child_env(closure_env) {
147 Ok(env) => env,
148 Err(diagnostic) => return Execution::Failed { diagnostic },
149 };
150 if let Err(diagnostic) = machine.charge_call_validation(&arguments) {
151 return Execution::Failed { diagnostic };
152 }
153 if let Err(diagnostic) = validate_call(&closure.function, &arguments) {
154 return Execution::Failed { diagnostic };
155 }
156 match machine.execute_function(&closure.function, entry_env, arguments) {
157 Step::Value(value) => match machine
158 .charge_value_work(&value)
159 .and_then(|()| DataValue::try_from(value))
160 {
161 Ok(value) => Execution::Completed { value },
162 Err(error) => Execution::Failed { diagnostic: error },
163 },
164 Step::Suspend(request) => machine.suspend(input, request),
165 Step::Error(error) => Execution::Failed { diagnostic: error },
166 }
167}
168
169struct Machine<'a> {
170 program: &'a ProgramArtifact,
171 grants: &'a GrantSet,
172 responses: Vec<CapabilityResult>,
173 response_cursor: usize,
174 request_ordinal: u64,
175 fuel: u64,
176 replay_credit: u64,
177 environments: Vec<Rc<Env>>,
178 modules: BTreeMap<String, Rc<ModuleInstance>>,
179 loading_modules: Vec<String>,
180 iterators: Vec<IteratorState>,
181}
182
183impl<'a> Machine<'a> {
184 fn new(
185 program: &'a ProgramArtifact,
186 root: Rc<Env>,
187 grants: &'a GrantSet,
188 responses: Vec<CapabilityResult>,
189 fuel_consumed: u64,
190 ) -> Self {
191 Self {
192 program,
193 grants,
194 responses,
195 response_cursor: 0,
196 request_ordinal: 0,
197 fuel: DEFAULT_FUEL.saturating_sub(fuel_consumed),
198 replay_credit: fuel_consumed.min(DEFAULT_FUEL),
199 environments: vec![root],
200 modules: BTreeMap::new(),
201 loading_modules: Vec::new(),
202 iterators: Vec::new(),
203 }
204 }
205
206 fn import_root_module(
207 &mut self,
208 path: &str,
209 selected_names: Option<&[String]>,
210 namespace_alias: Option<NamespaceProjection<'_>>,
211 env: &Rc<Env>,
212 ) -> OpStep {
213 let Some(spec) = self
214 .program
215 .root_imports()
216 .iter()
217 .find(|spec| spec.path == path)
218 else {
219 return OpStep::Error(diagnostic(
220 "portable_import",
221 format!("root import `{path}` is not present in the package closure"),
222 ));
223 };
224 let target = spec.target.clone();
225 let module = match self.load_module(&target) {
226 ModuleStep::Ready(module) => module,
227 ModuleStep::Suspend(request) => return OpStep::Suspend(request),
228 ModuleStep::Error(error) => return OpStep::Error(error),
229 };
230 match self.bind_module_projection(&module, selected_names, namespace_alias, env) {
231 Ok(()) => OpStep::Continue,
232 Err(error) => OpStep::Error(error),
233 }
234 }
235
236 fn load_module(&mut self, id: &str) -> ModuleStep {
237 if let Some(module) = self.modules.get(id).cloned() {
238 return ModuleStep::Ready(module);
239 }
240 if self.loading_modules.iter().any(|loading| loading == id) {
241 return ModuleStep::Error(diagnostic(
242 "portable_import_cycle",
243 format!("portable package import cycle reaches `{id}`"),
244 ));
245 }
246 let Some(module) = self
247 .program
248 .modules()
249 .iter()
250 .find(|module| module.id() == id)
251 .cloned()
252 else {
253 return ModuleStep::Error(diagnostic(
254 "portable_import",
255 format!("portable package does not contain module `{id}`"),
256 ));
257 };
258 let module_env = Env::root();
259 self.retain_environment(&module_env);
260 self.loading_modules.push(id.to_string());
261
262 for import in module.imports() {
263 let imported = match self.load_module(&import.target) {
264 ModuleStep::Ready(imported) => imported,
265 ModuleStep::Suspend(request) => {
266 self.loading_modules.pop();
267 return ModuleStep::Suspend(request);
268 }
269 ModuleStep::Error(error) => {
270 self.loading_modules.pop();
271 return ModuleStep::Error(error);
272 }
273 };
274 if let Err(error) = self.bind_module_projection(
275 &imported,
276 import.selected_names.as_deref(),
277 import.namespace_alias.as_deref().map(|alias| (alias, None)),
280 &module_env,
281 ) {
282 self.loading_modules.pop();
283 return ModuleStep::Error(error);
284 }
285 }
286
287 if let Some(init) = module.init() {
288 match self.execute(init.clone(), module_env.clone(), Vec::new()) {
289 Step::Value(_) => {}
290 Step::Suspend(request) => {
291 self.loading_modules.pop();
292 return ModuleStep::Suspend(request);
293 }
294 Step::Error(error) => {
295 self.loading_modules.pop();
296 return ModuleStep::Error(error);
297 }
298 }
299 }
300
301 for (name, function) in module.functions() {
302 self.retain_environment(&module_env);
303 let value = RuntimeValue::Closure(Closure {
304 function: function.clone(),
305 env: Rc::downgrade(&module_env),
306 });
307 module_env.define(name.clone(), value.clone());
308 }
309 let instance = Rc::new(ModuleInstance {
310 env: module_env,
311 exports: module.exports().clone(),
312 });
313 self.modules.insert(id.to_string(), instance.clone());
314 self.loading_modules.pop();
315 ModuleStep::Ready(instance)
316 }
317
318 fn bind_module_projection(
319 &self,
320 module: &ModuleInstance,
321 selected_names: Option<&[String]>,
322 namespace_alias: Option<NamespaceProjection<'_>>,
323 env: &Rc<Env>,
324 ) -> Result<(), Diagnostic> {
325 if let Some((alias, demanded)) = namespace_alias {
326 if env.contains_local(alias) {
327 return Err(diagnostic(
328 "portable_import_collision",
329 format!("import namespace `{alias}` collides with an existing binding"),
330 ));
331 }
332 let mut entries = BTreeMap::new();
333 for (name, kind) in &module.exports {
334 if !kind.has_runtime_value() {
335 continue;
336 }
337 if demanded.is_some_and(|members| !members.iter().any(|member| member == name)) {
338 continue;
339 }
340 if let Some(value) = module.env.get(name) {
341 entries.insert(name.clone(), value);
342 }
343 }
344 if let Some(members) = demanded {
345 for member in members {
346 if !module.exports.contains_key(member) {
347 return Err(diagnostic(
348 "portable_import",
349 format!("module does not export `{member}`"),
350 ));
351 }
352 }
353 }
354 env.define(alias.to_string(), RuntimeValue::Record(Rc::new(entries)));
355 return Ok(());
356 }
357 let names = selected_names
358 .map(|names| names.to_vec())
359 .unwrap_or_else(|| module.exports.keys().cloned().collect());
360 for name in names {
361 let Some(kind) = module.exports.get(&name) else {
362 return Err(diagnostic(
363 "portable_import",
364 format!("module does not export `{name}`"),
365 ));
366 };
367 if !kind.has_runtime_value() {
368 continue;
369 }
370 let Some(value) = module.env.get(&name) else {
371 return Err(diagnostic(
372 "portable_import",
373 format!("module export `{name}` has no runtime value"),
374 ));
375 };
376 if env.contains_local(&name) {
377 return Err(diagnostic(
378 "portable_import_collision",
379 format!("imported binding `{name}` collides with an existing binding"),
380 ));
381 }
382 env.define(name, value);
383 }
384 Ok(())
385 }
386
387 fn child_env(&mut self, parent: Rc<Env>) -> Result<Rc<Env>, Diagnostic> {
388 Env::child(parent)
389 }
390
391 fn retain_environment(&mut self, environment: &Rc<Env>) {
392 self.environments.push(environment.clone());
393 }
394
395 fn charge(&mut self, amount: u64) -> Result<(), Diagnostic> {
396 let replayed = amount.min(self.replay_credit);
397 self.replay_credit -= replayed;
398 let fresh = amount - replayed;
399 if fresh > self.fuel {
400 self.fuel = 0;
401 return Err(diagnostic(
402 "execution_fuel",
403 "portable execution exhausted its deterministic fuel limit",
404 ));
405 }
406 self.fuel -= fresh;
407 Ok(())
408 }
409
410 fn charge_value_work(&mut self, value: &RuntimeValue) -> Result<(), Diagnostic> {
411 let usage = validate_runtime_value(value)?;
412 self.charge(usage.nodes as u64)
413 }
414
415 fn charge_call_validation(&mut self, arguments: &[RuntimeValue]) -> Result<(), Diagnostic> {
416 let mut nodes = 0_u64;
417 for argument in arguments {
418 let usage = validate_runtime_value(argument)?;
419 nodes = nodes.saturating_add(usage.nodes as u64);
420 }
421 self.charge(nodes)
422 }
423
424 fn charge_values_work(&mut self, values: &[&RuntimeValue]) -> Result<(), Diagnostic> {
425 let mut nodes = 0_u64;
426 for value in values {
427 let usage = validate_runtime_value(value)?;
428 nodes = nodes.saturating_add(usage.nodes as u64);
429 }
430 self.charge(nodes)
431 }
432
433 fn render_value(&mut self, value: &RuntimeValue) -> Result<String, Diagnostic> {
434 self.charge_value_work(value)?;
435 Ok(value.display())
436 }
437
438 fn push_charged(&mut self, value: RuntimeValue) -> OpStep {
439 match self.charge_value_work(&value) {
440 Ok(()) => OpStep::Push(value),
441 Err(diagnostic) => OpStep::Error(diagnostic),
442 }
443 }
444
445 fn values_equal(
446 &mut self,
447 left: &RuntimeValue,
448 right: &RuntimeValue,
449 ) -> Result<bool, Diagnostic> {
450 self.charge_values_work(&[left, right])?;
451 Ok(equal(left, right))
452 }
453
454 fn suspend(&self, input: DataValue, request: CapabilityRequest) -> Execution {
455 let Some(snapshot_key) = self.grants.snapshot_key() else {
456 return failed(
457 "snapshot_key_required",
458 "suspendable capability grants require a host-owned snapshot key",
459 );
460 };
461 let snapshot = ReplaySnapshot {
462 artifact_digest: self.program.digest(),
463 grant_fingerprint: self.grants.fingerprint(),
464 fuel_consumed: DEFAULT_FUEL - self.fuel,
465 input,
466 responses: self.responses[..self.response_cursor].to_vec(),
467 pending_request: request.id.clone(),
468 };
469 match encode_snapshot(&snapshot, snapshot_key) {
470 Ok(snapshot) => Execution::Suspended { request, snapshot },
471 Err(diagnostic) => Execution::Failed { diagnostic },
472 }
473 }
474
475 fn execute(&mut self, chunk: Arc<Chunk>, env: Rc<Env>, arguments: Vec<RuntimeValue>) -> Step {
476 let mut frames = vec![Frame::new(chunk, env, arguments)];
477 self.execute_frames(&mut frames)
478 }
479
480 fn execute_function(
481 &mut self,
482 function: &CompiledFunction,
483 env: Rc<Env>,
484 arguments: Vec<RuntimeValue>,
485 ) -> Step {
486 let frame = match self.function_frame(function, env, arguments) {
487 Ok(frame) => frame,
488 Err(diagnostic) => return Step::Error(diagnostic),
489 };
490 let mut frames = vec![frame];
491 self.execute_frames(&mut frames)
492 }
493
494 fn function_frame(
495 &mut self,
496 function: &CompiledFunction,
497 env: Rc<Env>,
498 arguments: Vec<RuntimeValue>,
499 ) -> Result<Frame, Diagnostic> {
500 let frame = Frame::for_function(function, env, arguments);
501 if function.has_rest_param && !function.params.is_empty() {
502 let rest_index = function.params.len() - 1;
503 if let Some(Some(rest)) = frame.locals.get(rest_index) {
504 self.charge_value_work(rest)?;
505 }
506 }
507 Ok(frame)
508 }
509
510 fn execute_frames(&mut self, frames: &mut Vec<Frame>) -> Step {
511 loop {
512 if let Err(diagnostic) = self.charge(1) {
513 return Step::Error(diagnostic);
514 }
515 let Some(frame) = frames.last_mut() else {
516 return Step::Error(diagnostic(
517 "execution_state",
518 "execution has no active frame",
519 ));
520 };
521 if frame.ip >= frame.chunk.code.len() {
522 return Step::Error(diagnostic(
523 "instruction_pointer",
524 "instruction pointer escaped its chunk",
525 ));
526 }
527 let offset = frame.ip;
528 let byte = frame.chunk.code[frame.ip];
529 frame.ip += 1;
530 let Some(op) = Op::from_byte(byte) else {
531 return Step::Error(diagnostic(
532 "invalid_opcode",
533 format!("invalid opcode 0x{byte:02x}"),
534 ));
535 };
536 let result = match self.execute_op(op, offset, frames) {
537 Ok(result) | Err(result) => result,
538 };
539 match result {
540 OpStep::Continue => {}
541 OpStep::Push(value) => frames
542 .last_mut()
543 .expect("active frame accepts operation result")
544 .stack
545 .push(value),
546 OpStep::Call(closure, args, tail) => {
547 if frames.len() >= MAX_FRAMES {
548 return Step::Error(diagnostic(
549 "frame_limit",
550 "portable execution exceeded its frame limit",
551 ));
552 }
553 let Some(closure_env) = closure.env.upgrade() else {
554 return Step::Error(diagnostic(
555 "closure_environment",
556 "closure environment is no longer available",
557 ));
558 };
559 let env = match self.child_env(closure_env) {
560 Ok(env) => env,
561 Err(diagnostic) => return Step::Error(diagnostic),
562 };
563 if let Err(diagnostic) = self.charge_call_validation(&args) {
564 return Step::Error(diagnostic);
565 }
566 if let Err(diagnostic) = validate_call(&closure.function, &args) {
567 return Step::Error(diagnostic);
568 }
569 let next = match self.function_frame(&closure.function, env, args) {
570 Ok(frame) => frame,
571 Err(diagnostic) => return Step::Error(diagnostic),
572 };
573 if tail {
574 *frames.last_mut().expect("caller exists") = next;
575 } else {
576 frames.push(next);
577 }
578 }
579 OpStep::Return(value) => {
580 frames.pop();
581 if let Some(caller) = frames.last_mut() {
582 caller.stack.push(value);
583 } else {
584 return Step::Value(value);
585 }
586 }
587 OpStep::Suspend(request) => return Step::Suspend(request),
588 OpStep::Throw(value) => {
589 if !handle_throw(frames, value.clone()) {
590 let message = match self.render_value(&value) {
591 Ok(message) => message,
592 Err(diagnostic) => return Step::Error(diagnostic),
593 };
594 return Step::Error(diagnostic("harn_throw", message));
595 }
596 }
597 OpStep::Error(error) => return Step::Error(error),
598 }
599 if frames
600 .last()
601 .is_some_and(|frame| frame.stack.len() > MAX_OPERAND_STACK)
602 {
603 return Step::Error(diagnostic(
604 "operand_stack_limit",
605 "portable execution exceeded its operand stack limit",
606 ));
607 }
608 }
609 }
610
611 #[allow(clippy::too_many_lines)]
612 fn execute_op(
613 &mut self,
614 op: Op,
615 offset: usize,
616 frames: &mut [Frame],
617 ) -> Result<OpStep, OpStep> {
618 let frame = frames.last_mut().expect("active frame");
619 macro_rules! pop {
620 () => {
621 match frame.stack.pop() {
622 Some(value) => value,
623 None => {
624 return Err(OpStep::Error(diagnostic(
625 "stack_underflow",
626 format!("{} at {offset}", op.name()),
627 )))
628 }
629 }
630 };
631 }
632 match op {
633 Op::Constant => {
634 let index = read_u16(frame)?;
635 let Some(value) = frame.chunk.constants.get(index).cloned() else {
636 return Err(invalid_index("constant", index));
637 };
638 frame.stack.push(RuntimeValue::from(value));
639 }
640 Op::Nil => frame.stack.push(RuntimeValue::Nil),
641 Op::True => frame.stack.push(RuntimeValue::Bool(true)),
642 Op::False => frame.stack.push(RuntimeValue::Bool(false)),
643 Op::RootHarness => frame.stack.push(RuntimeValue::Harness("root".to_string())),
644 Op::GetVar => {
645 let name = read_constant_string(frame)?;
646 frame.stack.push(
647 frame
648 .env
649 .get(&name)
650 .unwrap_or_else(|| RuntimeValue::Builtin(name)),
651 );
652 }
653 Op::DefLet | Op::DefVar | Op::DefCell => {
654 let name = read_constant_string(frame)?;
655 let value = pop!();
656 frame.env.define(name, value);
657 }
658 Op::SetVar => {
659 let name = read_constant_string(frame)?;
660 let value = pop!();
661 frame.env.set(&name, value);
662 }
663 Op::PushScope => {
664 frame.env = self.child_env(frame.env.clone()).map_err(OpStep::Error)?;
665 }
666 Op::PopScope => {
667 if let Some(parent) = &frame.env.parent {
668 frame.env = parent.clone();
669 }
670 }
671 Op::GetLocalSlot => {
672 let slot = read_u16(frame)?;
673 let Some(value) = frame.locals.get(slot).and_then(Clone::clone) else {
674 return Err(invalid_index("local", slot));
675 };
676 frame.stack.push(value);
677 }
678 Op::DefLocalSlot | Op::SetLocalSlot => {
679 let slot = read_u16(frame)?;
680 let value = pop!();
681 if slot >= frame.locals.len() {
682 return Err(invalid_index("local", slot));
683 }
684 frame.locals[slot] = Some(value.clone());
685 if let Some(local) = frame.chunk.local_slots.get(slot) {
686 if op == Op::DefLocalSlot {
687 frame.env.define(local.name.clone(), value);
688 } else {
689 frame.env.set(&local.name, value);
690 }
691 }
692 }
693 Op::ConcatAssignLocal => {
694 let slot = read_u16(frame)?;
695 let rhs = pop!();
696 let Some(local) = frame.chunk.local_slots.get(slot) else {
697 return Err(invalid_index("local", slot));
698 };
699 if !local.mutable {
700 return Err(OpStep::Error(diagnostic(
701 "immutable_assignment",
702 format!("cannot assign to immutable binding `{}`", local.name),
703 )));
704 }
705 let Some(lhs) = frame.locals.get(slot).and_then(Clone::clone) else {
706 return Err(invalid_index("local", slot));
707 };
708 let value = add(lhs, rhs).map_err(OpStep::Error)?;
709 self.charge_value_work(&value).map_err(OpStep::Error)?;
710 frame.locals[slot] = Some(value.clone());
711 frame.env.set(&local.name, value);
712 }
713 Op::GetArgc => frame.stack.push(RuntimeValue::Int(frame.argc as i64)),
714 Op::Pop => {
715 pop!();
716 }
717 Op::Dup => {
718 let value = pop!();
719 frame.stack.push(value.clone());
720 frame.stack.push(value);
721 }
722 Op::Swap => {
723 let right = pop!();
724 let left = pop!();
725 frame.stack.push(right);
726 frame.stack.push(left);
727 }
728 Op::Add | Op::AddInt | Op::AddFloat => {
729 let value = binary(frame, add)?;
730 self.charge_value_work(&value).map_err(OpStep::Error)?;
731 frame.stack.push(value);
732 }
733 Op::Sub | Op::SubInt | Op::SubFloat => {
734 let value = binary(frame, sub)?;
735 self.charge_value_work(&value).map_err(OpStep::Error)?;
736 frame.stack.push(value);
737 }
738 Op::Mul | Op::MulInt | Op::MulFloat => {
739 let value = binary(frame, mul)?;
740 self.charge_value_work(&value).map_err(OpStep::Error)?;
741 frame.stack.push(value);
742 }
743 Op::Div | Op::DivInt | Op::DivFloat => {
744 let value = binary(frame, div)?;
745 self.charge_value_work(&value).map_err(OpStep::Error)?;
746 frame.stack.push(value);
747 }
748 Op::Mod | Op::ModInt | Op::ModFloat => {
749 let value = binary(frame, modulo)?;
750 self.charge_value_work(&value).map_err(OpStep::Error)?;
751 frame.stack.push(value);
752 }
753 Op::Pow => {
754 let value = binary(frame, pow)?;
755 self.charge_value_work(&value).map_err(OpStep::Error)?;
756 frame.stack.push(value);
757 }
758 Op::Negate => {
759 let value = pop!();
760 frame.stack.push(negate(value).map_err(OpStep::Error)?);
761 }
762 Op::Not => {
763 let value = pop!();
764 frame.stack.push(RuntimeValue::Bool(!value.truthy()));
765 }
766 Op::Equal | Op::EqualInt | Op::EqualFloat | Op::EqualBool | Op::EqualString => {
767 compare_equality(self, frame, true)?;
768 }
769 Op::NotEqual
770 | Op::NotEqualInt
771 | Op::NotEqualFloat
772 | Op::NotEqualBool
773 | Op::NotEqualString => compare_equality(self, frame, false)?,
774 Op::Less | Op::LessInt | Op::LessFloat => compare(self, frame, |value| value < 0)?,
775 Op::Greater | Op::GreaterInt | Op::GreaterFloat => {
776 compare(self, frame, |value| value > 0)?;
777 }
778 Op::LessEqual | Op::LessEqualInt | Op::LessEqualFloat => {
779 compare(self, frame, |value| value <= 0)?;
780 }
781 Op::GreaterEqual | Op::GreaterEqualInt | Op::GreaterEqualFloat => {
782 compare(self, frame, |value| value >= 0)?;
783 }
784 Op::Jump => frame.ip = read_u16(frame)?,
785 Op::JumpIfFalse => {
786 let target = read_u16(frame)?;
787 if !frame.stack.last().is_some_and(RuntimeValue::truthy) {
788 frame.ip = target;
789 }
790 }
791 Op::JumpIfTrue => {
792 let target = read_u16(frame)?;
793 if frame.stack.last().is_some_and(RuntimeValue::truthy) {
794 frame.ip = target;
795 }
796 }
797 Op::Closure => {
798 let index = read_u16(frame)?;
799 let Some(function) = frame.chunk.functions.get(index).cloned() else {
800 return Err(invalid_index("function", index));
801 };
802 self.retain_environment(&frame.env);
803 frame.stack.push(RuntimeValue::Closure(Closure {
804 function,
805 env: Rc::downgrade(&frame.env),
806 }));
807 }
808 Op::Call | Op::TailCall => {
809 let argc = read_u8(frame)?;
810 let args = pop_args(frame, argc)?;
811 let callee = pop!();
812 return Ok(call_value(
813 self,
814 &frame.env,
815 callee,
816 args,
817 op == Op::TailCall,
818 ));
819 }
820 Op::Return => {
821 return Ok(OpStep::Return(
822 frame.stack.pop().unwrap_or(RuntimeValue::Nil),
823 ))
824 }
825 Op::BuildList => {
826 let count = read_u16(frame)?;
827 let values = pop_args(frame, count)?;
828 let value = RuntimeValue::List(Rc::new(values));
829 self.charge_value_work(&value).map_err(OpStep::Error)?;
830 frame.stack.push(value);
831 }
832 Op::BuildDict => {
833 let count = read_u16(frame)?;
834 let values = pop_args(frame, count * 2)?;
835 let mut map = BTreeMap::new();
836 for pair in values.chunks_exact(2) {
837 let key = self.render_value(&pair[0]).map_err(OpStep::Error)?;
838 map.insert(key, pair[1].clone());
839 }
840 let value = RuntimeValue::Record(Rc::new(map));
841 self.charge_value_work(&value).map_err(OpStep::Error)?;
842 frame.stack.push(value);
843 }
844 Op::GetProperty | Op::GetPropertyOpt => {
845 let name = read_constant_string(frame)?;
846 let value = pop!();
847 match get_property(&value, &name) {
848 Some(value) => frame.stack.push(value),
849 None if op == Op::GetPropertyOpt => frame.stack.push(RuntimeValue::Nil),
850 None => {
851 return Err(OpStep::Error(diagnostic(
852 "missing_property",
853 format!("value has no property `{name}`"),
854 )))
855 }
856 }
857 }
858 Op::Subscript | Op::SubscriptOpt => {
859 let index = pop!();
860 let value = pop!();
861 match self.subscript(&value, &index).map_err(OpStep::Error)? {
862 Some(value) => frame.stack.push(value),
863 None if op == Op::SubscriptOpt => frame.stack.push(RuntimeValue::Nil),
864 None => {
865 return Err(OpStep::Error(diagnostic(
866 "subscript",
867 "subscript does not exist",
868 )))
869 }
870 }
871 }
872 Op::SetProperty => {
873 let property = read_constant_string(frame)?;
874 let binding = read_constant_string(frame)?;
875 let value = pop!();
876 let Some(target) = frame.env.get(&binding) else {
877 return Err(OpStep::Error(diagnostic(
878 "undefined_variable",
879 format!("cannot assign property on undefined binding `{binding}`"),
880 )));
881 };
882 let updated =
883 set_property_value(target, &property, value).map_err(OpStep::Error)?;
884 self.charge_value_work(&updated).map_err(OpStep::Error)?;
885 frame.env.set(&binding, updated);
886 }
887 Op::SetSubscript => {
888 let binding = read_constant_string(frame)?;
889 let index = pop!();
890 let value = pop!();
891 let Some(target) = frame.env.get(&binding) else {
892 return Err(OpStep::Error(diagnostic(
893 "undefined_variable",
894 format!("cannot assign subscript on undefined binding `{binding}`"),
895 )));
896 };
897 let updated = set_subscript_value(target, index, value).map_err(OpStep::Error)?;
898 self.charge_value_work(&updated).map_err(OpStep::Error)?;
899 frame.env.set(&binding, updated);
900 }
901 Op::SetLocalSlotProperty => {
902 let property = read_constant_string(frame)?;
903 let slot = read_u16(frame)?;
904 let value = pop!();
905 let Some(local) = frame.chunk.local_slots.get(slot) else {
906 return Err(invalid_index("local", slot));
907 };
908 if !local.mutable {
909 return Err(OpStep::Error(diagnostic(
910 "immutable_assignment",
911 format!("cannot assign to immutable binding `{}`", local.name),
912 )));
913 }
914 let Some(target) = frame.locals.get(slot).and_then(Clone::clone) else {
915 return Err(invalid_index("local", slot));
916 };
917 let updated =
918 set_property_value(target, &property, value).map_err(OpStep::Error)?;
919 self.charge_value_work(&updated).map_err(OpStep::Error)?;
920 frame.locals[slot] = Some(updated.clone());
921 frame.env.set(&local.name, updated);
922 }
923 Op::SetLocalSlotSubscript => {
924 let slot = read_u16(frame)?;
925 let index = pop!();
926 let value = pop!();
927 let Some(local) = frame.chunk.local_slots.get(slot) else {
928 return Err(invalid_index("local", slot));
929 };
930 if !local.mutable {
931 return Err(OpStep::Error(diagnostic(
932 "immutable_assignment",
933 format!("cannot assign to immutable binding `{}`", local.name),
934 )));
935 }
936 let Some(target) = frame.locals.get(slot).and_then(Clone::clone) else {
937 return Err(invalid_index("local", slot));
938 };
939 let updated = set_subscript_value(target, index, value).map_err(OpStep::Error)?;
940 self.charge_value_work(&updated).map_err(OpStep::Error)?;
941 frame.locals[slot] = Some(updated.clone());
942 frame.env.set(&local.name, updated);
943 }
944 Op::Slice => {
945 let end = pop!();
946 let start = pop!();
947 let value = pop!();
948 let value = slice(value, start, end).map_err(OpStep::Error)?;
949 self.charge_value_work(&value).map_err(OpStep::Error)?;
950 frame.stack.push(value);
951 }
952 Op::MethodCall | Op::MethodCallOpt => {
953 let name = read_constant_string(frame)?;
954 let argc = read_u8(frame)?;
955 let args = pop_args(frame, argc)?;
956 let receiver = pop!();
957 if op == Op::MethodCallOpt && matches!(receiver, RuntimeValue::Nil) {
958 frame.stack.push(RuntimeValue::Nil);
959 } else {
960 return Ok(self.call_method(receiver, &name, args));
961 }
962 }
963 Op::Concat => {
964 let count = read_u16(frame)?;
965 let values = pop_args(frame, count)?;
966 let mut rendered = String::new();
967 for value in &values {
968 let part = self.render_value(value).map_err(OpStep::Error)?;
969 if rendered.len().saturating_add(part.len()) > MAX_VALUE_BYTES {
970 return Err(OpStep::Error(diagnostic(
971 "value_byte_limit",
972 "string interpolation exceeds the portable value byte limit",
973 )));
974 }
975 rendered.push_str(&part);
976 }
977 frame.stack.push(RuntimeValue::String(Arc::from(rendered)));
978 }
979 Op::Contains => {
980 let container = pop!();
981 let item = pop!();
982 let found = self.contains(&container, &item).map_err(OpStep::Error)?;
983 frame.stack.push(RuntimeValue::Bool(found));
984 }
985 Op::IterInit => {
986 if self.iterators.len() >= MAX_ITERATORS {
987 return Err(OpStep::Error(diagnostic(
988 "iterator_limit",
989 "portable execution exceeded its iterator limit",
990 )));
991 }
992 let iterable = pop!();
993 let iterator = match iterable {
994 RuntimeValue::List(values) => IteratorState::List { values, index: 0 },
995 RuntimeValue::Record(values) => IteratorState::Record {
996 keys: values.keys().cloned().collect(),
997 values,
998 index: 0,
999 },
1000 other => {
1001 return Err(OpStep::Error(diagnostic(
1002 "iterator_type",
1003 format!(
1004 "cannot iterate over {} in the portable kernel",
1005 runtime_value_kind(&other)
1006 ),
1007 )))
1008 }
1009 };
1010 self.iterators.push(iterator);
1011 }
1012 Op::IterNext => {
1013 let target = read_u16(frame)?;
1014 let Some(iterator) = self.iterators.last_mut() else {
1015 return Err(OpStep::Error(diagnostic(
1016 "iterator_state",
1017 "iterator step has no active iterator",
1018 )));
1019 };
1020 match iterator {
1021 IteratorState::List { values, index } => {
1022 if let Some(value) = values.get(*index).cloned() {
1023 *index += 1;
1024 frame.stack.push(value);
1025 } else {
1026 self.iterators.pop();
1027 frame.ip = target;
1028 }
1029 }
1030 IteratorState::Record {
1031 keys,
1032 values,
1033 index,
1034 } => {
1035 if let Some(key) = keys.get(*index) {
1036 let value = values.get(key).cloned().unwrap_or(RuntimeValue::Nil);
1037 *index += 1;
1038 frame
1039 .stack
1040 .push(RuntimeValue::Record(Rc::new(BTreeMap::from([
1041 (
1042 "key".to_string(),
1043 RuntimeValue::String(Arc::from(key.as_str())),
1044 ),
1045 ("value".to_string(), value),
1046 ]))));
1047 } else {
1048 self.iterators.pop();
1049 frame.ip = target;
1050 }
1051 }
1052 }
1053 }
1054 Op::PopIterator => {
1055 self.iterators.pop();
1056 }
1057 Op::TryCatchSetup => {
1058 let target = read_u16(frame)?;
1059 let _type_name = read_u16(frame)?;
1060 frame.handlers.push(Handler {
1061 target,
1062 stack_depth: frame.stack.len(),
1063 env: frame.env.clone(),
1064 });
1065 }
1066 Op::PopHandler => {
1067 frame.handlers.pop();
1068 }
1069 Op::Throw => return Ok(OpStep::Throw(pop!())),
1070 Op::Import => {
1071 let path = read_constant_string(frame)?;
1072 let env = frame.env.clone();
1073 return Ok(self.import_root_module(&path, None, None, &env));
1074 }
1075 Op::SelectiveImport => {
1076 let path = read_constant_string(frame)?;
1077 let names = read_constant_string(frame)?;
1078 let selected = names
1079 .split(',')
1080 .filter(|name| !name.is_empty())
1081 .map(str::to_string)
1082 .collect::<Vec<_>>();
1083 let env = frame.env.clone();
1084 return Ok(self.import_root_module(&path, Some(&selected), None, &env));
1085 }
1086 Op::NamespaceImport => {
1087 let path = read_constant_string(frame)?;
1088 let alias = read_constant_string(frame)?;
1089 let env = frame.env.clone();
1090 return Ok(self.import_root_module(&path, None, Some((&alias, None)), &env));
1091 }
1092 Op::NamespaceImportMembers => {
1099 let path = read_constant_string(frame)?;
1100 let alias = read_constant_string(frame)?;
1101 let members = read_constant_string(frame)?;
1102 let demanded = members
1103 .split(',')
1104 .filter(|name| !name.is_empty())
1105 .map(str::to_string)
1106 .collect::<Vec<_>>();
1107 let env = frame.env.clone();
1108 return Ok(self.import_root_module(
1109 &path,
1110 None,
1111 Some((&alias, Some(&demanded))),
1112 &env,
1113 ));
1114 }
1115 Op::BuildEnum => {
1116 let enum_name = read_constant_string(frame)?;
1117 let variant = read_constant_string(frame)?;
1118 let field_count = read_u16(frame)?;
1119 let fields = pop_args(frame, field_count)?;
1120 let value = RuntimeValue::Enum(Rc::new(EnumValue {
1121 enum_name: Arc::from(enum_name),
1122 variant: Arc::from(variant),
1123 fields: Rc::new(fields),
1124 }));
1125 self.charge_value_work(&value).map_err(OpStep::Error)?;
1126 frame.stack.push(value);
1127 }
1128 Op::MatchEnum => {
1129 let enum_name = read_constant_string(frame)?;
1130 let variant = read_constant_string(frame)?;
1131 let value = pop!();
1132 let matches = matches!(
1133 &value,
1134 RuntimeValue::Enum(candidate)
1135 if candidate.is_variant(&enum_name, &variant)
1136 );
1137 frame.stack.push(value);
1138 frame.stack.push(RuntimeValue::Bool(matches));
1139 }
1140 Op::TryWrapOk => {
1141 let value = pop!();
1142 if matches!(&value, RuntimeValue::Enum(candidate) if candidate.enum_name.as_ref() == "Result")
1143 {
1144 frame.stack.push(value);
1145 } else {
1146 frame.stack.push(RuntimeValue::Enum(Rc::new(EnumValue {
1147 enum_name: Arc::from("Result"),
1148 variant: Arc::from("Ok"),
1149 fields: Rc::new(vec![value]),
1150 })));
1151 }
1152 }
1153 Op::TryUnwrap => {
1154 let value = pop!();
1155 let RuntimeValue::Enum(result) = &value else {
1156 return Err(OpStep::Error(diagnostic(
1157 "try_unwrap_type",
1158 format!(
1159 "? operator requires a Result value, got {}",
1160 runtime_value_kind(&value)
1161 ),
1162 )));
1163 };
1164 if result.enum_name.as_ref() != "Result" {
1165 return Err(OpStep::Error(diagnostic(
1166 "try_unwrap_type",
1167 format!(
1168 "? operator requires a Result value, got {}",
1169 runtime_value_kind(&value)
1170 ),
1171 )));
1172 }
1173 if result.variant.as_ref() == "Ok" {
1174 frame
1175 .stack
1176 .push(result.fields.first().cloned().unwrap_or(RuntimeValue::Nil));
1177 } else {
1178 return Ok(OpStep::Return(value));
1179 }
1180 }
1181 Op::AssertBindingType => {
1182 let index = read_u16(frame)?;
1183 let Some(slot) = frame.chunk.binding_types.get(index) else {
1184 return Err(invalid_index("binding type", index));
1185 };
1186 let Some(value) = frame.stack.last() else {
1187 return Err(OpStep::Error(diagnostic(
1188 "stack_underflow",
1189 "AssertBindingType requires the bound value on the stack",
1190 )));
1191 };
1192 type_guard::validate_binding(value, slot).map_err(OpStep::Error)?;
1193 }
1194 Op::CheckType => {
1195 return Err(OpStep::Error(diagnostic(
1196 "unsupported_portable_opcode",
1197 format!("{} is not implemented by the portable kernel", op.name()),
1198 )))
1199 }
1200 Op::CallBuiltin => {
1201 frame.ip += 8;
1202 let name = read_constant_string(frame)?;
1203 let argc = read_u8(frame)?;
1204 let args = pop_args(frame, argc)?;
1205 return Ok(call_named(self, &frame.env, &name, args, false));
1206 }
1207 Op::CallBuiltinSpread => {
1208 frame.ip += 8;
1209 let name = read_constant_string(frame)?;
1210 let spread = pop!();
1211 let RuntimeValue::List(args) = spread else {
1212 return Err(OpStep::Error(diagnostic(
1213 "spread_type",
1214 "spread call requires a list",
1215 )));
1216 };
1217 return Ok(call_named(
1218 self,
1219 &frame.env,
1220 &name,
1221 Rc::unwrap_or_clone(args),
1222 false,
1223 ));
1224 }
1225 unsupported @ (Op::Pipe
1226 | Op::Parallel
1227 | Op::ParallelMap
1228 | Op::ParallelMapStream
1229 | Op::ParallelSettle
1230 | Op::Spawn
1231 | Op::SyncMutexEnter
1232 | Op::SyncMutexEnterKeyed
1233 | Op::TaskScopeEnter
1234 | Op::TaskScopeExit
1235 | Op::DeadlineSetup
1236 | Op::DeadlineEnd
1237 | Op::CallSpread
1238 | Op::MethodCallSpread
1239 | Op::Yield) => {
1240 return Err(OpStep::Error(diagnostic(
1241 "unsupported_portable_opcode",
1242 format!(
1243 "{} is not implemented by the portable kernel",
1244 unsupported.name()
1245 ),
1246 )))
1247 }
1248 }
1249 Ok(OpStep::Continue)
1250 }
1251}
1252
1253struct Env {
1254 values: RefCell<BTreeMap<String, RuntimeValue>>,
1255 parent: Option<Rc<Env>>,
1256 depth: usize,
1257}
1258
1259struct ModuleInstance {
1260 env: Rc<Env>,
1261 exports: BTreeMap<String, crate::PortableExportKind>,
1262}
1263
1264enum IteratorState {
1265 List {
1266 values: Rc<Vec<RuntimeValue>>,
1267 index: usize,
1268 },
1269 Record {
1270 values: Rc<BTreeMap<String, RuntimeValue>>,
1271 keys: Vec<String>,
1272 index: usize,
1273 },
1274}
1275
1276enum ModuleStep {
1277 Ready(Rc<ModuleInstance>),
1278 Suspend(CapabilityRequest),
1279 Error(Diagnostic),
1280}
1281
1282type NamespaceProjection<'a> = (&'a str, Option<&'a [String]>);
1286
1287impl Env {
1288 fn root() -> Rc<Self> {
1289 Rc::new(Self {
1290 values: RefCell::new(BTreeMap::new()),
1291 parent: None,
1292 depth: 0,
1293 })
1294 }
1295 fn child(parent: Rc<Self>) -> Result<Rc<Self>, Diagnostic> {
1296 if parent.depth >= MAX_SCOPE_DEPTH {
1297 return Err(diagnostic(
1298 "scope_depth_limit",
1299 "portable execution exceeded its lexical scope depth limit",
1300 ));
1301 }
1302 let depth = parent.depth + 1;
1303 Ok(Rc::new(Self {
1304 values: RefCell::new(BTreeMap::new()),
1305 parent: Some(parent),
1306 depth,
1307 }))
1308 }
1309 fn define(&self, name: String, value: RuntimeValue) {
1310 self.values.borrow_mut().insert(name, value);
1311 }
1312 fn contains_local(&self, name: &str) -> bool {
1313 self.values.borrow().contains_key(name)
1314 }
1315 fn get(&self, name: &str) -> Option<RuntimeValue> {
1316 self.values
1317 .borrow()
1318 .get(name)
1319 .cloned()
1320 .or_else(|| self.parent.as_ref().and_then(|parent| parent.get(name)))
1321 }
1322 fn set(&self, name: &str, value: RuntimeValue) {
1323 if self.values.borrow().contains_key(name) {
1324 self.values.borrow_mut().insert(name.to_string(), value);
1325 } else if let Some(parent) = &self.parent {
1326 parent.set(name, value);
1327 } else {
1328 self.values.borrow_mut().insert(name.to_string(), value);
1329 }
1330 }
1331}
1332
1333struct Frame {
1334 chunk: Arc<Chunk>,
1335 ip: usize,
1336 stack: Vec<RuntimeValue>,
1337 locals: Vec<Option<RuntimeValue>>,
1338 env: Rc<Env>,
1339 handlers: Vec<Handler>,
1340 argc: usize,
1341}
1342impl Frame {
1343 fn new(chunk: Arc<Chunk>, env: Rc<Env>, arguments: Vec<RuntimeValue>) -> Self {
1344 let argc = arguments.len();
1345 let mut locals = vec![None; chunk.local_slots.len()];
1346 for (index, value) in arguments.into_iter().enumerate().take(locals.len()) {
1347 locals[index] = Some(value);
1348 }
1349 Self {
1350 chunk,
1351 ip: 0,
1352 stack: Vec::new(),
1353 locals,
1354 env,
1355 handlers: Vec::new(),
1356 argc,
1357 }
1358 }
1359
1360 fn for_function(
1361 function: &CompiledFunction,
1362 env: Rc<Env>,
1363 mut arguments: Vec<RuntimeValue>,
1364 ) -> Self {
1365 let supplied = arguments.len();
1366 if function.has_rest_param && !function.params.is_empty() {
1367 let rest_index = function.params.len() - 1;
1368 let rest = if arguments.len() > rest_index {
1369 arguments.split_off(rest_index)
1370 } else {
1371 Vec::new()
1372 };
1373 arguments.push(RuntimeValue::List(Rc::new(rest)));
1374 } else {
1375 arguments.truncate(function.params.len());
1376 }
1377 let mut frame = Self::new(function.chunk.clone(), env, arguments);
1378 for (parameter, value) in function.params.iter().zip(frame.locals.iter()) {
1379 if let Some(value) = value {
1380 frame.env.define(parameter.name.clone(), value.clone());
1381 }
1382 }
1383 frame.argc = supplied;
1384 frame
1385 }
1386}
1387struct Handler {
1388 target: usize,
1389 stack_depth: usize,
1390 env: Rc<Env>,
1391}
1392enum Step {
1393 Value(RuntimeValue),
1394 Suspend(CapabilityRequest),
1395 Error(Diagnostic),
1396}
1397enum OpStep {
1398 Continue,
1399 Push(RuntimeValue),
1400 Call(Closure, Vec<RuntimeValue>, bool),
1401 Return(RuntimeValue),
1402 Suspend(CapabilityRequest),
1403 Throw(RuntimeValue),
1404 Error(Diagnostic),
1405}
1406
1407fn read_u8(frame: &mut Frame) -> Result<usize, OpStep> {
1408 let value = *frame.chunk.code.get(frame.ip).ok_or_else(|| {
1409 OpStep::Error(diagnostic(
1410 "truncated_instruction",
1411 "u8 operand is truncated",
1412 ))
1413 })?;
1414 frame.ip += 1;
1415 Ok(value as usize)
1416}
1417fn read_u16(frame: &mut Frame) -> Result<usize, OpStep> {
1418 let bytes = frame
1419 .chunk
1420 .code
1421 .get(frame.ip..frame.ip + 2)
1422 .ok_or_else(|| {
1423 OpStep::Error(diagnostic(
1424 "truncated_instruction",
1425 "u16 operand is truncated",
1426 ))
1427 })?;
1428 frame.ip += 2;
1429 Ok(u16::from_be_bytes([bytes[0], bytes[1]]) as usize)
1430}
1431fn read_constant_string(frame: &mut Frame) -> Result<String, OpStep> {
1432 let index = read_u16(frame)?;
1433 match frame.chunk.constants.get(index) {
1434 Some(Constant::String(value)) => Ok(value.clone()),
1435 _ => Err(invalid_index("string constant", index)),
1436 }
1437}
1438fn pop_args(frame: &mut Frame, count: usize) -> Result<Vec<RuntimeValue>, OpStep> {
1439 if frame.stack.len() < count {
1440 return Err(OpStep::Error(diagnostic(
1441 "stack_underflow",
1442 "call argument stack is truncated",
1443 )));
1444 }
1445 Ok(frame.stack.split_off(frame.stack.len() - count))
1446}
1447fn invalid_index(kind: &str, index: usize) -> OpStep {
1448 OpStep::Error(diagnostic(
1449 "invalid_index",
1450 format!("{kind} index {index} is out of bounds"),
1451 ))
1452}
1453
1454fn call_value(
1455 machine: &mut Machine<'_>,
1456 env: &Rc<Env>,
1457 callee: RuntimeValue,
1458 args: Vec<RuntimeValue>,
1459 tail: bool,
1460) -> OpStep {
1461 match callee {
1462 RuntimeValue::Closure(closure) => OpStep::Call(closure, args, tail),
1463 RuntimeValue::Builtin(name) => machine.call_builtin(&name, args),
1464 RuntimeValue::String(name) => call_named(machine, env, &name, args, tail),
1468 RuntimeValue::Harness(capability) => {
1469 machine.call_method(RuntimeValue::Harness(capability), "call", args)
1470 }
1471 other => OpStep::Error(diagnostic(
1472 "not_callable",
1473 format!("{} is not callable", runtime_value_kind(&other)),
1474 )),
1475 }
1476}
1477
1478fn call_named(
1479 machine: &mut Machine<'_>,
1480 env: &Rc<Env>,
1481 name: &str,
1482 args: Vec<RuntimeValue>,
1483 tail: bool,
1484) -> OpStep {
1485 match env.get(name) {
1486 Some(callee) => call_value(machine, env, callee, args, tail),
1487 None => machine.call_builtin(name, args),
1488 }
1489}