Skip to main content

aranya_policy_vm/
machine.rs

1extern crate alloc;
2
3use alloc::{
4    boxed::Box,
5    collections::BTreeMap,
6    string::{String, ToString as _},
7    vec,
8    vec::Vec,
9};
10use core::fmt::{self, Display};
11
12use aranya_crypto::policy::CmdId;
13use aranya_policy_ast::{self as ast, Identifier, ident};
14use aranya_policy_module::{
15    ActionDef, CodeMap, CommandDef, ExitReason, Fact, FactKey, FactValue, HashableValue,
16    Instruction, KVPair, Label, LabelType, Module, ModuleData, ModuleV0, Struct, Target, TryAsMut,
17    UnsupportedVersion, Value, ValueConversionError, named::NamedMap,
18};
19use buggy::{Bug, BugExt as _};
20use heapless::Vec as HVec;
21
22#[cfg(feature = "bench")]
23use crate::bench::{Stopwatch, bench_aggregate};
24use crate::{
25    ActionContext, CommandContext, OpenContext, PolicyContext, SealContext,
26    error::{MachineError, MachineErrorType},
27    io::MachineIO,
28    scope::ScopeManager,
29    stack::Stack,
30};
31
32const STACK_SIZE: usize = 100;
33
34/// Compares a fact's keys and values to its schema.
35/// Bind values are omitted from keys/values, so we only compare the given keys/values. This allows us to do partial matches.
36fn validate_fact_schema(fact: &Fact, schema: &ast::FactDefinition) -> bool {
37    if fact.name != schema.identifier.name {
38        return false;
39    }
40
41    for key in &fact.keys {
42        let Some(key_value) = schema
43            .key
44            .iter()
45            .find(|k| k.identifier.name == key.identifier)
46        else {
47            return false;
48        };
49
50        if !key.value.vtype().matches(&key_value.field_type.kind) {
51            return false;
52        }
53    }
54
55    for value in &fact.values {
56        // Ensure named value exists in schema
57        let Some(schema_value) = schema
58            .value
59            .iter()
60            .find(|v| v.identifier.name == value.identifier)
61        else {
62            return false;
63        };
64
65        // Ensure fact value type matches schema
66        let Some(value_type) = value.value.vtype() else {
67            return false;
68        };
69        if !value_type.matches(&schema_value.field_type.kind) {
70            return false;
71        }
72    }
73    true
74}
75
76/// Compares a fact to the given keys and values.
77///
78/// Bind keys/values are not included in the fact literal (see lower_fact_literal), so we only
79/// compare key/value pairs with exact values.
80///
81/// Returns true if all given keys and values match the fact.
82fn fact_match(query: &Fact, keys: &[FactKey], values: &[FactValue]) -> bool {
83    if !keys.starts_with(&query.keys) {
84        return false;
85    }
86
87    for qv in &query.values {
88        if let Some(v) = values.iter().find(|v| v.identifier == qv.identifier) {
89            // value found, but types don't match
90            if v.value != qv.value {
91                return false;
92            }
93        } else {
94            // invalid value name
95            return false;
96        }
97    }
98
99    true
100}
101
102/// Status of machine execution after stepping through each instruction.
103///
104/// These are expected states entered after executing instructions, as opposed to MachineErrors,
105/// which are produced by invalid instructions or data.
106#[must_use]
107#[derive(Debug, PartialEq, Eq)]
108pub enum MachineStatus {
109    /// Execution will proceed as normal to the next instruction
110    Executing,
111    /// Execution has ended
112    Exited(ExitReason),
113}
114
115impl Display for MachineStatus {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            Self::Executing => write!(f, "Executing"),
119            Self::Exited(reason) => write!(f, "Exited: {}", reason),
120        }
121    }
122}
123
124/// The core policy VM type.
125///
126/// This contains the static data for the VM - instructions, entry points, schemas, globally scoped
127/// static values, and optionally a mapping between instructions and source code locations. For the
128/// VM's runtime data, see [`create_run_state()`](Self::create_run_state) and [`RunState`].
129#[derive(Clone, Debug, Eq, PartialEq)]
130pub struct Machine {
131    /// The program memory
132    pub progmem: Vec<Instruction>,
133    /// Mapping of Label names to addresses
134    pub labels: BTreeMap<Label, usize>,
135    /// Action definitions
136    pub action_defs: NamedMap<ActionDef>,
137    /// Command definitions
138    pub command_defs: NamedMap<CommandDef>,
139    /// Fact schemas
140    pub fact_defs: BTreeMap<Identifier, ast::FactDefinition>,
141    /// Struct schemas
142    pub struct_defs: BTreeMap<Identifier, Vec<ast::FieldDefinition>>,
143    /// Enum definitions
144    pub enum_defs: BTreeMap<Identifier, BTreeMap<Identifier, i64>>,
145    /// Mapping between program instructions and original code
146    pub codemap: Option<CodeMap>,
147    /// Globally scoped variables
148    pub globals: BTreeMap<Identifier, Value>,
149}
150
151impl Machine {
152    /// Creates a `Machine` from a list of instructions.
153    pub fn new<I>(instructions: I) -> Self
154    where
155        I: IntoIterator<Item = Instruction>,
156    {
157        Self {
158            progmem: Vec::from_iter(instructions),
159            labels: BTreeMap::new(),
160            action_defs: NamedMap::new(),
161            command_defs: NamedMap::new(),
162            fact_defs: BTreeMap::new(),
163            struct_defs: BTreeMap::new(),
164            enum_defs: BTreeMap::new(),
165            codemap: None,
166            globals: BTreeMap::new(),
167        }
168    }
169
170    /// Creates an empty `Machine` with a given codemap. Used by the compiler.
171    pub fn from_codemap(codemap: CodeMap) -> Self {
172        Self {
173            progmem: vec![],
174            labels: BTreeMap::new(),
175            action_defs: NamedMap::new(),
176            command_defs: NamedMap::new(),
177            fact_defs: BTreeMap::new(),
178            struct_defs: BTreeMap::new(),
179            enum_defs: BTreeMap::new(),
180            codemap: Some(codemap),
181            globals: BTreeMap::new(),
182        }
183    }
184
185    /// Creates a `Machine` from a `Module`.
186    pub fn from_module(m: Module) -> Result<Self, UnsupportedVersion> {
187        match m.data {
188            ModuleData::V0(m) => Ok(Self {
189                progmem: m.progmem.into(),
190                labels: m.labels,
191                action_defs: m.action_defs,
192                command_defs: m.command_defs,
193                fact_defs: m.fact_defs,
194                struct_defs: m.struct_defs,
195                enum_defs: m.enum_defs,
196                codemap: m.codemap,
197                globals: m.globals,
198            }),
199        }
200    }
201
202    /// Converts the `Machine` into a `Module`.
203    pub fn into_module(self) -> Module {
204        Module {
205            data: ModuleData::V0(ModuleV0 {
206                progmem: self.progmem.into_boxed_slice(),
207                labels: self.labels,
208                action_defs: self.action_defs,
209                command_defs: self.command_defs,
210                fact_defs: self.fact_defs,
211                struct_defs: self.struct_defs,
212                enum_defs: self.enum_defs,
213                codemap: self.codemap,
214                globals: self.globals,
215            }),
216        }
217    }
218
219    /// Parses an enum reference (e.g. `Color::Red`) into a [`Value::Enum`].
220    pub fn parse_enum(&self, value: &str) -> Result<Value, MachineError> {
221        let Some((name, variant)) = value.split_once("::") else {
222            return Err(MachineError::new(MachineErrorType::invalid_type(
223                "<Enum>::<Variant>",
224                value,
225                "invalid enum reference",
226            )));
227        };
228
229        let (name, variants) = self
230            .enum_defs
231            .get_key_value(name)
232            .ok_or_else(|| MachineErrorType::NotDefined(alloc::format!("enum {name}")))?;
233        let int_value = variants.get(variant).ok_or_else(|| {
234            MachineErrorType::NotDefined(alloc::format!("no value `{variant}` in enum `{name}`"))
235        })?;
236
237        Ok(Value::Enum(name.clone(), *int_value))
238    }
239
240    /// Create a RunState associated with this Machine.
241    pub fn create_run_state<'a, M>(&'a self, io: &'a mut M, ctx: CommandContext) -> RunState<'a, M>
242    where
243        M: MachineIO<MachineStack>,
244    {
245        RunState::new(self, io, ctx)
246    }
247
248    /// Call an action
249    pub fn call_action<Args, M>(
250        &mut self,
251        name: Identifier,
252        args: Args,
253        io: &mut M,
254        ctx: CommandContext,
255    ) -> Result<ExitReason, MachineError>
256    where
257        Args: IntoIterator,
258        Args::Item: Into<Value>,
259        M: MachineIO<MachineStack>,
260    {
261        let mut rs = self.create_run_state(io, ctx);
262        rs.call_action(name, args)
263    }
264
265    /// Call a command
266    pub fn call_command_policy<M>(
267        &mut self,
268        this_data: Struct,
269        envelope: Struct,
270        io: &mut M,
271        ctx: CommandContext,
272    ) -> Result<ExitReason, MachineError>
273    where
274        M: MachineIO<MachineStack>,
275    {
276        let mut rs = self.create_run_state(io, ctx);
277        rs.call_command_policy(this_data, envelope)
278    }
279}
280
281impl Display for Machine {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        writeln!(f, "Program memory:")?;
284        for (addr, instr) in self.progmem.iter().enumerate() {
285            writeln!(f, "  {:4}  {}", addr, instr)?;
286        }
287        writeln!(f, "Labels:")?;
288        for (k, v) in &self.labels {
289            writeln!(f, "  {}: {:?}", k, v)?;
290        }
291        writeln!(f, "Fact definitions:")?;
292        for (k, v) in &self.fact_defs {
293            writeln!(f, "  {}: {:?}", k, v)?;
294        }
295        writeln!(f, "Struct definitions:")?;
296        for (k, v) in &self.struct_defs {
297            writeln!(f, "  {}: {:?}", k, v)?;
298        }
299        Ok(())
300    }
301}
302
303/// The "run state" of the machine.
304///
305/// This includes variables, the stack, the call stack, the program counter, I/O, and the current
306/// execution context. Most commonly created from [`Machine::create_run_state()`]. It's separated
307/// from the rest of the VM so that it can be managed independently and potentially in multiple
308/// simultaneous instances.
309pub struct RunState<'a, M: MachineIO<MachineStack>> {
310    /// Reference to the underlying static machine data
311    machine: &'a Machine,
312    /// Named value definitions ("variables")
313    scope: ScopeManager<'a>,
314    /// The stack
315    pub stack: MachineStack,
316    /// The call state stack - stores return addresses and stack depths
317    call_state: Vec<usize>,
318    /// The program counter
319    pc: usize,
320    /// I/O callbacks
321    pub io: &'a mut M,
322    /// Execution Context (actually used for more than Commands)
323    ctx: CommandContext,
324    // Cursors for `QueryStart` results
325    query_iter_stack: Vec<M::QueryIterator>,
326    #[cfg(feature = "bench")]
327    stopwatch: Stopwatch,
328}
329
330impl<'a, M> RunState<'a, M>
331where
332    M: MachineIO<MachineStack>,
333{
334    /// Create a new, empty MachineState
335    pub fn new(machine: &'a Machine, io: &'a mut M, ctx: CommandContext) -> Self {
336        RunState {
337            machine,
338            scope: ScopeManager::new(&machine.globals),
339            stack: MachineStack(HVec::new()),
340            call_state: Vec::new(),
341            pc: 0,
342            io,
343            ctx,
344            query_iter_stack: vec![],
345            #[cfg(feature = "bench")]
346            stopwatch: Stopwatch::new(),
347        }
348    }
349
350    /// Returns the current context
351    pub fn get_context(&self) -> &CommandContext {
352        &self.ctx
353    }
354
355    /// Set the internal context object to a new reference. The old reference is not
356    /// preserved. This is a hack to allow a policy context to mutate into a recall context
357    /// when recall happens.
358    pub fn set_context(&mut self, ctx: CommandContext) {
359        self.ctx = ctx;
360    }
361
362    /// Update the context with a new head ID, e.g. after publishing a command.
363    pub fn update_context_with_new_head(&mut self, new_head_id: CmdId) -> Result<(), Bug> {
364        self.ctx = self.ctx.with_new_head(new_head_id)?;
365        Ok(())
366    }
367
368    /// Returns a string describing the source code at the current PC,
369    /// if available.
370    pub fn source_location(&self) -> Option<String> {
371        let source_span = self
372            .machine
373            .codemap
374            .as_ref()?
375            .span_from_instruction(self.pc)
376            .ok();
377        if let Some(span) = source_span {
378            let (row, col) = span.start_linecol();
379            Some(alloc::format!(
380                "at row {} col {}:\n\t{}",
381                row,
382                col,
383                span.as_str()
384            ))
385        } else {
386            None
387        }
388    }
389
390    /// Internal function to produce a MachineError with location
391    /// information.
392    fn err(&self, err_type: MachineErrorType) -> MachineError {
393        MachineError::from_position(err_type, self.pc, self.machine.codemap.as_ref())
394    }
395
396    /// Reset the machine state - undefine all named values, empty the
397    /// stack, and set the program counter to zero.
398    pub fn reset(&mut self) {
399        self.scope.clear();
400        self.stack.clear();
401        self.pc = 0;
402    }
403
404    /// Get the program counter.
405    pub fn pc(&self) -> usize {
406        self.pc
407    }
408
409    /// Internal wrapper around [Stack::push] that translates
410    /// [StackError] into [MachineError] with location information.
411    fn ipush<V>(&mut self, value: V) -> Result<(), MachineError>
412    where
413        V: Into<Value>,
414    {
415        self.stack.push(value).map_err(|e| self.err(e))
416    }
417
418    /// Internal wrapper around [Stack::pop] that translates
419    /// [StackError] into [MachineError] with location information.
420    fn ipop<V>(&mut self) -> Result<V, MachineError>
421    where
422        V: TryFrom<Value, Error = ValueConversionError>,
423    {
424        self.stack.pop().map_err(|e| self.err(e))
425    }
426
427    /// Internal wrapper around [Stack::pop_value] that translates
428    /// [StackError] into [MachineError] with location information.
429    fn ipop_value(&mut self) -> Result<Value, MachineError> {
430        self.stack.pop_value().map_err(|e| self.err(e))
431    }
432
433    /// Internal wrapper around [Stack::peek] that translates
434    /// [StackError] into [MachineError] with location information.
435    fn ipeek<V>(&mut self) -> Result<&mut V, MachineError>
436    where
437        V: ?Sized,
438        Value: TryAsMut<V, Error = ValueConversionError>,
439    {
440        // A little bit of chicanery - copy the PC now so we don't
441        // borrow from self when creating the error (as self.err()
442        // does). We can't do that inside the closure because peek()
443        // takes a mutable reference to self.
444        let pc = self.pc;
445        self.stack
446            .peek()
447            .map_err(|e| MachineError::from_position(e, pc, self.machine.codemap.as_ref()))
448    }
449
450    /// Validate a struct against defined schema.
451    // TODO(chip): This does not distinguish between Commands and
452    // Effects and it should.
453    fn validate_struct_schema(&mut self, s: &Struct) -> Result<(), MachineError> {
454        #[cfg(feature = "bench")]
455        self.stopwatch.start("validate_struct_schema");
456
457        let mk_err = || self.err(MachineErrorType::InvalidSchema(s.name.clone()));
458
459        match self.machine.struct_defs.get(&s.name) {
460            Some(fields) => {
461                // Check for struct fields that do not exist in the
462                // definition.
463                for f in &s.fields {
464                    if !fields.iter().any(|v| &v.identifier.name == f.0) {
465                        return Err(mk_err());
466                    }
467                }
468                // Ensure all defined fields exist and have the same
469                // types.
470                for f in fields {
471                    match s.fields.get(&f.identifier.name) {
472                        Some(v) => {
473                            if !v.fits_type(&f.field_type) {
474                                return Err(mk_err());
475                            }
476                        }
477                        None => {
478                            return Err(mk_err());
479                        }
480                    }
481                }
482
483                #[cfg(feature = "bench")]
484                self.stopwatch.stop();
485
486                Ok(())
487            }
488            None => Err(mk_err()),
489        }
490    }
491
492    /// Execute one machine instruction and return the status of the
493    /// machine or a MachineError.
494    pub fn step(&mut self) -> Result<MachineStatus, MachineError> {
495        if self.pc() >= self.machine.progmem.len() {
496            return Err(self.err(MachineErrorType::InvalidAddress(ident!("pc"))));
497        }
498        // Clone the instruction so we don't take an immutable
499        // reference to self while we manipulate the stack later.
500        let instruction = self.machine.progmem[self.pc()].clone();
501
502        match instruction {
503            Instruction::SaveSP => {
504                self.call_state.push(self.stack.len());
505            }
506            Instruction::RestoreSP => {
507                let saved_sp = self.call_state.pop().ok_or_else(|| {
508                    self.err(MachineErrorType::BadState("no saved stack pointer"))
509                })?;
510                match self
511                    .stack
512                    .len()
513                    .cmp(&saved_sp.checked_add(1).assume("stack size < isize::MAX")?)
514                {
515                    core::cmp::Ordering::Less => {
516                        return Err(self.err(MachineErrorType::BadState(
517                            "callable has consumed too many stack values",
518                        )));
519                    }
520                    core::cmp::Ordering::Equal => {}
521                    core::cmp::Ordering::Greater => {
522                        let v = self.stack.pop_value()?;
523                        while self.stack.len() > saved_sp {
524                            self.stack.pop_value()?;
525                        }
526                        self.stack.push_value(v)?;
527                    }
528                }
529            }
530            Instruction::Const(v) => {
531                self.ipush(v)?;
532            }
533            Instruction::Def(key) => {
534                let value = self.ipop_value()?;
535                self.scope.set(key, value)?;
536            }
537            Instruction::Get(key) => {
538                let value = self.scope.get(&key)?;
539                self.ipush(value)?;
540            }
541            Instruction::Dup => {
542                let v = self.stack.peek_value()?.clone();
543                self.ipush(v)?;
544            }
545            Instruction::Pop => {
546                let _ = self.stack.pop_value();
547            }
548            Instruction::Block => self.scope.enter_block().map_err(|e| self.err(e))?,
549            Instruction::End => self.scope.exit_block().map_err(|e| self.err(e))?,
550            Instruction::Jump(t) => match t {
551                Target::Unresolved(label) => {
552                    return Err(self.err(MachineErrorType::UnresolvedTarget(label)));
553                }
554                Target::Resolved(n) => {
555                    // We set the PC and return here to skip the
556                    // increment below. We could subtract 1 here to
557                    // compensate, but that doesn't work when we jump
558                    // to address 0.
559                    self.pc = n;
560                    return Ok(MachineStatus::Executing);
561                }
562            },
563            Instruction::Branch(t) => {
564                let conditional = self.ipop()?;
565                if conditional {
566                    match t {
567                        Target::Unresolved(label) => {
568                            return Err(self.err(MachineErrorType::UnresolvedTarget(label)));
569                        }
570                        Target::Resolved(n) => {
571                            self.pc = n;
572                            return Ok(MachineStatus::Executing);
573                        }
574                    }
575                }
576            }
577            Instruction::Next => todo!(),
578            Instruction::Last => todo!(),
579            Instruction::Call(t) => match t {
580                Target::Unresolved(label) => {
581                    return Err(self.err(MachineErrorType::UnresolvedTarget(label)));
582                }
583                Target::Resolved(n) => {
584                    self.scope.enter_function();
585                    // Store the current PC. The PC will be incremented after return,
586                    // so there's no need to increment here.
587                    self.call_state.push(self.pc);
588                    self.pc = n;
589                    return Ok(MachineStatus::Executing);
590                }
591            },
592            Instruction::Return => {
593                // When the outermost function completes, there is nothing left to do, so exit.
594                if self.call_state.is_empty() {
595                    return Ok(MachineStatus::Exited(ExitReason::Normal));
596                }
597                self.pc = self
598                    .call_state
599                    .pop()
600                    .ok_or_else(|| self.err(MachineErrorType::CallStack))?;
601                self.scope.exit_function().map_err(|e| self.err(e))?;
602            }
603            Instruction::ExtCall(module, proc) => {
604                self.io.call(module, proc, &mut self.stack, &self.ctx)?;
605            }
606            Instruction::Exit(reason) => return Ok(MachineStatus::Exited(reason)),
607            Instruction::Add | Instruction::Sub => {
608                let b: i64 = self.ipop()?;
609                let a: i64 = self.ipop()?;
610                let r = match instruction {
611                    Instruction::Add => a.checked_add(b),
612                    Instruction::Sub => a.checked_sub(b),
613                    _ => unreachable!(),
614                };
615                // Checked operations return Optional<Int>
616                self.ipush(r)?;
617            }
618            Instruction::SaturatingAdd | Instruction::SaturatingSub => {
619                let b: i64 = self.ipop()?;
620                let a: i64 = self.ipop()?;
621                let r = match instruction {
622                    Instruction::SaturatingAdd => a.saturating_add(b),
623                    Instruction::SaturatingSub => a.saturating_sub(b),
624                    _ => unreachable!(),
625                };
626                self.ipush(r)?;
627            }
628            Instruction::Not => {
629                let a: &mut bool = self.ipeek()?;
630                *a = !*a;
631            }
632            Instruction::Gt | Instruction::Lt | Instruction::Eq => {
633                let b = self.ipop_value()?;
634                let a = self.ipop_value()?;
635                let v = match instruction {
636                    Instruction::Gt => match (&a, &b) {
637                        (Value::Int(ia), Value::Int(ib)) => ia > ib,
638                        _ => {
639                            let a_type = a.type_name();
640                            let b_type = b.type_name();
641                            return Err(self.err(MachineErrorType::invalid_type(
642                                "Int, Int",
643                                alloc::format!("{a_type}, {b_type}"),
644                                "Greater-than comparison",
645                            )));
646                        }
647                    },
648                    Instruction::Lt => match (&a, &b) {
649                        (Value::Int(ia), Value::Int(ib)) => ia < ib,
650                        _ => {
651                            let a_type = a.type_name();
652                            let b_type = b.type_name();
653                            return Err(self.err(MachineErrorType::invalid_type(
654                                "Int, Int",
655                                alloc::format!("{a_type}, {b_type}"),
656                                "Less-than comparison",
657                            )));
658                        }
659                    },
660                    // This leans heavily on PartialEq to do the work.
661                    // Equality depends on values having the same type and
662                    // interior value.
663                    Instruction::Eq => a == b,
664                    _ => unreachable!(),
665                };
666                self.ipush(v)?;
667            }
668            Instruction::FactNew(name) => {
669                let fact = Fact::new(name);
670                self.ipush(fact)?;
671            }
672            Instruction::FactKeySet(varname) => {
673                let v: HashableValue = self.ipop()?;
674                let f: &mut Fact = self.ipeek()?;
675                f.set_key(varname, v);
676            }
677            Instruction::FactValueSet(varname) => {
678                let value = self.ipop_value()?;
679                let f: &mut Fact = self.ipeek()?;
680                f.set_value(varname, value);
681            }
682            Instruction::StructNew(name) => {
683                let fields = BTreeMap::new();
684                self.ipush(Struct { name, fields })?;
685            }
686            Instruction::StructSet(field_name) => {
687                let value = self.ipop_value()?;
688                let mut s: Struct = self.ipop()?;
689                // Validate that the field is part of this structure
690                // schema.
691                let struct_def_fields = self
692                    .machine
693                    .struct_defs
694                    .get(&s.name)
695                    .ok_or_else(|| self.err(MachineErrorType::InvalidSchema(s.name.clone())))?;
696                if !struct_def_fields
697                    .iter()
698                    .any(|f| f.identifier.name == field_name)
699                {
700                    return Err(self.err(MachineErrorType::InvalidStructMember(field_name)));
701                }
702                s.fields.insert(field_name, value);
703                self.ipush(s)?;
704            }
705            Instruction::StructGet(varname) => {
706                let mut s: Struct = self.ipop()?;
707                let v = s
708                    .fields
709                    .remove(&varname)
710                    .ok_or_else(|| self.err(MachineErrorType::InvalidStructMember(varname)))?;
711                self.ipush(v)?;
712            }
713            Instruction::MStructSet(n) => {
714                let n: usize = n.into();
715                let mut field_name_value_pairs = Vec::with_capacity(n);
716
717                for _ in 0..n {
718                    let field_val = self.ipop_value()?;
719                    let field_name = self.ipop::<Identifier>()?;
720
721                    field_name_value_pairs.push((field_name, field_val));
722                }
723
724                let mut target: Struct = self.ipop()?;
725                let struct_def_fields =
726                    self.machine.struct_defs.get(&target.name).ok_or_else(|| {
727                        self.err(MachineErrorType::InvalidSchema(target.name.clone()))
728                    })?;
729
730                for (field_name, field_val) in field_name_value_pairs {
731                    let Some(field_defn) = struct_def_fields
732                        .iter()
733                        .find(|f| f.identifier.name == field_name)
734                    else {
735                        return Err(self.err(MachineErrorType::InvalidStructMember(field_name)));
736                    };
737
738                    if !field_val.fits_type(&field_defn.field_type) {
739                        return Err(self.err(MachineErrorType::InvalidStructMember(field_name)));
740                    }
741
742                    target.fields.insert(field_name, field_val);
743                }
744                self.ipush(target)?;
745            }
746            Instruction::MStructGet(n) => {
747                let field_names = (0..n.into())
748                    .map(|_| self.ipop::<Identifier>())
749                    .collect::<Result<Vec<_>, _>>()?;
750                let mut s: Struct = self.ipop()?;
751
752                for field_name in field_names {
753                    let v = s.fields.remove(&field_name).ok_or_else(|| {
754                        self.err(MachineErrorType::InvalidStructMember(field_name.clone()))
755                    })?;
756
757                    self.ipush(field_name)?;
758                    self.ipush(v)?;
759                }
760            }
761            Instruction::Publish => {
762                let command_struct: Struct = self.ipop()?;
763                self.validate_struct_schema(&command_struct)?;
764                self.ipush(Value::Struct(command_struct))?;
765
766                self.pc = self.pc.checked_add(1).assume("self.pc + 1 must not wrap")?;
767                return Ok(MachineStatus::Exited(ExitReason::Yield));
768            }
769            Instruction::Create => {
770                let f: Fact = self.ipop()?;
771                self.io.fact_insert(f.name, f.keys, f.values)?;
772            }
773            Instruction::Delete => {
774                let f: Fact = self.ipop()?;
775                self.io.fact_delete(f.name, f.keys)?;
776            }
777            Instruction::Update => {
778                let fact_to: Fact = self.ipop()?;
779                let mut fact_from: Fact = self.ipop()?;
780                let mut replaced_fact = {
781                    let mut iter = self.io.fact_query(fact_from.name.clone(), fact_from.keys)?;
782                    iter.next().ok_or_else(|| {
783                        self.err(MachineErrorType::InvalidFact(fact_from.name.clone()))
784                    })??
785                };
786
787                if !fact_from.values.is_empty() {
788                    let replaced_fact_values = &mut replaced_fact.1;
789
790                    replaced_fact_values
791                        .sort_unstable_by(|v1, v2| v1.identifier.cmp(&v2.identifier));
792                    fact_from
793                        .values
794                        .sort_unstable_by(|v1, v2| v1.identifier.cmp(&v2.identifier));
795
796                    if replaced_fact_values.as_slice() != fact_from.values.as_slice() {
797                        return Err(self.err(MachineErrorType::InvalidFact(fact_from.name.clone())));
798                    }
799                }
800
801                self.io.fact_delete(fact_from.name, replaced_fact.0)?;
802                self.io
803                    .fact_insert(fact_to.name, fact_to.keys, fact_to.values)?;
804            }
805            Instruction::Emit => {
806                let s: Struct = self.ipop()?;
807                self.validate_struct_schema(&s)?;
808                let fields = s.fields.into_iter().map(|(k, v)| KVPair::new(k, v));
809                let (command, recall) = match &self.ctx {
810                    CommandContext::Policy(ctx) => (ctx.id, false),
811                    CommandContext::Recall(ctx) => (ctx.id, true),
812                    _ => {
813                        return Err(
814                            self.err(MachineErrorType::BadState("Emit: wrong command context"))
815                        );
816                    }
817                };
818                self.io.effect(s.name, fields, command, recall);
819            }
820            Instruction::Query => {
821                let qf: Fact = self.ipop()?;
822
823                // Before we spend time fetching facts from storage, make sure the given fact literal is valid.
824                self.validate_fact_literal(&qf)?;
825
826                let result = {
827                    let mut iter = self.io.fact_query(qf.name.clone(), qf.keys.clone())?;
828                    // Find the first match, or the first error
829                    iter.find_map(|r| match r {
830                        Ok(f) => {
831                            if fact_match(&qf, &f.0, &f.1) {
832                                Some(Ok(f))
833                            } else {
834                                None
835                            }
836                        }
837                        Err(e) => Some(Err(e)),
838                    })
839                };
840                let maybe_fact = result.transpose()?.map(|f| {
841                    let mut fields: Vec<KVPair> = vec![];
842                    fields.append(&mut f.0.into_iter().map(Into::into).collect());
843                    fields.append(&mut f.1.into_iter().map(Into::into).collect());
844                    Struct::new(qf.name, fields)
845                });
846                self.ipush(maybe_fact)?;
847            }
848            Instruction::FactCount(limit) => {
849                let fact: Fact = self.ipop()?;
850                self.validate_fact_literal(&fact)?;
851
852                let mut count = 0;
853                {
854                    let mut iter = self.io.fact_query(fact.name.clone(), fact.keys.clone())?;
855
856                    while count < limit {
857                        let Some(r) = iter.next() else { break };
858                        match r {
859                            Ok(f) => {
860                                if fact_match(&fact, &f.0, &f.1) {
861                                    count = count
862                                        .checked_add(1)
863                                        .assume("should be able to increment fact counter")?;
864                                }
865                            }
866                            Err(e) => return Err(self.err(MachineErrorType::IO(e))),
867                        }
868                    }
869                }
870
871                self.ipush(Value::Int(count))?;
872            }
873            Instruction::QueryStart => {
874                let fact: Fact = self.ipop()?;
875                self.validate_fact_literal(&fact)?;
876                let iter = self.io.fact_query(fact.name, fact.keys)?;
877                self.query_iter_stack.push(iter);
878            }
879            Instruction::QueryNext(ident) => {
880                // Fetch next fact from iterator
881                let iter = self.query_iter_stack.last_mut().ok_or_else(|| {
882                    MachineError::from_position(
883                        MachineErrorType::BadState("QueryNext: no results"),
884                        self.pc,
885                        self.machine.codemap.as_ref(),
886                    )
887                })?;
888                // Update `as` variable value and push an end-of-results bool.
889                match iter.next() {
890                    Some(result) => {
891                        let (k, v) = result?;
892                        let mut fields: Vec<KVPair> = vec![];
893                        fields.append(&mut k.into_iter().map(Into::into).collect());
894                        fields.append(&mut v.into_iter().map(Into::into).collect());
895                        let s = Struct::new(ident.clone(), fields);
896                        self.scope.set(ident, Value::Struct(s))?;
897                        self.ipush(Value::Bool(false))?;
898                    }
899                    None => {
900                        // When there are no more results, dispose of the iterator.
901                        self.query_iter_stack.pop();
902                        self.ipush(Value::Bool(true))?;
903                    }
904                }
905            }
906            Instruction::Serialize => {
907                let CommandContext::Seal(SealContext { name, .. }) = &self.ctx else {
908                    return Err(self.err(MachineErrorType::BadState(
909                        "Serialize: expected seal context",
910                    )));
911                };
912                let name = name.clone();
913
914                let command_struct: Struct = self.ipop()?;
915                if command_struct.name != name {
916                    return Err(MachineError::from_position(
917                        MachineErrorType::BadState(
918                            "Serialize: context name doesn't match command name",
919                        ),
920                        self.pc,
921                        self.machine.codemap.as_ref(),
922                    ));
923                }
924                let bytes = postcard::to_allocvec(&command_struct).map_err(|_| {
925                    self.err(MachineErrorType::Unknown(String::from(
926                        "could not serialize command Struct",
927                    )))
928                })?;
929                self.ipush(bytes)?;
930            }
931            Instruction::Deserialize => {
932                let CommandContext::Open(OpenContext { name, .. }) = &self.ctx else {
933                    return Err(MachineError::from_position(
934                        MachineErrorType::InvalidInstruction,
935                        self.pc,
936                        self.machine.codemap.as_ref(),
937                    ));
938                };
939                let name = name.clone();
940
941                let bytes: Vec<u8> = self.ipop()?;
942                let s: Struct = postcard::from_bytes(&bytes).map_err(|_| {
943                    MachineError::from_position(
944                        MachineErrorType::Unknown(String::from("could not deserialize Struct")),
945                        self.pc,
946                        self.machine.codemap.as_ref(),
947                    )
948                })?;
949                if name != s.name.as_str() {
950                    return Err(MachineError::from_position(
951                        MachineErrorType::InvalidInstruction,
952                        self.pc,
953                        self.machine.codemap.as_ref(),
954                    ));
955                }
956                self.ipush(s)?;
957            }
958            Instruction::Some => {
959                let value = self.ipop_value()?;
960                self.ipush(Value::Option(Some(Box::new(value))))?;
961            }
962            Instruction::Unwrap => {
963                let value = self.ipop_value()?;
964                if let Value::Option(opt) = value {
965                    if let Some(inner) = opt {
966                        self.ipush(*inner)?;
967                    } else {
968                        return Err(self.err(MachineErrorType::Unknown("unwrapped None".into())));
969                    }
970                } else {
971                    return Err(self.err(MachineErrorType::invalid_type(
972                        "Option[_]",
973                        value.type_name(),
974                        "Option[T] -> T",
975                    )));
976                }
977            }
978            Instruction::Meta(_m) => {}
979            Instruction::Cast(identifier) => {
980                let value = self.ipop_value()?;
981                match value {
982                    Value::Struct(s) => {
983                        // make sure identifier is a valid struct name
984                        let rhs_struct =
985                            self.machine.struct_defs.get(&identifier).ok_or_else(|| {
986                                self.err(MachineErrorType::NotDefined(alloc::format!(
987                                    "struct `{}`",
988                                    identifier
989                                )))
990                            })?;
991
992                        // Check that all required fields exist and have matching types
993                        for field in rhs_struct {
994                            let field_name = &field.identifier;
995                            let field_type = &field.field_type;
996
997                            // Check if the source struct has this field
998                            let value = s.fields.get(&field_name.name).ok_or_else(|| {
999                                self.err(MachineErrorType::Unknown(alloc::format!(
1000                                    "cannot cast to `struct {}`: missing field `{}`",
1001                                    identifier,
1002                                    field_name
1003                                )))
1004                            })?;
1005
1006                            // Check if the type matches
1007                            if !value.fits_type(field_type) {
1008                                return Err(self.err(MachineErrorType::Unknown(alloc::format!(
1009                                    "cannot cast to `struct {}`: field `{}` has wrong type (expected `{}`, found `{}`)",
1010                                    identifier, field_name, field_type, value.type_name()
1011                                ))));
1012                            }
1013                        }
1014
1015                        // replace value on stack with clone, under new name
1016                        let mut s = s;
1017                        s.name = identifier.clone();
1018                        self.ipush(Value::Struct(s))?;
1019                    }
1020                    _ => {
1021                        return Err(self.err(MachineErrorType::invalid_type(
1022                            "Struct",
1023                            value.type_name(),
1024                            "Cast LHS",
1025                        )));
1026                    }
1027                }
1028            }
1029        }
1030
1031        self.pc = self.pc.checked_add(1).assume("self.pc + 1 must not wrap")?;
1032
1033        Ok(MachineStatus::Executing)
1034    }
1035
1036    /// Execute machine instructions while each instruction returns
1037    /// MachineStatus::Executing. Returns the ExitReason it exited
1038    /// with, or an error.
1039    pub fn run(&mut self) -> Result<ExitReason, MachineError> {
1040        loop {
1041            #[cfg(feature = "bench")]
1042            if let Some(instruction) = self.machine.progmem.get(self.pc())
1043                && let Some(name) = instruction.to_string().split_whitespace().next()
1044            {
1045                self.stopwatch.start(name);
1046            }
1047
1048            let result = self
1049                .step()
1050                .map_err(|err| err.with_position(self.pc, self.machine.codemap.as_ref()))?;
1051
1052            #[cfg(feature = "bench")]
1053            if !self.stopwatch.measurement_stack.is_empty() {
1054                self.stopwatch.stop();
1055            }
1056
1057            if let MachineStatus::Exited(reason) = result {
1058                #[cfg(feature = "bench")]
1059                bench_aggregate(&mut self.stopwatch);
1060                return Ok(reason);
1061            }
1062        }
1063    }
1064
1065    /// Set the program counter to the given label.
1066    pub fn set_pc_by_label(&mut self, label: &Label) -> Result<(), MachineError> {
1067        let addr: &usize = self
1068            .machine
1069            .labels
1070            .get(label)
1071            .ok_or_else(|| self.err(MachineErrorType::InvalidAddress(label.name.clone())))?;
1072        self.pc = *addr;
1073        Ok(())
1074    }
1075
1076    /// Set up machine state for a command policy call
1077    pub fn setup_command(
1078        &mut self,
1079        label_type: LabelType,
1080        this_data: Struct,
1081    ) -> Result<(), MachineError> {
1082        let name = this_data.name.clone();
1083
1084        #[cfg(feature = "bench")]
1085        self.stopwatch
1086            .start(format!("setup_command: {}", name).as_str());
1087
1088        self.setup_function(&Label::new(name.clone(), label_type))?;
1089
1090        // Verify 'this' arg matches command's fields
1091        let command_def = self
1092            .machine
1093            .command_defs
1094            .get(&name)
1095            .ok_or_else(|| self.err(MachineErrorType::NotDefined(name.to_string())))?;
1096
1097        if this_data.fields.len() != command_def.fields.len() {
1098            return Err(self.err(MachineErrorType::Unknown(alloc::format!(
1099                "command `{}` expects {} field(s), but `this` contains {}",
1100                name,
1101                command_def.fields.len(),
1102                this_data.fields.len()
1103            ))));
1104        }
1105        for (name, value) in &this_data.fields {
1106            let expected_type = &command_def
1107                .fields
1108                .get(name)
1109                .ok_or_else(|| self.err(MachineErrorType::InvalidStructMember(name.clone())))?
1110                .ty;
1111
1112            if !value.fits_type(expected_type) {
1113                return Err(self.err(MachineErrorType::invalid_type(
1114                    expected_type.to_string(),
1115                    value.type_name(),
1116                    "invalid function argument",
1117                )));
1118            }
1119        }
1120
1121        self.ipush(this_data)?;
1122
1123        #[cfg(feature = "bench")]
1124        self.stopwatch.stop();
1125
1126        Ok(())
1127    }
1128
1129    /// Call a command policy loaded into the VM by name. Accepts a
1130    /// `Struct` containing the Command's data. Returns a Vec of effect
1131    /// structs or a MachineError.
1132    /// If the command check-exits, its recall block will be executed.
1133    pub fn call_command_policy(
1134        &mut self,
1135        this_data: Struct,
1136        envelope: Struct,
1137    ) -> Result<ExitReason, MachineError> {
1138        if !matches!(&self.ctx, CommandContext::Policy(PolicyContext{name: ctx_name,..}) if *ctx_name == this_data.name)
1139        {
1140            return Err(MachineErrorType::ContextMismatch.into());
1141        }
1142        self.setup_command(LabelType::CommandPolicy, this_data)?;
1143        self.ipush(envelope)?;
1144        self.run()
1145    }
1146
1147    /// Call a command policy loaded into the VM by name. Accepts a
1148    /// `Struct` containing the Command's data. Returns a Vec of effect
1149    /// structs or a MachineError.
1150    pub fn call_command_recall(
1151        &mut self,
1152        this_data: Struct,
1153        envelope: Struct,
1154    ) -> Result<ExitReason, MachineError> {
1155        if !matches!(&self.ctx, CommandContext::Recall(PolicyContext{name: ctx_name,..}) if *ctx_name == this_data.name)
1156        {
1157            return Err(MachineErrorType::ContextMismatch.into());
1158        }
1159        self.setup_command(LabelType::CommandRecall, this_data)?;
1160        self.ipush(envelope)?;
1161        self.run()
1162    }
1163
1164    fn setup_function(&mut self, label: &Label) -> Result<(), MachineError> {
1165        self.set_pc_by_label(label)?;
1166        self.call_state.clear();
1167        self.scope.clear();
1168
1169        Ok(())
1170    }
1171
1172    /// Set up machine state for an action call.
1173    pub fn setup_action<Args>(&mut self, name: Identifier, args: Args) -> Result<(), MachineError>
1174    where
1175        Args: IntoIterator,
1176        Args::Item: Into<Value>,
1177    {
1178        #[cfg(feature = "bench")]
1179        self.stopwatch
1180            .start(format!("setup_action: {}", name).as_str());
1181
1182        // verify number and types of arguments
1183        let action_def = self
1184            .machine
1185            .action_defs
1186            .get(&name)
1187            .ok_or_else(|| MachineError::new(MachineErrorType::NotDefined(name.to_string())))?;
1188        let args: Vec<Value> = args.into_iter().map(Into::into).collect();
1189        if args.len() != action_def.params.len() {
1190            return Err(MachineError::new(MachineErrorType::Unknown(
1191                alloc::format!(
1192                    "action `{}` expects {} argument(s), but was called with {}",
1193                    name,
1194                    action_def.params.len(),
1195                    args.len()
1196                ),
1197            )));
1198        }
1199        for (arg, param) in args.iter().zip(action_def.params.iter()) {
1200            if !arg.fits_type(&param.ty) {
1201                return Err(MachineError::new(MachineErrorType::invalid_type(
1202                    param.ty.to_string(),
1203                    arg.type_name(),
1204                    "invalid function argument",
1205                )));
1206            }
1207        }
1208
1209        self.setup_function(&Label::new(name, LabelType::Action))?;
1210        for a in args {
1211            self.ipush(a)?;
1212        }
1213
1214        #[cfg(feature = "bench")]
1215        self.stopwatch.stop();
1216
1217        Ok(())
1218    }
1219
1220    /// Call an action loaded into the VM by name. Accepts a list of
1221    /// arguments to the function, which must match the number of
1222    /// arguments expected. Returns a MachineError on failure.
1223    // TODO(chip): I don't really like how V: Into<Value> works here
1224    // because it still means all of the args have to have the same
1225    // type.
1226    pub fn call_action<Args>(
1227        &mut self,
1228        name: Identifier,
1229        args: Args,
1230    ) -> Result<ExitReason, MachineError>
1231    where
1232        Args: IntoIterator,
1233        Args::Item: Into<Value>,
1234    {
1235        if !matches!(&self.ctx, CommandContext::Action(ActionContext{name: ctx_name,..}) if *ctx_name == name)
1236        {
1237            return Err(MachineErrorType::ContextMismatch.into());
1238        }
1239        self.setup_action(name, args)?;
1240        self.run()
1241    }
1242
1243    /// Call the seal block on this command to produce an envelope. The
1244    /// seal block is given an implicit parameter `this` and should
1245    /// return an opaque envelope struct on the stack.
1246    pub fn call_seal(&mut self, this_data: Struct) -> Result<ExitReason, MachineError> {
1247        let name = this_data.name.clone();
1248        if !matches!(&self.ctx, CommandContext::Seal(SealContext{name: ctx_name,..}) if *ctx_name == name)
1249        {
1250            return Err(MachineErrorType::ContextMismatch.into());
1251        }
1252        self.setup_function(&Label::new(name, LabelType::CommandSeal))?;
1253
1254        // Seal/Open pushes the argument and defines it itself, because
1255        // it calls through a function stub. So we just push `this_data`
1256        // onto the stack.
1257        self.ipush(this_data)?;
1258        self.run()
1259    }
1260
1261    /// Call the open block on an envelope struct to produce a command struct.
1262    pub fn call_open(
1263        &mut self,
1264        name: Identifier,
1265        envelope: Struct,
1266    ) -> Result<ExitReason, MachineError> {
1267        if !matches!(&self.ctx, CommandContext::Open(OpenContext{name: ctx_name,..}) if *ctx_name == name)
1268        {
1269            return Err(MachineErrorType::ContextMismatch.into());
1270        }
1271        self.setup_function(&Label::new(name, LabelType::CommandOpen))?;
1272        self.ipush(envelope)?;
1273        self.run()
1274    }
1275
1276    /// Destroy the `RunState` and return the value on top of the stack.
1277    pub fn consume_return(mut self) -> Result<Value, MachineError> {
1278        self.stack
1279            .pop_value()
1280            .map_err(|t| MachineError::from_position(t, self.pc, self.machine.codemap.as_ref()))
1281    }
1282
1283    fn validate_fact_literal(&mut self, fact: &Fact) -> Result<(), MachineError> {
1284        #[cfg(feature = "bench")]
1285        self.stopwatch.start("validate_fact_literal");
1286
1287        if !self
1288            .machine
1289            .fact_defs
1290            .get(&fact.name)
1291            .is_some_and(|schema| validate_fact_schema(fact, schema))
1292        {
1293            return Err(MachineError::from_position(
1294                MachineErrorType::InvalidSchema(fact.name.clone()),
1295                self.pc,
1296                self.machine.codemap.as_ref(),
1297            ));
1298        }
1299
1300        #[cfg(feature = "bench")]
1301        self.stopwatch.stop();
1302
1303        Ok(())
1304    }
1305}
1306
1307/// An implementation of [`Stack`].
1308pub struct MachineStack(pub(crate) HVec<Value, STACK_SIZE>);
1309
1310impl MachineStack {
1311    /// Creates an empty stack.
1312    pub const fn new() -> Self {
1313        Self(HVec::new())
1314    }
1315
1316    /// Returns the number of values in the stack.
1317    pub fn len(&self) -> usize {
1318        self.0.len()
1319    }
1320
1321    /// Reports whether the stack is empty.
1322    pub fn is_empty(&self) -> bool {
1323        self.0.len() == 0
1324    }
1325
1326    fn clear(&mut self) {
1327        self.0.clear();
1328    }
1329
1330    /// Turn a Stack into a Vec of Values.
1331    pub fn into_vec(self) -> Vec<Value> {
1332        self.0.into_iter().collect()
1333    }
1334}
1335
1336impl Stack for MachineStack {
1337    fn push_value(&mut self, value: Value) -> Result<(), MachineErrorType> {
1338        self.0
1339            .push(value)
1340            .map_err(|_| MachineErrorType::StackOverflow)
1341    }
1342
1343    fn pop_value(&mut self) -> Result<Value, MachineErrorType> {
1344        self.0.pop().ok_or(MachineErrorType::StackUnderflow)
1345    }
1346
1347    fn peek_value(&mut self) -> Result<&mut Value, MachineErrorType> {
1348        self.0.last_mut().ok_or(MachineErrorType::StackUnderflow)
1349    }
1350}
1351
1352impl Default for MachineStack {
1353    fn default() -> Self {
1354        Self::new()
1355    }
1356}
1357
1358impl<M> Display for RunState<'_, M>
1359where
1360    M: MachineIO<MachineStack>,
1361{
1362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1363        writeln!(f, "# Name table:")?;
1364        for (k, v) in &self.machine.labels {
1365            writeln!(f, "  {}: {:?}", k, v)?;
1366        }
1367        write!(f, "# Current defs")?;
1368        if !self.call_state.is_empty() {
1369            write!(f, " ({} stacked)", self.call_state.len())?;
1370        }
1371        writeln!(f, ":")?;
1372        for (k, v) in self.scope.locals() {
1373            writeln!(f, "  {}: {}", k, v)?;
1374        }
1375        writeln!(f, "# Stack:")?;
1376        for v in &self.stack.0 {
1377            write!(f, "{} ", v)?;
1378        }
1379        writeln!(f)?;
1380        writeln!(f, "# Program:")?;
1381        for (addr, instr) in self.machine.progmem.iter().enumerate() {
1382            for (k, v) in &self.machine.labels {
1383                if *v == addr {
1384                    writeln!(f, "{}:", k)?;
1385                }
1386            }
1387            if addr == self.pc() {
1388                write!(f, "*")?;
1389            } else {
1390                write!(f, " ")?;
1391            }
1392            writeln!(f, "  {:4}  {}", addr, instr)?;
1393        }
1394        Ok(())
1395    }
1396}