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