miden_core/events/sys_events.rs
1use core::fmt;
2
3use super::{EventId, EventName};
4
5// SYSTEM EVENTS
6// ================================================================================================
7
8/// Defines a set of host-side actions which can be initiated from the VM.
9///
10/// Most actions update or query one of the three advice-provider components: Merkle store, advice
11/// stack, or advice map. Deferred-DAG actions update host-side deferred state, and evaluation may
12/// also push canonical node data to the advice stack.
13///
14/// All actions, except for `MerkleNodeMerge`, `Ext2Inv` and `UpdateMerkleNode` can be invoked
15/// directly from Miden assembly via dedicated instructions.
16///
17/// System event IDs are derived from blake3-hashing their names (prefixed with "sys::").
18///
19/// The enum variant order matches the indices in SYSTEM_EVENT_LOOKUP, allowing efficient const
20/// lookup via `to_event_id()`. The discriminants are implicitly 0, 1, 2, ... `COUNT - 1`.
21#[derive(Copy, Clone, Debug, Eq, PartialEq)]
22#[repr(u8)]
23pub enum SystemEvent {
24 // MERKLE STORE EVENTS
25 // --------------------------------------------------------------------------------------------
26 /// Creates a new Merkle tree in the advice provider by combining Merkle trees with the
27 /// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`.
28 ///
29 /// Inputs:
30 /// Operand stack: [LEFT_ROOT, RIGHT_ROOT, ...]
31 /// Merkle store: {LEFT_ROOT, RIGHT_ROOT}
32 ///
33 /// Outputs:
34 /// Operand stack: [LEFT_ROOT, RIGHT_ROOT, ...]
35 /// Merkle store: {LEFT_ROOT, RIGHT_ROOT, hash(LEFT_ROOT, RIGHT_ROOT)}
36 ///
37 /// After the operation, both the original trees and the new tree remains in the advice
38 /// provider (i.e., the input trees are not removed).
39 MerkleNodeMerge,
40
41 // ADVICE STACK SYSTEM EVENTS
42 // --------------------------------------------------------------------------------------------
43 /// Pushes a node of the Merkle tree specified by the values on the top of the operand stack
44 /// onto the advice stack in structural order for consumption by `AdvPopW`.
45 ///
46 /// Inputs:
47 /// Operand stack: [depth, index, TREE_ROOT, ...]
48 /// Advice stack: [...]
49 /// Merkle store: {TREE_ROOT<-NODE}
50 ///
51 /// Outputs:
52 /// Operand stack: [depth, index, TREE_ROOT, ...]
53 /// Advice stack: [NODE, ...]
54 /// Merkle store: {TREE_ROOT<-NODE}
55 MerkleNodeToStack,
56
57 /// Pushes a list of field elements onto the advice stack. The list is looked up in the advice
58 /// map using the specified word from the operand stack as the key.
59 ///
60 /// Inputs:
61 /// Operand stack: [KEY, ...]
62 /// Advice stack: [...]
63 /// Advice map: {KEY: values}
64 ///
65 /// Outputs:
66 /// Operand stack: [KEY, ...]
67 /// Advice stack: [values, ...]
68 /// Advice map: {KEY: values}
69 MapValueToStack,
70
71 /// Pushes the number of elements in a list of field elements onto the advice stack. The list is
72 /// looked up in the advice map using the specified word from the operand stack as the key.
73 ///
74 /// Inputs:
75 /// Operand stack: [KEY, ...]
76 /// Advice stack: [...]
77 /// Advice map: {KEY: values}
78 ///
79 /// Outputs:
80 /// Operand stack: [KEY, ...]
81 /// Advice stack: [values.len(), ...]
82 /// Advice map: {KEY: values}
83 MapValueCountToStack,
84
85 /// Pushes a list of field elements onto the advice stack, along with the number of elements in
86 /// that list. The list is looked up in the advice map using the word at the top of the operand
87 /// stack as the key.
88 ///
89 /// Notice that the resulting elements list is not padded.
90 ///
91 /// Inputs:
92 /// Operand stack: [KEY, ...]
93 /// Advice stack: [...]
94 /// Advice map: {KEY: values}
95 ///
96 /// Outputs:
97 /// Operand stack: [KEY, ...]
98 /// Advice stack: [num_values, values, ...]
99 /// Advice map: {KEY: values}
100 MapValueToStackN0,
101
102 /// Pushes a padded list of field elements onto the advice stack, along with the number of
103 /// elements in that list. The list is looked up in the advice map using the word at the top of
104 /// the operand stack as the key.
105 ///
106 /// Notice that the elements list obtained from the advice map will be padded with zeros,
107 /// increasing its length to the next multiple of 4.
108 ///
109 /// Inputs:
110 /// Operand stack: [KEY, ...]
111 /// Advice stack: [...]
112 /// Advice map: {KEY: values}
113 ///
114 /// Outputs:
115 /// Operand stack: [KEY, ...]
116 /// Advice stack: [num_values, values, padding, ...]
117 /// Advice map: {KEY: values}
118 MapValueToStackN4,
119
120 /// Pushes a padded list of field elements onto the advice stack, along with the number of
121 /// elements in that list. The list is looked up in the advice map using the word at the top of
122 /// the operand stack as the key.
123 ///
124 /// Notice that the elements list obtained from the advice map will be padded with zeros,
125 /// increasing its length to the next multiple of 8.
126 ///
127 /// Inputs:
128 /// Operand stack: [KEY, ...]
129 /// Advice stack: [...]
130 /// Advice map: {KEY: values}
131 ///
132 /// Outputs:
133 /// Operand stack: [KEY, ...]
134 /// Advice stack: [num_values, values, padding, ...]
135 /// Advice map: {KEY: values}
136 MapValueToStackN8,
137
138 /// Pushes a flag onto the advice stack whether advice map has an entry with specified key.
139 ///
140 /// If the advice map has the entry with the key equal to the key placed at the top of the
141 /// operand stack, `1` will be pushed to the advice stack and `0` otherwise.
142 ///
143 /// Inputs:
144 /// Operand stack: [KEY, ...]
145 /// Advice stack: [...]
146 ///
147 /// Outputs:
148 /// Operand stack: [KEY, ...]
149 /// Advice stack: [has_mapkey, ...]
150 HasMapKey,
151
152 /// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1),
153 /// computes its multiplicative inverse and push the result onto the advice stack.
154 ///
155 /// Inputs:
156 /// Operand stack: [a1, a0, ...]
157 /// Advice stack: [...]
158 ///
159 /// Outputs:
160 /// Operand stack: [a1, a0, ...]
161 /// Advice stack: [b0, b1...]
162 ///
163 /// Where (b0, b1) is the multiplicative inverse of the extension field element (a0, a1) at the
164 /// top of the stack.
165 Ext2Inv,
166
167 /// Pushes the number of the leading zeros of the top stack element onto the advice stack.
168 ///
169 /// Inputs:
170 /// Operand stack: [n, ...]
171 /// Advice stack: [...]
172 ///
173 /// Outputs:
174 /// Operand stack: [n, ...]
175 /// Advice stack: [leading_zeros, ...]
176 U32Clz,
177
178 /// Pushes the number of the trailing zeros of the top stack element onto the advice stack.
179 ///
180 /// Inputs:
181 /// Operand stack: [n, ...]
182 /// Advice stack: [...]
183 ///
184 /// Outputs:
185 /// Operand stack: [n, ...]
186 /// Advice stack: [trailing_zeros, ...]
187 U32Ctz,
188
189 /// Pushes the number of the leading ones of the top stack element onto the advice stack.
190 ///
191 /// Inputs:
192 /// Operand stack: [n, ...]
193 /// Advice stack: [...]
194 ///
195 /// Outputs:
196 /// Operand stack: [n, ...]
197 /// Advice stack: [leading_ones, ...]
198 U32Clo,
199
200 /// Pushes the number of the trailing ones of the top stack element onto the advice stack.
201 ///
202 /// Inputs:
203 /// Operand stack: [n, ...]
204 /// Advice stack: [...]
205 ///
206 /// Outputs:
207 /// Operand stack: [n, ...]
208 /// Advice stack: [trailing_ones, ...]
209 U32Cto,
210
211 /// Pushes the base 2 logarithm of the top stack element, rounded down.
212 /// Inputs:
213 /// Operand stack: [n, ...]
214 /// Advice stack: [...]
215 ///
216 /// Outputs:
217 /// Operand stack: [n, ...]
218 /// Advice stack: [ilog2(n), ...]
219 ILog2,
220
221 // ADVICE MAP SYSTEM EVENTS
222 // --------------------------------------------------------------------------------------------
223 /// Reads words from memory at the specified range and inserts them into the advice map under
224 /// the key `KEY` located at the top of the stack.
225 ///
226 /// Inputs:
227 /// Operand stack: [KEY, start_addr, end_addr, ...]
228 /// Advice map: {...}
229 ///
230 /// Outputs:
231 /// Operand stack: [KEY, start_addr, end_addr, ...]
232 /// Advice map: {KEY: values}
233 ///
234 /// Where `values` are the elements located in memory[start_addr..end_addr].
235 MemToMap,
236
237 /// Reads two words from the operand stack and inserts them into the advice map under
238 /// `hdword(A, B)`.
239 ///
240 /// Inputs:
241 /// Operand stack: [A, B, ...]
242 /// Advice map: {...}
243 ///
244 /// Outputs:
245 /// Operand stack: [A, B, ...]
246 /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]}
247 ///
248 /// KEY is the generic Eidos hash of the eight Felts in `A || B`.
249 HdwordToMap,
250
251 /// Reads four words from the operand stack and inserts them into the advice map under the key
252 /// defined by the hash of these words.
253 ///
254 /// Inputs:
255 /// Operand stack: [A, B, C, D, ...]
256 /// Advice map: {...}
257 ///
258 /// Outputs:
259 /// Operand stack: [A, B, C, D, ...]
260 /// Advice map: {KEY: [A, B, C, D]} (16 elements)
261 ///
262 /// Where:
263 /// - KEY is computed as the canonical Eidos hash of `[A, B, C, D]`, using two 8-Felt
264 /// compression blocks with the full 16-Felt length bound into the initial chaining value.
265 HqwordToMap,
266
267 /// Reads three words from the operand stack and inserts the top two words into the advice map
268 /// under the key `Eidos::compress(C, A || B)`.
269 ///
270 /// Inputs:
271 /// Operand stack: [A, B, C, ...]
272 /// Advice map: {...}
273 ///
274 /// Outputs:
275 /// Operand stack: [A, B, C, ...]
276 /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]}
277 ///
278 /// In particular, using the framed initial CV for `(domain_tag = d, param0 = 8)` produces
279 /// `Eidos::hash_elements_in_domain(A || B, d)`.
280 CompressToMap,
281
282 // DEFERRED-DAG SYSTEM EVENTS
283 // --------------------------------------------------------------------------------------------
284 /// Registers and eagerly evaluates a deferred node whose full payload is on the operand stack.
285 ///
286 /// `CV` is one word containing the framed initial Eidos chaining value.
287 /// `PAYLOAD_LO || PAYLOAD_HI` is eight field elements:
288 /// either one [`crate::deferred::DataChunk`], two child digests (`lhs || rhs`) for a
289 /// precompile-owned join, or one `lhs || rhs` pair for a pair-list node. The installed registry
290 /// recovers the semantic
291 /// [`crate::deferred::EidosFrame`] from `CV` and decodes it via
292 /// [`crate::deferred::DeferredState::decode`]. Framework-owned AND and `TRUE` nodes are not
293 /// accepted by this generic event; `log_deferred` constructs rolling AND nodes. Frames that
294 /// semantically require more data chunks or pairs are rejected during precompile-specific
295 /// evaluation. Registration is performed by
296 /// [`crate::deferred::DeferredState::register`], so semantic failures surface immediately.
297 ///
298 /// This event does not push advice or return the node digest. The stack arguments are visible
299 /// in the VM execution trace, but the host-side registration is not constrained by the event.
300 /// Assembly code that later relies on the digest must compute it inside the VM from the same
301 /// `CV` and payload.
302 ///
303 /// Inputs:
304 /// Operand stack: [event_id, CV, PAYLOAD_LO, PAYLOAD_HI, ...]
305 ///
306 /// Outputs:
307 /// Operand stack: unchanged
308 /// Advice stack: unchanged
309 /// Deferred state: node registered and semantically evaluated
310 DeferredRegister,
311
312 /// Evaluates a registered deferred node and pushes its canonical frame and payload as advice.
313 ///
314 /// `NODE_DIGEST` is one word (4 field elements) and must already be registered in deferred
315 /// state. The handler evaluates it with [`crate::deferred::DeferredState::evaluate_digest`],
316 /// fetches the canonical node, and pushes its frame followed by its payload to the advice
317 /// stack.
318 ///
319 /// The frame is emitted first in advice-pop order so `adv_pushw adv_pushw adv_pushw` leaves
320 /// `[PAYLOAD_LO, PAYLOAD_HI, FRAME, ...]` on the operand stack for a single 8-felt payload.
321 /// Data payloads push two words per 8-felt chunk in advice order `HIGH, LOW`, preserving
322 /// canonical chunk order. Join payloads use the same two-word LIFO convention, leaving
323 /// `[lhs, rhs, FRAME, ...]`. `TRUE` pushes one zero word. These felts are unbound host hints.
324 /// Before proof-relevant use, assembly code must relate them with VM instructions to values
325 /// established independently of that advice.
326 ///
327 /// Inputs:
328 /// Operand stack: [event_id, NODE_DIGEST, ...]
329 ///
330 /// Outputs:
331 /// Operand stack: unchanged
332 /// Advice stack: canonical frame, then canonical payload words for `adv_pushw` LIFO
333 /// consumption
334 DeferredEvaluate,
335
336 /// Evaluates a registered deferred node and pushes only its canonical frame as advice.
337 ///
338 /// `NODE_DIGEST` is one word (4 field elements) and must already be registered in deferred
339 /// state. `TRUE` pushes one zero word. The returned frame is an unbound host hint; before
340 /// proof-relevant use, assembly code must relate it with VM instructions to a value established
341 /// independently of that advice.
342 ///
343 /// Inputs:
344 /// Operand stack: [event_id, NODE_DIGEST, ...]
345 ///
346 /// Outputs:
347 /// Operand stack: unchanged
348 /// Advice stack: canonical frame only
349 DeferredEvaluateFrame,
350
351 /// Evaluates a registered deferred node and pushes only its canonical payload as advice.
352 ///
353 /// Data payloads push two words per 8-felt chunk in advice order `HIGH, LOW` so
354 /// `adv_pushw adv_pushw` leaves `[LOW, HIGH, ...]` on the operand stack for that chunk. Chunks
355 /// are emitted in canonical chunk order. Join payloads use the same two-word LIFO convention,
356 /// leaving `[lhs, rhs, ...]` after two `adv_pushw`s. `TRUE` pushes no advice. These felts are
357 /// unbound host hints. Before proof-relevant use, assembly code must relate them with VM
358 /// instructions to values established independently of that advice.
359 ///
360 /// Inputs:
361 /// Operand stack: [event_id, NODE_DIGEST, ...]
362 ///
363 /// Outputs:
364 /// Operand stack: unchanged
365 /// Advice stack: canonical payload only, word-ordered for `adv_pushw` LIFO consumption
366 DeferredEvaluatePayload,
367
368 /// Registers and eagerly evaluates a memory-backed deferred node.
369 ///
370 /// `CV` is one word containing the framed initial Eidos chaining value. The installed registry
371 /// recovers its semantic frame to determine the memory-backed payload shape. The stack-supplied
372 /// `ptr` and `n_chunks` are visible in the VM
373 /// execution trace and select the range `[ptr, ptr + 8 * n_chunks)`. The host reads `n_chunks`
374 /// 8-felt [`crate::deferred::DataChunk`] values from that range, but this event adds no AIR
375 /// constraint tying the registered contents to those memory cells.
376 ///
377 /// The registered deferred-CHUNKS domain denotes framework-owned opaque data, while other data
378 /// frames remain precompile-owned. Pair-list frames interpret chunks as `lhs || rhs` pairs.
379 /// Precompile-owned join frames require `n_chunks == 1` and interpret the single chunk as
380 /// `lhs || rhs`. Framework-owned AND and `TRUE` nodes are not accepted by this generic event;
381 /// `log_deferred` constructs rolling AND nodes. The handler performs a cheap budget pre-check
382 /// before allocating or reading memory, then delegates registration to
383 /// [`crate::deferred::DeferredState::register`].
384 ///
385 /// This event does not push advice or return the node digest. A program that relies on the
386 /// registered node must compute its digest with VM instructions from the same `CV` and ordered
387 /// chunk sequence. The `register_mem` MASM wrapper does this with the canonical Eidos deferred
388 /// framing: the frame determines the initial chaining word, then payload chunks are compressed
389 /// in order. If the event and the VM hash different chunk
390 /// sequences, the VM-computed digest does not identify the host-registered node and cannot bind
391 /// that registration into a proof-relevant deferred claim.
392 ///
393 /// Inputs:
394 /// Operand stack: [event_id, n_chunks, CV, ptr, ...]
395 ///
396 /// Outputs:
397 /// Operand stack: unchanged
398 /// Advice stack: unchanged
399 /// Deferred state: node registered and semantically evaluated
400 DeferredRegisterData,
401
402 /// Exports a registered true assertion's witness into the advice map.
403 ///
404 /// The selected node may be unlogged, but cannot be literal TRUE. Its original digest is
405 /// both the witness root and map key; any existing value is replaced with the computed witness.
406 /// The value is `[byte_len, packed_u32_le(witness_bytes)...]`. Only the final u32 limb is
407 /// zero-padded, and `byte_len` excludes padding.
408 ///
409 /// Both stacks and the deferred root are preserved. Errors leave existing advice unchanged.
410 /// Consumers must validate the witness and bind its root to an independently established
411 /// expected digest.
412 ///
413 /// Inputs:
414 /// Operand stack: [event_id, NODE_DIGEST, ...]
415 ///
416 /// Outputs:
417 /// Operand stack: unchanged
418 /// Advice stack: unchanged
419 /// Advice map: {NODE_DIGEST: [byte_len, packed_u32_le(witness_bytes)...]}
420 DeferredExportWitness,
421
422 // NON-MUTATING SYSTEM EVENTS
423 // --------------------------------------------------------------------------------------------
424 /// Signals an optional, read-only trace event to the host.
425 ///
426 /// Assembly programs emit trace events with `trace`, `trace.CONST`, or `trace.event("...")`.
427 /// When the underlying `emit` observes this system event ID at stack position 0, the VM
428 /// forwards the user trace event ID at stack position 1 to the host's trace handler. The
429 /// immediate form lowers to `push.<user_trace_id> push.<sys::trace_event> emit drop drop`.
430 /// Trace handlers can observe the processor state, but cannot mutate VM state or the advice
431 /// provider. If no handler is registered for the user trace event ID, the event is a no-op.
432 ///
433 /// Hosts are expected not to raise an error if they encounter a `user_trace_id` for which no
434 /// trace handler is registered.
435 ///
436 /// Inputs:
437 /// Operand stack: [sys::trace_event, user_trace_id, ...]
438 ///
439 /// Outputs:
440 /// Operand stack: unchanged
441 /// Advice provider: unchanged
442 TraceEvent,
443}
444
445impl SystemEvent {
446 /// Attempts to convert an EventId into a SystemEvent by looking it up in the const table.
447 ///
448 /// Returns `Some(SystemEvent)` if the ID matches a known system event, `None` otherwise.
449 /// This uses a const lookup table with hardcoded EventIds, avoiding runtime hash computation.
450 pub const fn from_event_id(event_id: EventId) -> Option<Self> {
451 let lookup = Self::LOOKUP;
452 let mut i = 0;
453 while i < lookup.len() {
454 if lookup[i].id.as_u64() == event_id.as_u64() {
455 return Some(lookup[i].event);
456 }
457 i += 1;
458 }
459 None
460 }
461
462 /// Attempts to convert a name into a SystemEvent by looking it up in the const table.
463 ///
464 /// Returns `Some(SystemEvent)` if the name matches a known system event, `None` otherwise.
465 /// This uses const string comparison against the lookup table.
466 pub const fn from_name(name: &str) -> Option<Self> {
467 let lookup = Self::LOOKUP;
468 let mut i = 0;
469 while i < lookup.len() {
470 if str_eq(name, lookup[i].name) {
471 return Some(lookup[i].event);
472 }
473 i += 1;
474 }
475 None
476 }
477
478 /// Returns the human-readable name of this system event as an [`EventName`].
479 ///
480 /// System event names are prefixed with `sys::` to distinguish them from user-defined events.
481 pub const fn event_name(&self) -> EventName {
482 EventName::new(Self::LOOKUP[*self as usize].name)
483 }
484
485 /// Returns the [`EventId`] for this system event.
486 ///
487 /// The ID is looked up from the const LOOKUP table using the enum's discriminant
488 /// as the index. The discriminants are explicitly set to match the array indices.
489 pub const fn event_id(&self) -> EventId {
490 Self::LOOKUP[*self as usize].id
491 }
492
493 /// Returns an array of all system event variants.
494 pub const fn all() -> [Self; Self::COUNT] {
495 [
496 Self::MerkleNodeMerge,
497 Self::MerkleNodeToStack,
498 Self::MapValueToStack,
499 Self::MapValueCountToStack,
500 Self::MapValueToStackN0,
501 Self::MapValueToStackN4,
502 Self::MapValueToStackN8,
503 Self::HasMapKey,
504 Self::Ext2Inv,
505 Self::U32Clz,
506 Self::U32Ctz,
507 Self::U32Clo,
508 Self::U32Cto,
509 Self::ILog2,
510 Self::MemToMap,
511 Self::HdwordToMap,
512 Self::HqwordToMap,
513 Self::CompressToMap,
514 Self::DeferredRegister,
515 Self::DeferredEvaluate,
516 Self::DeferredEvaluateFrame,
517 Self::DeferredEvaluatePayload,
518 Self::DeferredRegisterData,
519 Self::DeferredExportWitness,
520 Self::TraceEvent,
521 ]
522 }
523}
524
525impl From<SystemEvent> for EventName {
526 fn from(system_event: SystemEvent) -> Self {
527 system_event.event_name()
528 }
529}
530
531impl crate::prettier::PrettyPrint for SystemEvent {
532 fn render(&self) -> crate::prettier::Document {
533 crate::prettier::display(self)
534 }
535}
536
537impl fmt::Display for SystemEvent {
538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539 const PREFIX_LEN: usize = "sys::".len();
540
541 let (_prefix, rest) = Self::LOOKUP[*self as usize].name.split_at(PREFIX_LEN);
542 write!(f, "{rest}")
543 }
544}
545
546// LOOKUP TABLE
547// ================================================================================================
548
549/// An entry in the system event lookup table, containing all metadata for a system event.
550#[derive(Copy, Clone, Debug)]
551pub(crate) struct SystemEventEntry {
552 /// The unique event ID (hash of the name)
553 pub id: EventId,
554 /// The system event variant
555 pub event: SystemEvent,
556 /// The full event name string (e.g., "sys::merkle_node_merge")
557 pub name: &'static str,
558}
559
560impl SystemEvent {
561 /// The total number of system events.
562 pub const COUNT: usize = 25;
563
564 /// Lookup table mapping system events to their metadata.
565 ///
566 /// The enum variant order matches the indices in this table, allowing efficient const
567 /// lookup via array indexing using discriminants.
568 const LOOKUP: [SystemEventEntry; Self::COUNT] = [
569 SystemEventEntry {
570 id: EventId::from_u64(7243907139105902342),
571 event: SystemEvent::MerkleNodeMerge,
572 name: "sys::merkle_node_merge",
573 },
574 SystemEventEntry {
575 id: EventId::from_u64(6873007751276594108),
576 event: SystemEvent::MerkleNodeToStack,
577 name: "sys::merkle_node_to_stack",
578 },
579 SystemEventEntry {
580 id: EventId::from_u64(17843484659000820118),
581 event: SystemEvent::MapValueToStack,
582 name: "sys::map_value_to_stack",
583 },
584 SystemEventEntry {
585 id: EventId::from_u64(3470274154276391308),
586 event: SystemEvent::MapValueCountToStack,
587 name: "sys::map_value_count_to_stack",
588 },
589 SystemEventEntry {
590 id: EventId::from_u64(11775886982554463322),
591 event: SystemEvent::MapValueToStackN0,
592 name: "sys::map_value_to_stack_n_0",
593 },
594 SystemEventEntry {
595 id: EventId::from_u64(3443305460233942990),
596 event: SystemEvent::MapValueToStackN4,
597 name: "sys::map_value_to_stack_n_4",
598 },
599 SystemEventEntry {
600 id: EventId::from_u64(1741586542981559489),
601 event: SystemEvent::MapValueToStackN8,
602 name: "sys::map_value_to_stack_n_8",
603 },
604 SystemEventEntry {
605 id: EventId::from_u64(5642583036089175977),
606 event: SystemEvent::HasMapKey,
607 name: "sys::has_map_key",
608 },
609 SystemEventEntry {
610 id: EventId::from_u64(9660728691489438960),
611 event: SystemEvent::Ext2Inv,
612 name: "sys::ext2_inv",
613 },
614 SystemEventEntry {
615 id: EventId::from_u64(1503707361178382932),
616 event: SystemEvent::U32Clz,
617 name: "sys::u32_clz",
618 },
619 SystemEventEntry {
620 id: EventId::from_u64(10656887096526143429),
621 event: SystemEvent::U32Ctz,
622 name: "sys::u32_ctz",
623 },
624 SystemEventEntry {
625 id: EventId::from_u64(12846584985739176048),
626 event: SystemEvent::U32Clo,
627 name: "sys::u32_clo",
628 },
629 SystemEventEntry {
630 id: EventId::from_u64(6773574803673468616),
631 event: SystemEvent::U32Cto,
632 name: "sys::u32_cto",
633 },
634 SystemEventEntry {
635 id: EventId::from_u64(7444351342957461231),
636 event: SystemEvent::ILog2,
637 name: "sys::ilog2",
638 },
639 SystemEventEntry {
640 id: EventId::from_u64(5768534446586058686),
641 event: SystemEvent::MemToMap,
642 name: "sys::mem_to_map",
643 },
644 SystemEventEntry {
645 id: EventId::from_u64(5988159172915333521),
646 event: SystemEvent::HdwordToMap,
647 name: "sys::hdword_to_map",
648 },
649 SystemEventEntry {
650 id: EventId::from_u64(11723176702659679401),
651 event: SystemEvent::HqwordToMap,
652 name: "sys::hqword_to_map",
653 },
654 SystemEventEntry {
655 // This string is part of the event identity and is hashed into compiled programs.
656 // Changing it requires a MAST format transition even though the assembly instruction is
657 // spelled `adv.insert_compress`.
658 id: EventId::from_u64(454105713963103935),
659 event: SystemEvent::CompressToMap,
660 name: "sys::bcompress_to_map",
661 },
662 SystemEventEntry {
663 id: EventId::from_u64(2036565939244996992),
664 event: SystemEvent::DeferredRegister,
665 name: "sys::adv::register_deferred",
666 },
667 SystemEventEntry {
668 id: EventId::from_u64(2795079927114754645),
669 event: SystemEvent::DeferredEvaluate,
670 name: "sys::adv::evaluate_deferred",
671 },
672 SystemEventEntry {
673 id: EventId::from_u64(7236367774590045862),
674 event: SystemEvent::DeferredEvaluateFrame,
675 name: "sys::adv::evaluate_deferred_frame",
676 },
677 SystemEventEntry {
678 id: EventId::from_u64(8091749904895009326),
679 event: SystemEvent::DeferredEvaluatePayload,
680 name: "sys::adv::evaluate_deferred_payload",
681 },
682 SystemEventEntry {
683 id: EventId::from_u64(13021247594355482329),
684 event: SystemEvent::DeferredRegisterData,
685 name: "sys::adv::register_deferred_data",
686 },
687 SystemEventEntry {
688 id: EventId::from_u64(15159456528524262446),
689 event: SystemEvent::DeferredExportWitness,
690 name: "sys::adv::export_deferred_witness",
691 },
692 SystemEventEntry {
693 id: EventId::from_u64(1768618069850226410),
694 event: SystemEvent::TraceEvent,
695 name: "sys::trace_event",
696 },
697 ];
698}
699
700// HELPERS
701// ================================================================================================
702
703/// Const-compatible string equality check.
704const fn str_eq(a: &str, b: &str) -> bool {
705 let a_bytes = a.as_bytes();
706 let b_bytes = b.as_bytes();
707
708 if a_bytes.len() != b_bytes.len() {
709 return false;
710 }
711
712 let mut i = 0;
713 while i < a_bytes.len() {
714 if a_bytes[i] != b_bytes[i] {
715 return false;
716 }
717 i += 1;
718 }
719 true
720}
721
722#[cfg(test)]
723mod test {
724 use super::*;
725
726 #[test]
727 fn test_system_events() {
728 // Comprehensive test verifying consistency between SystemEvent::all() and
729 // SystemEvent::LOOKUP. This ensures all() and LOOKUP are in sync, lookup table has
730 // correct IDs/names, and all variants are covered.
731
732 // Verify lengths match COUNT
733 assert_eq!(SystemEvent::all().len(), SystemEvent::COUNT);
734 assert_eq!(SystemEvent::LOOKUP.len(), SystemEvent::COUNT);
735
736 // Iterate through both all() and LOOKUP together, checking all invariants
737 for (i, (event, entry)) in
738 SystemEvent::all().iter().zip(SystemEvent::LOOKUP.iter()).enumerate()
739 {
740 // Verify LOOKUP entry matches the event at the same index
741 assert_eq!(
742 entry.event, *event,
743 "LOOKUP[{}].event ({:?}) doesn't match all()[{}] ({:?})",
744 i, entry.event, i, event
745 );
746
747 // Verify LOOKUP entry ID matches enum lookup.
748 let looked_up_id = event.event_id();
749 assert_eq!(
750 entry.id,
751 looked_up_id,
752 "LOOKUP[{}].id is EventId::from_u64({}), but {:?}.event_id() returns EventId::from_u64({})",
753 i,
754 entry.id.as_u64(),
755 event,
756 looked_up_id.as_u64()
757 );
758
759 // Verify name has correct "sys::" prefix
760 assert!(
761 entry.name.starts_with("sys::"),
762 "SystemEvent name should start with 'sys::': {}",
763 entry.name
764 );
765 assert_eq!(
766 entry.id,
767 EventId::from_name(entry.name),
768 "LOOKUP entry for {} has a stale hardcoded event ID",
769 entry.name
770 );
771
772 // Verify from_event_id lookup works
773 let looked_up =
774 SystemEvent::from_event_id(entry.id).expect("SystemEvent should be found by ID");
775 assert_eq!(looked_up, *event);
776
777 // Verify from_name lookup works
778 let looked_up_by_name =
779 SystemEvent::from_name(entry.name).expect("SystemEvent should be found by name");
780 assert_eq!(looked_up_by_name, *event);
781
782 // Verify EventName conversion works
783 let event_name = event.event_name();
784 assert_eq!(event_name.as_str(), entry.name);
785 assert!(SystemEvent::from_name(event_name.as_str()).is_some());
786 let event_name_from_into: EventName = (*event).into();
787 assert_eq!(event_name_from_into.as_str(), entry.name);
788 assert!(SystemEvent::from_name(event_name_from_into.as_str()).is_some());
789
790 // Exhaustive match to ensure compile-time error when adding new variants
791 match event {
792 SystemEvent::MerkleNodeMerge
793 | SystemEvent::MerkleNodeToStack
794 | SystemEvent::MapValueToStack
795 | SystemEvent::MapValueCountToStack
796 | SystemEvent::MapValueToStackN0
797 | SystemEvent::MapValueToStackN4
798 | SystemEvent::MapValueToStackN8
799 | SystemEvent::HasMapKey
800 | SystemEvent::Ext2Inv
801 | SystemEvent::U32Clz
802 | SystemEvent::U32Ctz
803 | SystemEvent::U32Clo
804 | SystemEvent::U32Cto
805 | SystemEvent::ILog2
806 | SystemEvent::MemToMap
807 | SystemEvent::HdwordToMap
808 | SystemEvent::HqwordToMap
809 | SystemEvent::CompressToMap
810 | SystemEvent::DeferredRegister
811 | SystemEvent::DeferredEvaluate
812 | SystemEvent::DeferredEvaluateFrame
813 | SystemEvent::DeferredEvaluatePayload
814 | SystemEvent::DeferredRegisterData
815 | SystemEvent::DeferredExportWitness
816 | SystemEvent::TraceEvent => {},
817 }
818 }
819 }
820
821 #[test]
822 fn compress_to_map_preserves_its_wire_identity() {
823 assert_eq!(SystemEvent::CompressToMap.event_id(), EventId::from_u64(454105713963103935));
824 assert_eq!(SystemEvent::CompressToMap.event_name().as_str(), "sys::bcompress_to_map");
825 }
826}