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, RuntimeException, 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(error) => {
589 if !handle_throw(frames, error.clone()) {
590 let message = match self.render_value(&error.value) {
591 Ok(message) => message,
592 Err(diagnostic) => return Step::Error(diagnostic),
593 };
594 return Step::Error(diagnostic(
595 if error.declared {
596 "harn_declared_throw"
597 } else {
598 "harn_throw"
599 },
600 message,
601 ));
602 }
603 }
604 OpStep::RuntimeError(error) => {
605 if let Err(error) = self.catch_builtin_error(frames, error) {
606 return Step::Error(error);
607 }
608 }
609 OpStep::Error(error) => return Step::Error(error),
610 }
611 if frames
612 .last()
613 .is_some_and(|frame| frame.stack.len() > MAX_OPERAND_STACK)
614 {
615 return Step::Error(diagnostic(
616 "operand_stack_limit",
617 "portable execution exceeded its operand stack limit",
618 ));
619 }
620 }
621 }
622
623 #[allow(clippy::too_many_lines)]
624 fn execute_op(
625 &mut self,
626 op: Op,
627 offset: usize,
628 frames: &mut [Frame],
629 ) -> Result<OpStep, OpStep> {
630 let frame = frames.last_mut().expect("active frame");
631 macro_rules! pop {
632 () => {
633 match frame.stack.pop() {
634 Some(value) => value,
635 None => {
636 return Err(OpStep::Error(diagnostic(
637 "stack_underflow",
638 format!("{} at {offset}", op.name()),
639 )))
640 }
641 }
642 };
643 }
644 match op {
645 Op::Constant => {
646 let index = read_u16(frame)?;
647 let Some(value) = frame.chunk.constants.get(index).cloned() else {
648 return Err(invalid_index("constant", index));
649 };
650 frame.stack.push(RuntimeValue::from(value));
651 }
652 Op::Nil => frame.stack.push(RuntimeValue::Nil),
653 Op::True => frame.stack.push(RuntimeValue::Bool(true)),
654 Op::False => frame.stack.push(RuntimeValue::Bool(false)),
655 Op::RootHarness => frame.stack.push(RuntimeValue::Harness("root".to_string())),
656 Op::GetVar => {
657 let name = read_constant_string(frame)?;
658 frame.stack.push(
659 frame
660 .env
661 .get(&name)
662 .unwrap_or_else(|| RuntimeValue::Builtin(name)),
663 );
664 }
665 Op::DefLet | Op::DefVar | Op::DefCell => {
666 let name = read_constant_string(frame)?;
667 let value = pop!();
668 frame.env.define(name, value);
669 }
670 Op::SetVar => {
671 let name = read_constant_string(frame)?;
672 let value = pop!();
673 frame.env.set(&name, value);
674 }
675 Op::PushScope => {
676 frame.env = self.child_env(frame.env.clone()).map_err(OpStep::Error)?;
677 }
678 Op::PopScope => {
679 if let Some(parent) = &frame.env.parent {
680 frame.env = parent.clone();
681 }
682 }
683 Op::GetLocalSlot => {
684 let slot = read_u16(frame)?;
685 let Some(value) = frame.locals.get(slot).and_then(Clone::clone) else {
686 return Err(invalid_index("local", slot));
687 };
688 frame.stack.push(value);
689 }
690 Op::DefLocalSlot | Op::SetLocalSlot => {
691 let slot = read_u16(frame)?;
692 let value = pop!();
693 if slot >= frame.locals.len() {
694 return Err(invalid_index("local", slot));
695 }
696 frame.locals[slot] = Some(value.clone());
697 if let Some(local) = frame.chunk.local_slots.get(slot) {
698 if op == Op::DefLocalSlot {
699 frame.env.define(local.name.clone(), value);
700 } else {
701 frame.env.set(&local.name, value);
702 }
703 }
704 }
705 Op::ConcatAssignLocal => {
706 let slot = read_u16(frame)?;
707 let rhs = pop!();
708 let Some(local) = frame.chunk.local_slots.get(slot) else {
709 return Err(invalid_index("local", slot));
710 };
711 if !local.mutable {
712 return Err(OpStep::Error(diagnostic(
713 "immutable_assignment",
714 format!("cannot assign to immutable binding `{}`", local.name),
715 )));
716 }
717 let Some(lhs) = frame.locals.get(slot).and_then(Clone::clone) else {
718 return Err(invalid_index("local", slot));
719 };
720 let value = add(lhs, rhs).map_err(OpStep::Error)?;
721 self.charge_value_work(&value).map_err(OpStep::Error)?;
722 frame.locals[slot] = Some(value.clone());
723 frame.env.set(&local.name, value);
724 }
725 Op::GetArgc => frame.stack.push(RuntimeValue::Int(frame.argc as i64)),
726 Op::Pop => {
727 pop!();
728 }
729 Op::Dup => {
730 let value = pop!();
731 frame.stack.push(value.clone());
732 frame.stack.push(value);
733 }
734 Op::Swap => {
735 let right = pop!();
736 let left = pop!();
737 frame.stack.push(right);
738 frame.stack.push(left);
739 }
740 Op::Add | Op::AddInt | Op::AddFloat => {
741 let value = binary(frame, add)?;
742 self.charge_value_work(&value).map_err(OpStep::Error)?;
743 frame.stack.push(value);
744 }
745 Op::Sub | Op::SubInt | Op::SubFloat => {
746 let value = binary(frame, sub)?;
747 self.charge_value_work(&value).map_err(OpStep::Error)?;
748 frame.stack.push(value);
749 }
750 Op::Mul | Op::MulInt | Op::MulFloat => {
751 let value = binary(frame, mul)?;
752 self.charge_value_work(&value).map_err(OpStep::Error)?;
753 frame.stack.push(value);
754 }
755 Op::Div | Op::DivInt | Op::DivFloat => {
756 let value = binary(frame, div)?;
757 self.charge_value_work(&value).map_err(OpStep::Error)?;
758 frame.stack.push(value);
759 }
760 Op::Mod | Op::ModInt | Op::ModFloat => {
761 let value = binary(frame, modulo)?;
762 self.charge_value_work(&value).map_err(OpStep::Error)?;
763 frame.stack.push(value);
764 }
765 Op::Pow => {
766 let value = binary(frame, pow)?;
767 self.charge_value_work(&value).map_err(OpStep::Error)?;
768 frame.stack.push(value);
769 }
770 Op::Negate => {
771 let value = pop!();
772 frame.stack.push(negate(value).map_err(OpStep::Error)?);
773 }
774 Op::Not => {
775 let value = pop!();
776 frame.stack.push(RuntimeValue::Bool(!value.truthy()));
777 }
778 Op::Equal | Op::EqualInt | Op::EqualFloat | Op::EqualBool | Op::EqualString => {
779 compare_equality(self, frame, true)?;
780 }
781 Op::NotEqual
782 | Op::NotEqualInt
783 | Op::NotEqualFloat
784 | Op::NotEqualBool
785 | Op::NotEqualString => compare_equality(self, frame, false)?,
786 Op::Less | Op::LessInt | Op::LessFloat => compare(self, frame, |value| value < 0)?,
787 Op::Greater | Op::GreaterInt | Op::GreaterFloat => {
788 compare(self, frame, |value| value > 0)?;
789 }
790 Op::LessEqual | Op::LessEqualInt | Op::LessEqualFloat => {
791 compare(self, frame, |value| value <= 0)?;
792 }
793 Op::GreaterEqual | Op::GreaterEqualInt | Op::GreaterEqualFloat => {
794 compare(self, frame, |value| value >= 0)?;
795 }
796 Op::Jump => frame.ip = read_u16(frame)?,
797 Op::JumpIfFalse => {
798 let target = read_u16(frame)?;
799 if !frame.stack.last().is_some_and(RuntimeValue::truthy) {
800 frame.ip = target;
801 }
802 }
803 Op::JumpIfTrue => {
804 let target = read_u16(frame)?;
805 if frame.stack.last().is_some_and(RuntimeValue::truthy) {
806 frame.ip = target;
807 }
808 }
809 Op::Closure => {
810 let index = read_u16(frame)?;
811 let Some(function) = frame.chunk.functions.get(index).cloned() else {
812 return Err(invalid_index("function", index));
813 };
814 self.retain_environment(&frame.env);
815 frame.stack.push(RuntimeValue::Closure(Closure {
816 function,
817 env: Rc::downgrade(&frame.env),
818 }));
819 }
820 Op::Call | Op::TailCall => {
821 let argc = read_u8(frame)?;
822 let args = pop_args(frame, argc)?;
823 let callee = pop!();
824 return Ok(call_value(
825 self,
826 &frame.env,
827 callee,
828 args,
829 op == Op::TailCall,
830 ));
831 }
832 Op::Return => {
833 return Ok(OpStep::Return(
834 frame.stack.pop().unwrap_or(RuntimeValue::Nil),
835 ))
836 }
837 Op::BuildList => {
838 let count = read_u16(frame)?;
839 let values = pop_args(frame, count)?;
840 let value = RuntimeValue::List(Rc::new(values));
841 self.charge_value_work(&value).map_err(OpStep::Error)?;
842 frame.stack.push(value);
843 }
844 Op::BuildDict => {
845 let count = read_u16(frame)?;
846 let values = pop_args(frame, count * 2)?;
847 let mut map = BTreeMap::new();
848 for pair in values.chunks_exact(2) {
849 let key = self.render_value(&pair[0]).map_err(OpStep::Error)?;
850 map.insert(key, pair[1].clone());
851 }
852 let value = RuntimeValue::Record(Rc::new(map));
853 self.charge_value_work(&value).map_err(OpStep::Error)?;
854 frame.stack.push(value);
855 }
856 Op::GetProperty | Op::GetPropertyOpt => {
857 let name = read_constant_string(frame)?;
858 let value = pop!();
859 match get_property(&value, &name) {
860 Some(value) => frame.stack.push(value),
861 None if op == Op::GetPropertyOpt => frame.stack.push(RuntimeValue::Nil),
862 None => {
863 return Err(OpStep::Error(diagnostic(
864 "missing_property",
865 format!("value has no property `{name}`"),
866 )))
867 }
868 }
869 }
870 Op::Subscript | Op::SubscriptOpt => {
871 let index = pop!();
872 let value = pop!();
873 match self.subscript(&value, &index).map_err(OpStep::Error)? {
874 Some(value) => frame.stack.push(value),
875 None if op == Op::SubscriptOpt => frame.stack.push(RuntimeValue::Nil),
876 None => {
877 return Err(OpStep::Error(diagnostic(
878 "subscript",
879 "subscript does not exist",
880 )))
881 }
882 }
883 }
884 Op::SetProperty => {
885 let property = read_constant_string(frame)?;
886 let binding = read_constant_string(frame)?;
887 let value = pop!();
888 let Some(target) = frame.env.get(&binding) else {
889 return Err(OpStep::Error(diagnostic(
890 "undefined_variable",
891 format!("cannot assign property on undefined binding `{binding}`"),
892 )));
893 };
894 let updated =
895 set_property_value(target, &property, value).map_err(OpStep::Error)?;
896 self.charge_value_work(&updated).map_err(OpStep::Error)?;
897 frame.env.set(&binding, updated);
898 }
899 Op::SetSubscript => {
900 let binding = read_constant_string(frame)?;
901 let index = pop!();
902 let value = pop!();
903 let Some(target) = frame.env.get(&binding) else {
904 return Err(OpStep::Error(diagnostic(
905 "undefined_variable",
906 format!("cannot assign subscript on undefined binding `{binding}`"),
907 )));
908 };
909 let updated = set_subscript_value(target, index, value).map_err(OpStep::Error)?;
910 self.charge_value_work(&updated).map_err(OpStep::Error)?;
911 frame.env.set(&binding, updated);
912 }
913 Op::SetLocalSlotProperty => {
914 let property = read_constant_string(frame)?;
915 let slot = read_u16(frame)?;
916 let value = pop!();
917 let Some(local) = frame.chunk.local_slots.get(slot) else {
918 return Err(invalid_index("local", slot));
919 };
920 if !local.mutable {
921 return Err(OpStep::Error(diagnostic(
922 "immutable_assignment",
923 format!("cannot assign to immutable binding `{}`", local.name),
924 )));
925 }
926 let Some(target) = frame.locals.get(slot).and_then(Clone::clone) else {
927 return Err(invalid_index("local", slot));
928 };
929 let updated =
930 set_property_value(target, &property, value).map_err(OpStep::Error)?;
931 self.charge_value_work(&updated).map_err(OpStep::Error)?;
932 frame.locals[slot] = Some(updated.clone());
933 frame.env.set(&local.name, updated);
934 }
935 Op::SetLocalSlotSubscript => {
936 let slot = read_u16(frame)?;
937 let index = pop!();
938 let value = pop!();
939 let Some(local) = frame.chunk.local_slots.get(slot) else {
940 return Err(invalid_index("local", slot));
941 };
942 if !local.mutable {
943 return Err(OpStep::Error(diagnostic(
944 "immutable_assignment",
945 format!("cannot assign to immutable binding `{}`", local.name),
946 )));
947 }
948 let Some(target) = frame.locals.get(slot).and_then(Clone::clone) else {
949 return Err(invalid_index("local", slot));
950 };
951 let updated = set_subscript_value(target, index, value).map_err(OpStep::Error)?;
952 self.charge_value_work(&updated).map_err(OpStep::Error)?;
953 frame.locals[slot] = Some(updated.clone());
954 frame.env.set(&local.name, updated);
955 }
956 Op::Slice => {
957 let end = pop!();
958 let start = pop!();
959 let value = pop!();
960 let value = slice(value, start, end).map_err(OpStep::Error)?;
961 self.charge_value_work(&value).map_err(OpStep::Error)?;
962 frame.stack.push(value);
963 }
964 Op::MethodCall | Op::MethodCallOpt => {
965 let name = read_constant_string(frame)?;
966 let argc = read_u8(frame)?;
967 let args = pop_args(frame, argc)?;
968 let receiver = pop!();
969 if op == Op::MethodCallOpt && matches!(receiver, RuntimeValue::Nil) {
970 frame.stack.push(RuntimeValue::Nil);
971 } else {
972 return Ok(self.call_method(receiver, &name, args));
973 }
974 }
975 Op::Concat => {
976 let count = read_u16(frame)?;
977 let values = pop_args(frame, count)?;
978 let mut rendered = String::new();
979 for value in &values {
980 let part = self.render_value(value).map_err(OpStep::Error)?;
981 if rendered.len().saturating_add(part.len()) > MAX_VALUE_BYTES {
982 return Err(OpStep::Error(diagnostic(
983 "value_byte_limit",
984 "string interpolation exceeds the portable value byte limit",
985 )));
986 }
987 rendered.push_str(&part);
988 }
989 frame.stack.push(RuntimeValue::String(Arc::from(rendered)));
990 }
991 Op::Contains => {
992 let container = pop!();
993 let item = pop!();
994 let found = self.contains(&container, &item).map_err(OpStep::Error)?;
995 frame.stack.push(RuntimeValue::Bool(found));
996 }
997 Op::IterInit => {
998 if self.iterators.len() >= MAX_ITERATORS {
999 return Err(OpStep::Error(diagnostic(
1000 "iterator_limit",
1001 "portable execution exceeded its iterator limit",
1002 )));
1003 }
1004 let iterable = pop!();
1005 let iterator = match iterable {
1006 RuntimeValue::List(values) => IteratorState::List { values, index: 0 },
1007 RuntimeValue::Record(values) => IteratorState::Record {
1008 keys: values.keys().cloned().collect(),
1009 values,
1010 index: 0,
1011 },
1012 other => {
1013 return Err(OpStep::Error(diagnostic(
1014 "iterator_type",
1015 format!(
1016 "cannot iterate over {} in the portable kernel",
1017 runtime_value_kind(&other)
1018 ),
1019 )))
1020 }
1021 };
1022 self.iterators.push(iterator);
1023 }
1024 Op::IterNext => {
1025 let target = read_u16(frame)?;
1026 let Some(iterator) = self.iterators.last_mut() else {
1027 return Err(OpStep::Error(diagnostic(
1028 "iterator_state",
1029 "iterator step has no active iterator",
1030 )));
1031 };
1032 match iterator {
1033 IteratorState::List { values, index } => {
1034 if let Some(value) = values.get(*index).cloned() {
1035 *index += 1;
1036 frame.stack.push(value);
1037 } else {
1038 self.iterators.pop();
1039 frame.ip = target;
1040 }
1041 }
1042 IteratorState::Record {
1043 keys,
1044 values,
1045 index,
1046 } => {
1047 if let Some(key) = keys.get(*index) {
1048 let value = values.get(key).cloned().unwrap_or(RuntimeValue::Nil);
1049 *index += 1;
1050 frame
1051 .stack
1052 .push(RuntimeValue::Record(Rc::new(BTreeMap::from([
1053 (
1054 "key".to_string(),
1055 RuntimeValue::String(Arc::from(key.as_str())),
1056 ),
1057 ("value".to_string(), value),
1058 ]))));
1059 } else {
1060 self.iterators.pop();
1061 frame.ip = target;
1062 }
1063 }
1064 }
1065 }
1066 Op::PopIterator => {
1067 self.iterators.pop();
1068 }
1069 Op::TryCatchSetup | Op::TryCatchPreserve => {
1070 let target = read_u16(frame)?;
1071 let _type_name = read_u16(frame)?;
1072 frame.handlers.push(Handler {
1073 target,
1074 stack_depth: frame.stack.len(),
1075 env: frame.env.clone(),
1076 preserve: op == Op::TryCatchPreserve,
1077 });
1078 }
1079 Op::PopHandler => {
1080 frame.handlers.pop();
1081 }
1082 Op::Throw | Op::ThrowDeclared => {
1083 return Ok(OpStep::Throw(RuntimeException {
1084 value: pop!(),
1085 declared: op == Op::ThrowDeclared,
1086 }))
1087 }
1088 Op::Rethrow => {
1089 let RuntimeValue::Exception(error) = pop!() else {
1090 return Err(OpStep::Error(diagnostic(
1091 "invalid_rethrow",
1092 "rethrow requires a caught exception carrier",
1093 )));
1094 };
1095 return Ok(OpStep::Throw((*error).clone()));
1096 }
1097 Op::Import => {
1098 let path = read_constant_string(frame)?;
1099 let env = frame.env.clone();
1100 return Ok(self.import_root_module(&path, None, None, &env));
1101 }
1102 Op::SelectiveImport => {
1103 let path = read_constant_string(frame)?;
1104 let names = read_constant_string(frame)?;
1105 let selected = names
1106 .split(',')
1107 .filter(|name| !name.is_empty())
1108 .map(str::to_string)
1109 .collect::<Vec<_>>();
1110 let env = frame.env.clone();
1111 return Ok(self.import_root_module(&path, Some(&selected), None, &env));
1112 }
1113 Op::NamespaceImport => {
1114 let path = read_constant_string(frame)?;
1115 let alias = read_constant_string(frame)?;
1116 let env = frame.env.clone();
1117 return Ok(self.import_root_module(&path, None, Some((&alias, None)), &env));
1118 }
1119 Op::NamespaceImportMembers => {
1126 let path = read_constant_string(frame)?;
1127 let alias = read_constant_string(frame)?;
1128 let members = read_constant_string(frame)?;
1129 let demanded = members
1130 .split(',')
1131 .filter(|name| !name.is_empty())
1132 .map(str::to_string)
1133 .collect::<Vec<_>>();
1134 let env = frame.env.clone();
1135 return Ok(self.import_root_module(
1136 &path,
1137 None,
1138 Some((&alias, Some(&demanded))),
1139 &env,
1140 ));
1141 }
1142 Op::BuildEnum => {
1143 let enum_name = read_constant_string(frame)?;
1144 let variant = read_constant_string(frame)?;
1145 let field_count = read_u16(frame)?;
1146 let fields = pop_args(frame, field_count)?;
1147 let value = RuntimeValue::Enum(Rc::new(EnumValue {
1148 enum_name: Arc::from(enum_name),
1149 variant: Arc::from(variant),
1150 fields: Rc::new(fields),
1151 }));
1152 self.charge_value_work(&value).map_err(OpStep::Error)?;
1153 frame.stack.push(value);
1154 }
1155 Op::MatchEnum => {
1156 let enum_name = read_constant_string(frame)?;
1157 let variant = read_constant_string(frame)?;
1158 let value = pop!();
1159 let matches = matches!(
1160 &value,
1161 RuntimeValue::Enum(candidate)
1162 if candidate.is_variant(&enum_name, &variant)
1163 );
1164 frame.stack.push(value);
1165 frame.stack.push(RuntimeValue::Bool(matches));
1166 }
1167 Op::TryWrapOk => {
1168 let value = pop!();
1169 if matches!(&value, RuntimeValue::Enum(candidate) if candidate.enum_name.as_ref() == "Result")
1170 {
1171 frame.stack.push(value);
1172 } else {
1173 frame.stack.push(RuntimeValue::Enum(Rc::new(EnumValue {
1174 enum_name: Arc::from("Result"),
1175 variant: Arc::from("Ok"),
1176 fields: Rc::new(vec![value]),
1177 })));
1178 }
1179 }
1180 Op::TryUnwrap => {
1181 let value = pop!();
1182 let RuntimeValue::Enum(result) = &value else {
1183 return Err(OpStep::Error(diagnostic(
1184 "try_unwrap_type",
1185 format!(
1186 "? operator requires a Result value, got {}",
1187 runtime_value_kind(&value)
1188 ),
1189 )));
1190 };
1191 if result.enum_name.as_ref() != "Result" {
1192 return Err(OpStep::Error(diagnostic(
1193 "try_unwrap_type",
1194 format!(
1195 "? operator requires a Result value, got {}",
1196 runtime_value_kind(&value)
1197 ),
1198 )));
1199 }
1200 if result.variant.as_ref() == "Ok" {
1201 frame
1202 .stack
1203 .push(result.fields.first().cloned().unwrap_or(RuntimeValue::Nil));
1204 } else {
1205 return Ok(OpStep::Return(value));
1206 }
1207 }
1208 Op::AssertBindingType => {
1209 let index = read_u16(frame)?;
1210 let Some(slot) = frame.chunk.binding_types.get(index) else {
1211 return Err(invalid_index("binding type", index));
1212 };
1213 let Some(value) = frame.stack.last() else {
1214 return Err(OpStep::Error(diagnostic(
1215 "stack_underflow",
1216 "AssertBindingType requires the bound value on the stack",
1217 )));
1218 };
1219 type_guard::validate_binding(value, slot).map_err(OpStep::Error)?;
1220 }
1221 Op::CheckType => {
1222 return Err(OpStep::Error(diagnostic(
1223 "unsupported_portable_opcode",
1224 format!("{} is not implemented by the portable kernel", op.name()),
1225 )))
1226 }
1227 Op::CallBuiltin => {
1228 frame.ip += 8;
1229 let name = read_constant_string(frame)?;
1230 let argc = read_u8(frame)?;
1231 let args = pop_args(frame, argc)?;
1232 return Ok(call_named(self, &frame.env, &name, args, false));
1233 }
1234 Op::CallBuiltinSpread => {
1235 frame.ip += 8;
1236 let name = read_constant_string(frame)?;
1237 let spread = pop!();
1238 let RuntimeValue::List(args) = spread else {
1239 return Err(OpStep::Error(diagnostic(
1240 "spread_type",
1241 "spread call requires a list",
1242 )));
1243 };
1244 return Ok(call_named(
1245 self,
1246 &frame.env,
1247 &name,
1248 Rc::unwrap_or_clone(args),
1249 false,
1250 ));
1251 }
1252 unsupported @ (Op::Pipe
1253 | Op::Parallel
1254 | Op::ParallelMap
1255 | Op::ParallelMapStream
1256 | Op::ParallelSettle
1257 | Op::Spawn
1258 | Op::SyncMutexEnter
1259 | Op::SyncMutexEnterKeyed
1260 | Op::TaskScopeEnter
1261 | Op::TaskScopeExit
1262 | Op::DeadlineSetup
1263 | Op::DeadlineEnd
1264 | Op::CallSpread
1265 | Op::MethodCallSpread
1266 | Op::Yield) => {
1267 return Err(OpStep::Error(diagnostic(
1268 "unsupported_portable_opcode",
1269 format!(
1270 "{} is not implemented by the portable kernel",
1271 unsupported.name()
1272 ),
1273 )))
1274 }
1275 }
1276 Ok(OpStep::Continue)
1277 }
1278}
1279
1280struct Env {
1281 values: RefCell<BTreeMap<String, RuntimeValue>>,
1282 parent: Option<Rc<Env>>,
1283 depth: usize,
1284}
1285
1286struct ModuleInstance {
1287 env: Rc<Env>,
1288 exports: BTreeMap<String, crate::PortableExportKind>,
1289}
1290
1291enum IteratorState {
1292 List {
1293 values: Rc<Vec<RuntimeValue>>,
1294 index: usize,
1295 },
1296 Record {
1297 values: Rc<BTreeMap<String, RuntimeValue>>,
1298 keys: Vec<String>,
1299 index: usize,
1300 },
1301}
1302
1303enum ModuleStep {
1304 Ready(Rc<ModuleInstance>),
1305 Suspend(CapabilityRequest),
1306 Error(Diagnostic),
1307}
1308
1309type NamespaceProjection<'a> = (&'a str, Option<&'a [String]>);
1313
1314impl Env {
1315 fn root() -> Rc<Self> {
1316 Rc::new(Self {
1317 values: RefCell::new(BTreeMap::new()),
1318 parent: None,
1319 depth: 0,
1320 })
1321 }
1322 fn child(parent: Rc<Self>) -> Result<Rc<Self>, Diagnostic> {
1323 if parent.depth >= MAX_SCOPE_DEPTH {
1324 return Err(diagnostic(
1325 "scope_depth_limit",
1326 "portable execution exceeded its lexical scope depth limit",
1327 ));
1328 }
1329 let depth = parent.depth + 1;
1330 Ok(Rc::new(Self {
1331 values: RefCell::new(BTreeMap::new()),
1332 parent: Some(parent),
1333 depth,
1334 }))
1335 }
1336 fn define(&self, name: String, value: RuntimeValue) {
1337 self.values.borrow_mut().insert(name, value);
1338 }
1339 fn contains_local(&self, name: &str) -> bool {
1340 self.values.borrow().contains_key(name)
1341 }
1342 fn get(&self, name: &str) -> Option<RuntimeValue> {
1343 self.values
1344 .borrow()
1345 .get(name)
1346 .cloned()
1347 .or_else(|| self.parent.as_ref().and_then(|parent| parent.get(name)))
1348 }
1349 fn set(&self, name: &str, value: RuntimeValue) {
1350 if self.values.borrow().contains_key(name) {
1351 self.values.borrow_mut().insert(name.to_string(), value);
1352 } else if let Some(parent) = &self.parent {
1353 parent.set(name, value);
1354 } else {
1355 self.values.borrow_mut().insert(name.to_string(), value);
1356 }
1357 }
1358}
1359
1360struct Frame {
1361 chunk: Arc<Chunk>,
1362 ip: usize,
1363 stack: Vec<RuntimeValue>,
1364 locals: Vec<Option<RuntimeValue>>,
1365 env: Rc<Env>,
1366 handlers: Vec<Handler>,
1367 argc: usize,
1368}
1369impl Frame {
1370 fn new(chunk: Arc<Chunk>, env: Rc<Env>, arguments: Vec<RuntimeValue>) -> Self {
1371 let argc = arguments.len();
1372 let mut locals = vec![None; chunk.local_slots.len()];
1373 for (index, value) in arguments.into_iter().enumerate().take(locals.len()) {
1374 locals[index] = Some(value);
1375 }
1376 Self {
1377 chunk,
1378 ip: 0,
1379 stack: Vec::new(),
1380 locals,
1381 env,
1382 handlers: Vec::new(),
1383 argc,
1384 }
1385 }
1386
1387 fn for_function(
1388 function: &CompiledFunction,
1389 env: Rc<Env>,
1390 mut arguments: Vec<RuntimeValue>,
1391 ) -> Self {
1392 let supplied = arguments.len();
1393 if function.has_rest_param && !function.params.is_empty() {
1394 let rest_index = function.params.len() - 1;
1395 let rest = if arguments.len() > rest_index {
1396 arguments.split_off(rest_index)
1397 } else {
1398 Vec::new()
1399 };
1400 arguments.push(RuntimeValue::List(Rc::new(rest)));
1401 } else {
1402 arguments.truncate(function.params.len());
1403 }
1404 let mut frame = Self::new(function.chunk.clone(), env, arguments);
1405 for (parameter, value) in function.params.iter().zip(frame.locals.iter()) {
1406 if let Some(value) = value {
1407 frame.env.define(parameter.name.clone(), value.clone());
1408 }
1409 }
1410 frame.argc = supplied;
1411 frame
1412 }
1413}
1414struct Handler {
1415 target: usize,
1416 stack_depth: usize,
1417 env: Rc<Env>,
1418 preserve: bool,
1419}
1420enum Step {
1421 Value(RuntimeValue),
1422 Suspend(CapabilityRequest),
1423 Error(Diagnostic),
1424}
1425enum OpStep {
1426 Continue,
1427 Push(RuntimeValue),
1428 Call(Closure, Vec<RuntimeValue>, bool),
1429 Return(RuntimeValue),
1430 Suspend(CapabilityRequest),
1431 Throw(RuntimeException),
1432 RuntimeError(Diagnostic),
1433 Error(Diagnostic),
1434}
1435
1436fn read_constant_string(frame: &mut Frame) -> Result<String, OpStep> {
1437 let index = read_u16(frame)?;
1438 match frame.chunk.constants.get(index) {
1439 Some(Constant::String(value)) => Ok(value.clone()),
1440 _ => Err(invalid_index("string constant", index)),
1441 }
1442}
1443fn pop_args(frame: &mut Frame, count: usize) -> Result<Vec<RuntimeValue>, OpStep> {
1444 if frame.stack.len() < count {
1445 return Err(OpStep::Error(diagnostic(
1446 "stack_underflow",
1447 "call argument stack is truncated",
1448 )));
1449 }
1450 Ok(frame.stack.split_off(frame.stack.len() - count))
1451}
1452fn invalid_index(kind: &str, index: usize) -> OpStep {
1453 OpStep::Error(diagnostic(
1454 "invalid_index",
1455 format!("{kind} index {index} is out of bounds"),
1456 ))
1457}
1458
1459fn call_value(
1460 machine: &mut Machine<'_>,
1461 env: &Rc<Env>,
1462 callee: RuntimeValue,
1463 args: Vec<RuntimeValue>,
1464 tail: bool,
1465) -> OpStep {
1466 match callee {
1467 RuntimeValue::Closure(closure) => OpStep::Call(closure, args, tail),
1468 RuntimeValue::Builtin(name) => machine.call_builtin(&name, args),
1469 RuntimeValue::String(name) => call_named(machine, env, &name, args, tail),
1473 RuntimeValue::Harness(capability) => {
1474 machine.call_method(RuntimeValue::Harness(capability), "call", args)
1475 }
1476 other => OpStep::Error(diagnostic(
1477 "not_callable",
1478 format!("{} is not callable", runtime_value_kind(&other)),
1479 )),
1480 }
1481}
1482
1483fn call_named(
1484 machine: &mut Machine<'_>,
1485 env: &Rc<Env>,
1486 name: &str,
1487 args: Vec<RuntimeValue>,
1488 tail: bool,
1489) -> OpStep {
1490 match env.get(name) {
1491 Some(callee) => call_value(machine, env, callee, args, tail),
1492 None => machine.call_builtin(name, args),
1493 }
1494}