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
38pub 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, ¶meter.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 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}