miden_processor/system/
mod.rs

1use alloc::vec::Vec;
2use core::fmt::{self, Display};
3
4use miden_air::RowIndex;
5
6use super::{ExecutionError, Felt, FieldElement, SysTrace, Word, EMPTY_WORD, ONE, 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;
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        debug_assert!(!self.in_syscall, "call in syscall");
185        self.ctx = (self.clk + 1).into();
186        self.fmp = Felt::new(FMP_MIN);
187        self.fn_hash = fn_hash;
188    }
189
190    /// Updates system registers to mark a new syscall.
191    ///
192    /// Internally, this performs the following updates:
193    /// - Set the execution context to 0 (the root context).
194    /// - Sets the free memory pointer to the initial value of syscalls (SYSCALL_FMP_MIN). This
195    ///   ensures that procedure locals within a syscall do not conflict with procedure locals of
196    ///   the original root context.
197    /// - Sets the in_syscall flag to true.
198    ///
199    /// A SYSCALL cannot be started when the VM is executing a SYSCALL.
200    ///
201    /// Note that this does not change the hash of the function which initiated the context:
202    /// for SYSCALLs this remains set to the hash of the last invoked function.
203    pub fn start_syscall(&mut self) {
204        debug_assert!(!self.in_syscall, "already in syscall");
205        self.ctx = ContextId::root();
206        self.fmp = Felt::from(SYSCALL_FMP_MIN);
207        self.in_syscall = true;
208    }
209
210    /// Updates system registers to the provided values. These updates are made at the end of a
211    /// CALL or a SYSCALL blocks.
212    ///
213    /// Note that we set in_syscall flag to true regardless of whether we return from a CALL or a
214    /// SYSCALL.
215    pub fn restore_context(&mut self, ctx: ContextId, fmp: Felt, fn_hash: Word) {
216        self.ctx = ctx;
217        self.fmp = fmp;
218        self.in_syscall = false;
219        self.fn_hash = fn_hash;
220    }
221
222    // TRACE GENERATIONS
223    // --------------------------------------------------------------------------------------------
224
225    /// Returns an execution trace of this system info container.
226    ///
227    /// If the trace is smaller than the specified `trace_len`, the columns of the trace are
228    /// extended to match the specified length as follows:
229    /// - the remainder of the `clk` column is filled in with increasing values of `clk`.
230    /// - the remainder of the `ctx` column is filled in with ZERO, which should be the last value
231    ///   in the column.
232    /// - the remainder of the `fmp` column is filled in with the last value in the column.
233    /// - the remainder of the `in_syscall` column is filled with ZERO, which should be the last
234    ///   value in this colum.
235    /// - the remainder of the `fn_hash` columns are filled with ZERO, which should be the last
236    ///   values in these columns.
237    ///
238    /// `num_rand_rows` indicates the number of rows at the end of the trace which will be
239    /// overwritten with random values. This parameter is unused because last rows are just
240    /// duplicates of the prior rows and thus can be safely overwritten.
241    pub fn into_trace(mut self, trace_len: usize, num_rand_rows: usize) -> SysTrace {
242        let clk: usize = self.clk().into();
243        // make sure that only the duplicate rows will be overwritten with random values
244        assert!(clk + num_rand_rows <= trace_len, "target trace length too small");
245
246        // complete the clk column by filling in all values after the last clock cycle. The values
247        // in the clk column are equal to the index of the row in the trace table.
248        self.clk_trace.resize(trace_len, ZERO);
249        for (i, clk) in self.clk_trace.iter_mut().enumerate().skip(clk) {
250            // converting from u32 is OK here because max trace length is 2^32
251            *clk = Felt::from(i as u32);
252        }
253
254        // complete the ctx column by filling all values after the last clock cycle with ZEROs as
255        // the last context must be zero context.
256        debug_assert!(self.ctx.is_root());
257        self.ctx_trace.resize(trace_len, ZERO);
258
259        // complete the fmp column by filling in all values after the last clock cycle with the
260        // value in the column at the last clock cycle.
261        let last_value = self.fmp_trace[clk];
262        self.fmp_trace[clk..].fill(last_value);
263        self.fmp_trace.resize(trace_len, last_value);
264
265        // complete the in_syscall column by filling all values after the last clock cycle with
266        // ZEROs as we must end the program in the root context which is not a SYSCALL
267        debug_assert!(!self.in_syscall);
268        self.in_syscall_trace.resize(trace_len, ZERO);
269
270        let mut trace = vec![self.clk_trace, self.fmp_trace, self.ctx_trace, self.in_syscall_trace];
271
272        // complete the fn hash columns by filling them with ZEROs as program execution must always
273        // end in the root context.
274        debug_assert_eq!(self.fn_hash, EMPTY_WORD);
275        for mut column in self.fn_hash_trace.into_iter() {
276            column.resize(trace_len, ZERO);
277            trace.push(column);
278        }
279
280        trace.try_into().expect("failed to convert vector to array")
281    }
282
283    // UTILITY METHODS
284    // --------------------------------------------------------------------------------------------
285
286    /// Makes sure there is enough memory allocated for the trace to accommodate a new row.
287    ///
288    /// Trace length is doubled every time it needs to be increased.
289    pub fn ensure_trace_capacity(&mut self) {
290        let current_capacity = self.clk_trace.len();
291        if self.clk + 1 >= RowIndex::from(current_capacity) {
292            let new_length = current_capacity * 2;
293            self.clk_trace.resize(new_length, ZERO);
294            self.ctx_trace.resize(new_length, ZERO);
295            self.fmp_trace.resize(new_length, ZERO);
296            self.in_syscall_trace.resize(new_length, ZERO);
297            for column in self.fn_hash_trace.iter_mut() {
298                column.resize(new_length, ZERO);
299            }
300        }
301    }
302}
303
304// EXECUTION CONTEXT
305// ================================================================================================
306
307/// Represents the ID of an execution context
308#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
309pub struct ContextId(u32);
310
311impl ContextId {
312    /// Returns the root context ID
313    pub fn root() -> Self {
314        Self(0)
315    }
316
317    /// Returns true if the context ID represents the root context
318    pub fn is_root(&self) -> bool {
319        self.0 == 0
320    }
321}
322
323impl From<RowIndex> for ContextId {
324    fn from(value: RowIndex) -> Self {
325        Self(value.as_u32())
326    }
327}
328
329impl From<u32> for ContextId {
330    fn from(value: u32) -> Self {
331        Self(value)
332    }
333}
334
335impl From<ContextId> for u32 {
336    fn from(context_id: ContextId) -> Self {
337        context_id.0
338    }
339}
340
341impl From<ContextId> for u64 {
342    fn from(context_id: ContextId) -> Self {
343        context_id.0.into()
344    }
345}
346
347impl From<ContextId> for Felt {
348    fn from(context_id: ContextId) -> Self {
349        context_id.0.into()
350    }
351}
352
353impl Display for ContextId {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        write!(f, "{}", self.0)
356    }
357}