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::CheckType => {
1182 return Err(OpStep::Error(diagnostic(
1183 "unsupported_portable_opcode",
1184 format!("{} is not implemented by the portable kernel", op.name()),
1185 )))
1186 }
1187 Op::CallBuiltin => {
1188 frame.ip += 8;
1189 let name = read_constant_string(frame)?;
1190 let argc = read_u8(frame)?;
1191 let args = pop_args(frame, argc)?;
1192 return Ok(call_named(self, &frame.env, &name, args, false));
1193 }
1194 Op::CallBuiltinSpread => {
1195 frame.ip += 8;
1196 let name = read_constant_string(frame)?;
1197 let spread = pop!();
1198 let RuntimeValue::List(args) = spread else {
1199 return Err(OpStep::Error(diagnostic(
1200 "spread_type",
1201 "spread call requires a list",
1202 )));
1203 };
1204 return Ok(call_named(
1205 self,
1206 &frame.env,
1207 &name,
1208 Rc::unwrap_or_clone(args),
1209 false,
1210 ));
1211 }
1212 unsupported @ (Op::Pipe
1213 | Op::Parallel
1214 | Op::ParallelMap
1215 | Op::ParallelMapStream
1216 | Op::ParallelSettle
1217 | Op::Spawn
1218 | Op::SyncMutexEnter
1219 | Op::SyncMutexEnterKeyed
1220 | Op::TaskScopeEnter
1221 | Op::TaskScopeExit
1222 | Op::DeadlineSetup
1223 | Op::DeadlineEnd
1224 | Op::CallSpread
1225 | Op::MethodCallSpread
1226 | Op::Yield) => {
1227 return Err(OpStep::Error(diagnostic(
1228 "unsupported_portable_opcode",
1229 format!(
1230 "{} is not implemented by the portable kernel",
1231 unsupported.name()
1232 ),
1233 )))
1234 }
1235 }
1236 Ok(OpStep::Continue)
1237 }
1238}
1239
1240struct Env {
1241 values: RefCell<BTreeMap<String, RuntimeValue>>,
1242 parent: Option<Rc<Env>>,
1243 depth: usize,
1244}
1245
1246struct ModuleInstance {
1247 env: Rc<Env>,
1248 exports: BTreeMap<String, crate::PortableExportKind>,
1249}
1250
1251enum IteratorState {
1252 List {
1253 values: Rc<Vec<RuntimeValue>>,
1254 index: usize,
1255 },
1256 Record {
1257 values: Rc<BTreeMap<String, RuntimeValue>>,
1258 keys: Vec<String>,
1259 index: usize,
1260 },
1261}
1262
1263enum ModuleStep {
1264 Ready(Rc<ModuleInstance>),
1265 Suspend(CapabilityRequest),
1266 Error(Diagnostic),
1267}
1268
1269type NamespaceProjection<'a> = (&'a str, Option<&'a [String]>);
1273
1274impl Env {
1275 fn root() -> Rc<Self> {
1276 Rc::new(Self {
1277 values: RefCell::new(BTreeMap::new()),
1278 parent: None,
1279 depth: 0,
1280 })
1281 }
1282 fn child(parent: Rc<Self>) -> Result<Rc<Self>, Diagnostic> {
1283 if parent.depth >= MAX_SCOPE_DEPTH {
1284 return Err(diagnostic(
1285 "scope_depth_limit",
1286 "portable execution exceeded its lexical scope depth limit",
1287 ));
1288 }
1289 let depth = parent.depth + 1;
1290 Ok(Rc::new(Self {
1291 values: RefCell::new(BTreeMap::new()),
1292 parent: Some(parent),
1293 depth,
1294 }))
1295 }
1296 fn define(&self, name: String, value: RuntimeValue) {
1297 self.values.borrow_mut().insert(name, value);
1298 }
1299 fn contains_local(&self, name: &str) -> bool {
1300 self.values.borrow().contains_key(name)
1301 }
1302 fn get(&self, name: &str) -> Option<RuntimeValue> {
1303 self.values
1304 .borrow()
1305 .get(name)
1306 .cloned()
1307 .or_else(|| self.parent.as_ref().and_then(|parent| parent.get(name)))
1308 }
1309 fn set(&self, name: &str, value: RuntimeValue) {
1310 if self.values.borrow().contains_key(name) {
1311 self.values.borrow_mut().insert(name.to_string(), value);
1312 } else if let Some(parent) = &self.parent {
1313 parent.set(name, value);
1314 } else {
1315 self.values.borrow_mut().insert(name.to_string(), value);
1316 }
1317 }
1318}
1319
1320struct Frame {
1321 chunk: Arc<Chunk>,
1322 ip: usize,
1323 stack: Vec<RuntimeValue>,
1324 locals: Vec<Option<RuntimeValue>>,
1325 env: Rc<Env>,
1326 handlers: Vec<Handler>,
1327 argc: usize,
1328}
1329impl Frame {
1330 fn new(chunk: Arc<Chunk>, env: Rc<Env>, arguments: Vec<RuntimeValue>) -> Self {
1331 let argc = arguments.len();
1332 let mut locals = vec![None; chunk.local_slots.len()];
1333 for (index, value) in arguments.into_iter().enumerate().take(locals.len()) {
1334 locals[index] = Some(value);
1335 }
1336 Self {
1337 chunk,
1338 ip: 0,
1339 stack: Vec::new(),
1340 locals,
1341 env,
1342 handlers: Vec::new(),
1343 argc,
1344 }
1345 }
1346
1347 fn for_function(
1348 function: &CompiledFunction,
1349 env: Rc<Env>,
1350 mut arguments: Vec<RuntimeValue>,
1351 ) -> Self {
1352 let supplied = arguments.len();
1353 if function.has_rest_param && !function.params.is_empty() {
1354 let rest_index = function.params.len() - 1;
1355 let rest = if arguments.len() > rest_index {
1356 arguments.split_off(rest_index)
1357 } else {
1358 Vec::new()
1359 };
1360 arguments.push(RuntimeValue::List(Rc::new(rest)));
1361 } else {
1362 arguments.truncate(function.params.len());
1363 }
1364 let mut frame = Self::new(function.chunk.clone(), env, arguments);
1365 for (parameter, value) in function.params.iter().zip(frame.locals.iter()) {
1366 if let Some(value) = value {
1367 frame.env.define(parameter.name.clone(), value.clone());
1368 }
1369 }
1370 frame.argc = supplied;
1371 frame
1372 }
1373}
1374struct Handler {
1375 target: usize,
1376 stack_depth: usize,
1377 env: Rc<Env>,
1378}
1379enum Step {
1380 Value(RuntimeValue),
1381 Suspend(CapabilityRequest),
1382 Error(Diagnostic),
1383}
1384enum OpStep {
1385 Continue,
1386 Push(RuntimeValue),
1387 Call(Closure, Vec<RuntimeValue>, bool),
1388 Return(RuntimeValue),
1389 Suspend(CapabilityRequest),
1390 Throw(RuntimeValue),
1391 Error(Diagnostic),
1392}
1393
1394fn read_u8(frame: &mut Frame) -> Result<usize, OpStep> {
1395 let value = *frame.chunk.code.get(frame.ip).ok_or_else(|| {
1396 OpStep::Error(diagnostic(
1397 "truncated_instruction",
1398 "u8 operand is truncated",
1399 ))
1400 })?;
1401 frame.ip += 1;
1402 Ok(value as usize)
1403}
1404fn read_u16(frame: &mut Frame) -> Result<usize, OpStep> {
1405 let bytes = frame
1406 .chunk
1407 .code
1408 .get(frame.ip..frame.ip + 2)
1409 .ok_or_else(|| {
1410 OpStep::Error(diagnostic(
1411 "truncated_instruction",
1412 "u16 operand is truncated",
1413 ))
1414 })?;
1415 frame.ip += 2;
1416 Ok(u16::from_be_bytes([bytes[0], bytes[1]]) as usize)
1417}
1418fn read_constant_string(frame: &mut Frame) -> Result<String, OpStep> {
1419 let index = read_u16(frame)?;
1420 match frame.chunk.constants.get(index) {
1421 Some(Constant::String(value)) => Ok(value.clone()),
1422 _ => Err(invalid_index("string constant", index)),
1423 }
1424}
1425fn pop_args(frame: &mut Frame, count: usize) -> Result<Vec<RuntimeValue>, OpStep> {
1426 if frame.stack.len() < count {
1427 return Err(OpStep::Error(diagnostic(
1428 "stack_underflow",
1429 "call argument stack is truncated",
1430 )));
1431 }
1432 Ok(frame.stack.split_off(frame.stack.len() - count))
1433}
1434fn invalid_index(kind: &str, index: usize) -> OpStep {
1435 OpStep::Error(diagnostic(
1436 "invalid_index",
1437 format!("{kind} index {index} is out of bounds"),
1438 ))
1439}
1440
1441fn call_value(
1442 machine: &mut Machine<'_>,
1443 env: &Rc<Env>,
1444 callee: RuntimeValue,
1445 args: Vec<RuntimeValue>,
1446 tail: bool,
1447) -> OpStep {
1448 match callee {
1449 RuntimeValue::Closure(closure) => OpStep::Call(closure, args, tail),
1450 RuntimeValue::Builtin(name) => machine.call_builtin(&name, args),
1451 RuntimeValue::String(name) => call_named(machine, env, &name, args, tail),
1455 RuntimeValue::Harness(capability) => {
1456 machine.call_method(RuntimeValue::Harness(capability), "call", args)
1457 }
1458 other => OpStep::Error(diagnostic(
1459 "not_callable",
1460 format!("{} is not callable", runtime_value_kind(&other)),
1461 )),
1462 }
1463}
1464
1465fn call_named(
1466 machine: &mut Machine<'_>,
1467 env: &Rc<Env>,
1468 name: &str,
1469 args: Vec<RuntimeValue>,
1470 tail: bool,
1471) -> OpStep {
1472 match env.get(name) {
1473 Some(callee) => call_value(machine, env, callee, args, tail),
1474 None => machine.call_builtin(name, args),
1475 }
1476}