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