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}