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}