Skip to main content

harn_kernel/
execution.rs

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
46/// Deterministically execute from the beginning with a recorded capability
47/// transcript. This is the native replay path and the oracle for snapshot
48/// resume: responses are consumed only when their request IDs match.
49pub 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                // The manifest records an alias without member demand, so a
278                // module-to-module namespace import is projected whole.
279                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            // The narrowed form the compiler emits when every use of the
1120            // namespace is a statically known member. The projection is the
1121            // demand set rather than the module's whole export surface;
1122            // escaping or dynamic uses compile to `NamespaceImport` instead,
1123            // so a missing member here is a compiler contract violation, not a
1124            // program error.
1125            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
1309/// A namespace import's alias and, when the compiler proved the whole set of
1310/// members a module actually demands, that demand set. `None` members means
1311/// the namespace escaped static analysis and must be projected whole.
1312type 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        // Optimized named tail calls carry the source name as a string. Match
1470        // the native VM's lexical-first late binding so recursion, mutual
1471        // recursion, and sibling calls all retain one compiler representation.
1472        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}