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}