miden_processor/system/
mod.rs

1use alloc::vec::Vec;
2use core::fmt::{self, Display};
3
4use miden_air::RowIndex;
5
6use super::{EMPTY_WORD, ExecutionError, Felt, FieldElement, SysTrace, Word, ZERO};
7
8#[cfg(test)]
9mod tests;
10
11// SYSTEM INFO
12// ================================================================================================
13
14/// System info container for the VM.
15///
16/// This keeps track of the following system variables:
17/// - clock cycle (clk), which starts at 0 and is incremented with every step.
18/// - execution context (ctx), which starts at 0 (root context), and changes when CALL or SYSCALL
19///   operations are executed by the VM (or when we return from a CALL or SYSCALL).
20/// - free memory pointer (fmp), which is initially set to 2^30.
21/// - hash of the function which initiated the current execution context. if the context was
22///   initiated from the root context, this will be set to ZEROs.
23#[derive(Debug)]
24pub struct System {
25    clk: RowIndex,
26    ctx: ContextId,
27    fn_hash: Word,
28    ctx_trace: Vec<Felt>,
29    clk_trace: Vec<Felt>,
30    fn_hash_trace: [Vec<Felt>; 4],
31}
32
33impl System {
34    // CONSTRUCTOR
35    // --------------------------------------------------------------------------------------------
36    /// Returns a new [System] struct with execution traces instantiated with the specified length.
37    ///
38    /// Initializes the free memory pointer `fmp` used for local memory offsets to 2^30.
39    pub fn new(init_trace_capacity: usize) -> Self {
40        Self {
41            clk: RowIndex::from(0),
42            ctx: ContextId::root(),
43            fn_hash: EMPTY_WORD,
44            clk_trace: vec![Felt::ZERO; init_trace_capacity],
45            ctx_trace: vec![Felt::ZERO; init_trace_capacity],
46            fn_hash_trace: [
47                vec![Felt::ZERO; init_trace_capacity],
48                vec![Felt::ZERO; init_trace_capacity],
49                vec![Felt::ZERO; init_trace_capacity],
50                vec![Felt::ZERO; init_trace_capacity],
51            ],
52        }
53    }
54
55    // PUBLIC ACCESSORS
56    // --------------------------------------------------------------------------------------------
57
58    /// Returns the current clock cycle of a process.
59    #[inline(always)]
60    pub fn clk(&self) -> RowIndex {
61        self.clk
62    }
63
64    /// Returns the current execution context ID.
65    #[inline(always)]
66    pub fn ctx(&self) -> ContextId {
67        self.ctx
68    }
69
70    /// Returns hash of the function which initiated the current execution context.
71    #[inline(always)]
72    pub fn fn_hash(&self) -> Word {
73        self.fn_hash
74    }
75
76    /// Returns execution trace length for the systems columns of the process.
77    ///
78    /// Trace length of the system columns is equal to the number of cycles executed by the VM.
79    #[inline(always)]
80    pub fn trace_len(&self) -> usize {
81        self.clk.into()
82    }
83
84    /// Returns execution context ID at the specified clock cycle.
85    #[inline(always)]
86    pub fn get_ctx_at(&self, clk: RowIndex) -> ContextId {
87        (self.ctx_trace[clk.as_usize()].as_int() as u32).into()
88    }
89
90    // STATE MUTATORS
91    // --------------------------------------------------------------------------------------------
92
93    /// Increments the clock cycle.
94    pub fn advance_clock(&mut self, max_cycles: u32) -> Result<(), ExecutionError> {
95        self.clk += 1_u32;
96
97        // Check that maximum number of cycles is not exceeded.
98        if self.clk.as_u32() > max_cycles {
99            return Err(ExecutionError::CycleLimitExceeded(max_cycles));
100        }
101
102        let clk: usize = self.clk.into();
103
104        self.clk_trace[clk] = Felt::from(self.clk);
105        self.ctx_trace[clk] = Felt::from(self.ctx);
106
107        self.fn_hash_trace[0][clk] = self.fn_hash[0];
108        self.fn_hash_trace[1][clk] = self.fn_hash[1];
109        self.fn_hash_trace[2][clk] = self.fn_hash[2];
110        self.fn_hash_trace[3][clk] = self.fn_hash[3];
111
112        Ok(())
113    }
114
115    /// Updates system registers to mark a new function call.
116    ///
117    /// Internally, this performs the following updates:
118    /// - Set the execution context to the current clock cycle + 1. This ensures that the context is
119    ///   globally unique as is never set to 0.
120    /// - Sets the free memory pointer to its initial value (FMP_MIN).
121    /// - Sets the hash of the function which initiated the current context to the provided value.
122    ///
123    /// A CALL or DYNCALL cannot be started when the VM is executing a SYSCALL.
124    pub fn start_call_or_dyncall(&mut self, fn_hash: Word) {
125        self.ctx = self.get_next_ctx_id();
126        self.fn_hash = fn_hash;
127    }
128
129    /// Updates system registers to mark a new syscall.
130    ///
131    /// Internally, this performs the following updates:
132    /// - Set the execution context to 0 (the root context).
133    /// - Sets the free memory pointer to the initial value of syscalls (SYSCALL_FMP_MIN). This
134    ///   ensures that procedure locals within a syscall do not conflict with procedure locals of
135    ///   the original root context.
136    ///
137    /// A SYSCALL cannot be started when the VM is executing a SYSCALL.
138    ///
139    /// Note that this does not change the hash of the function which initiated the context:
140    /// for SYSCALLs this remains set to the hash of the last invoked function.
141    pub fn start_syscall(&mut self) {
142        self.ctx = ContextId::root();
143    }
144
145    /// Updates system registers to the provided values. These updates are made at the end of a
146    /// CALL or a SYSCALL blocks.
147    pub fn restore_context(&mut self, ctx: ContextId, fn_hash: Word) {
148        self.ctx = ctx;
149        self.fn_hash = fn_hash;
150    }
151
152    // TRACE GENERATIONS
153    // --------------------------------------------------------------------------------------------
154
155    /// Returns an execution trace of this system info container.
156    ///
157    /// If the trace is smaller than the specified `trace_len`, the columns of the trace are
158    /// extended to match the specified length as follows:
159    /// - the remainder of the `clk` column is filled in with increasing values of `clk`.
160    /// - the remainder of the `ctx` column is filled in with ZERO, which should be the last value
161    ///   in the column.
162    /// - the remainder of the `fn_hash` columns are filled with ZERO, which should be the last
163    ///   values in these columns.
164    ///
165    /// `num_rand_rows` indicates the number of rows at the end of the trace which will be
166    /// overwritten with random values. This parameter is unused because last rows are just
167    /// duplicates of the prior rows and thus can be safely overwritten.
168    pub fn into_trace(mut self, trace_len: usize, num_rand_rows: usize) -> SysTrace {
169        let clk: usize = self.clk().into();
170        // make sure that only the duplicate rows will be overwritten with random values
171        assert!(clk + num_rand_rows <= trace_len, "target trace length too small");
172
173        // complete the clk column by filling in all values after the last clock cycle. The values
174        // in the clk column are equal to the index of the row in the trace table.
175        self.clk_trace.resize(trace_len, ZERO);
176        for (i, clk) in self.clk_trace.iter_mut().enumerate().skip(clk) {
177            // converting from u32 is OK here because max trace length is 2^32
178            *clk = Felt::from(i as u32);
179        }
180
181        // complete the ctx column by filling all values after the last clock cycle with ZEROs as
182        // the last context must be zero context.
183        debug_assert!(self.ctx.is_root());
184        self.ctx_trace.resize(trace_len, ZERO);
185
186        let mut trace = vec![self.clk_trace, self.ctx_trace];
187
188        // complete the fn hash columns by filling them with ZEROs as program execution must always
189        // end in the root context.
190        debug_assert_eq!(self.fn_hash, EMPTY_WORD);
191        for mut column in self.fn_hash_trace.into_iter() {
192            column.resize(trace_len, ZERO);
193            trace.push(column);
194        }
195
196        trace.try_into().expect("failed to convert vector to array")
197    }
198
199    // UTILITY METHODS
200    // --------------------------------------------------------------------------------------------
201
202    /// Makes sure there is enough memory allocated for the trace to accommodate a new row.
203    ///
204    /// Trace length is doubled every time it needs to be increased.
205    pub fn ensure_trace_capacity(&mut self) {
206        let current_capacity = self.clk_trace.len();
207        if self.clk + 1 >= RowIndex::from(current_capacity) {
208            let new_length = current_capacity * 2;
209            self.clk_trace.resize(new_length, ZERO);
210            self.ctx_trace.resize(new_length, ZERO);
211            for column in self.fn_hash_trace.iter_mut() {
212                column.resize(new_length, ZERO);
213            }
214        }
215    }
216
217    /// Returns the next context ID that would be created given the current state.
218    ///
219    /// Note: This only applies to the context created upon a `CALL` or `DYNCALL` operation;
220    /// specifically the `SYSCALL` operation doesn't apply as it always goes back to the root
221    /// context.
222    pub fn get_next_ctx_id(&self) -> ContextId {
223        (self.clk + 1).into()
224    }
225}
226
227// EXECUTION CONTEXT
228// ================================================================================================
229
230/// Represents the ID of an execution context
231#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
232pub struct ContextId(u32);
233
234impl ContextId {
235    /// Returns the root context ID
236    pub fn root() -> Self {
237        Self(0)
238    }
239
240    /// Returns true if the context ID represents the root context
241    pub fn is_root(&self) -> bool {
242        self.0 == 0
243    }
244}
245
246impl From<RowIndex> for ContextId {
247    fn from(value: RowIndex) -> Self {
248        Self(value.as_u32())
249    }
250}
251
252impl From<u32> for ContextId {
253    fn from(value: u32) -> Self {
254        Self(value)
255    }
256}
257
258impl From<ContextId> for u32 {
259    fn from(context_id: ContextId) -> Self {
260        context_id.0
261    }
262}
263
264impl From<ContextId> for u64 {
265    fn from(context_id: ContextId) -> Self {
266        context_id.0.into()
267    }
268}
269
270impl From<ContextId> for Felt {
271    fn from(context_id: ContextId) -> Self {
272        context_id.0.into()
273    }
274}
275
276impl Display for ContextId {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        write!(f, "{}", self.0)
279    }
280}