Skip to main content

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 word from the operand stack and inserts them into the advice map under the key
238    /// defined by the hash of these words.
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    /// Where KEY is computed as hash(A || B, domain=0).
249    HdwordToMap,
250
251    /// Reads two words from the operand stack and inserts them into the advice map under the key
252    /// defined by the hash of these words (using `d` as the domain).
253    ///
254    /// Inputs:
255    ///   Operand stack: [A, B, d, ...]
256    ///   Advice map: {...}
257    ///
258    /// Outputs:
259    ///   Operand stack: [A, B, d, ...]
260    ///   Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]}
261    ///
262    /// Where KEY is computed as hash(A || B, d).
263    HdwordToMapWithDomain,
264
265    /// Reads four words from the operand stack and inserts them into the advice map under the key
266    /// defined by the hash of these words.
267    ///
268    /// Inputs:
269    ///   Operand stack: [A, B, C, D, ...]
270    ///   Advice map: {...}
271    ///
272    /// Outputs:
273    ///   Operand stack: [A, B, C, D, ...]
274    ///   Advice map: {KEY: [A, B, C, D]} (16 elements)
275    ///
276    /// Where:
277    /// - KEY is computed as hash_elements([A, B, C, D]) using the sponge construction (sequential
278    ///   absorption; two rounds for four words).
279    HqwordToMap,
280
281    /// Reads three words from the operand stack and inserts the top two words into the advice map
282    /// under the key defined by applying a Poseidon2 permutation to all three words.
283    ///
284    /// Inputs:
285    ///   Operand stack: [A, B, C, ...]
286    ///   Advice map: {...}
287    ///
288    /// Outputs:
289    ///   Operand stack: [A, B, C, ...]
290    ///   Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]}
291    ///
292    /// Where KEY is computed by extracting the digest elements from hperm([C, A, B]). For example,
293    /// if C is [0, d, 0, 0], KEY will be set as hash(A || B, d).
294    HpermToMap,
295
296    // DEFERRED-DAG SYSTEM EVENTS
297    // --------------------------------------------------------------------------------------------
298    /// Registers and eagerly evaluates a deferred node whose full payload is on the operand stack.
299    ///
300    /// `TAG` is one word (4 field elements). `PAYLOAD_LO || PAYLOAD_HI` is eight field elements:
301    /// either one [`crate::deferred::DataChunk`], two child digests (`lhs || rhs`) for a join, or
302    /// one `lhs || rhs` pair for a pair-list node. Exact [`crate::deferred::Tag::CHUNKS`]
303    /// (`[2, 0, 0, 0]`) is framework-owned opaque data; malformed id-2 tags are rejected during tag
304    /// decode. The installed registry decodes `TAG` via
305    /// [`crate::deferred::DeferredState::decode`]; `TRUE` is not accepted by this event. Tags that
306    /// semantically require more data chunks or pairs are rejected during precompile-specific
307    /// evaluation. Registration is performed by [`crate::deferred::DeferredState::register`], so
308    /// semantic failures surface immediately.
309    ///
310    /// This event does not push advice or return the node digest. The stack arguments are visible
311    /// in the VM execution trace, but the host-side registration is not constrained by the event.
312    /// Assembly code that later relies on the digest must compute it inside the VM from the same
313    /// `TAG` and payload.
314    ///
315    /// Inputs:
316    ///   Operand stack: [event_id, PAYLOAD_LO, PAYLOAD_HI, TAG, ...]
317    ///
318    /// Outputs:
319    ///   Operand stack:  unchanged
320    ///   Advice stack:   unchanged
321    ///   Deferred state: node registered and semantically evaluated
322    DeferredRegister,
323
324    /// Evaluates a registered deferred node and pushes its canonical tag and payload as advice.
325    ///
326    /// `NODE_DIGEST` is one word (4 field elements) and must already be registered in deferred
327    /// state. The handler evaluates it with [`crate::deferred::DeferredState::evaluate_digest`],
328    /// fetches the canonical node, and pushes its tag followed by its payload to the advice stack.
329    ///
330    /// The tag is emitted first in advice-pop order so `adv_pushw adv_pushw adv_pushw` leaves
331    /// `[PAYLOAD_LO, PAYLOAD_HI, TAG, ...]` on the operand stack for a single 8-felt payload. Data
332    /// payloads push two words per 8-felt chunk in advice order `HIGH, LOW`, preserving canonical
333    /// chunk order. Join payloads use the same two-word LIFO convention, leaving
334    /// `[lhs, rhs, TAG, ...]`. `TRUE` pushes only `Tag::TRUE`. These felts are unbound host hints.
335    /// Before proof-relevant use, assembly code must relate them with VM instructions to values
336    /// established independently of that advice.
337    ///
338    /// Inputs:
339    ///   Operand stack: [event_id, NODE_DIGEST, ...]
340    ///
341    /// Outputs:
342    ///   Operand stack: unchanged
343    ///   Advice stack:  canonical tag, then canonical payload words for `adv_pushw` LIFO
344    /// consumption
345    DeferredEvaluate,
346
347    /// Evaluates a registered deferred node and pushes only its canonical tag as advice.
348    ///
349    /// `NODE_DIGEST` is one word (4 field elements) and must already be registered in deferred
350    /// state. `TRUE` pushes `Tag::TRUE`. The returned tag is an unbound host hint; before
351    /// proof-relevant use, assembly code must relate it with VM instructions to a value established
352    /// independently of that advice.
353    ///
354    /// Inputs:
355    ///   Operand stack: [event_id, NODE_DIGEST, ...]
356    ///
357    /// Outputs:
358    ///   Operand stack: unchanged
359    ///   Advice stack:  canonical tag only
360    DeferredEvaluateTag,
361
362    /// Evaluates a registered deferred node and pushes only its canonical payload as advice.
363    ///
364    /// This is the payload-only compatibility event. Data payloads push two words per 8-felt chunk
365    /// in advice order `HIGH, LOW` so `adv_pushw adv_pushw` leaves `[LOW, HIGH, ...]` on the
366    /// operand stack for that chunk. Chunks are emitted in canonical chunk order. Join payloads use
367    /// the same two-word LIFO convention, leaving `[lhs, rhs, ...]` after two `adv_pushw`s. `TRUE`
368    /// pushes no advice. These felts are unbound host hints. Before proof-relevant use, assembly
369    /// code must relate them with VM instructions to values established independently of that
370    /// advice.
371    ///
372    /// Inputs:
373    ///   Operand stack: [event_id, NODE_DIGEST, ...]
374    ///
375    /// Outputs:
376    ///   Operand stack: unchanged
377    ///   Advice stack:  canonical payload only, word-ordered for `adv_pushw` LIFO consumption
378    DeferredEvaluatePayload,
379
380    /// Registers and eagerly evaluates a memory-backed deferred node.
381    ///
382    /// `TAG` is one word (4 field elements), and the installed registry decodes it to determine the
383    /// memory-backed payload shape. The stack-supplied `ptr` and `n_chunks` are visible in the VM
384    /// execution trace and select the range `[ptr, ptr + 8 * n_chunks)`. The host reads `n_chunks`
385    /// 8-felt [`crate::deferred::DataChunk`] values from that range, but this event adds no AIR
386    /// constraint tying the registered contents to those memory cells.
387    ///
388    /// Exact [`crate::deferred::Tag::CHUNKS`] (`[2, 0, 0, 0]`) registers the chunks as
389    /// framework-owned opaque data, while other data tags remain precompile-owned. Malformed id-2
390    /// tags are rejected during tag decode. Pair-list tags interpret chunks as `lhs || rhs` pairs.
391    /// Join tags require `n_chunks == 1` and interpret the single chunk as `lhs || rhs`. `TRUE` is
392    /// not accepted. The handler performs a cheap budget pre-check before allocating or reading
393    /// memory, then delegates registration to [`crate::deferred::DeferredState::register`].
394    ///
395    /// This event does not push advice or return the node digest. A program that relies on the
396    /// registered node must compute its digest with VM instructions from the same `TAG` and ordered
397    /// chunk sequence. The `register_mem` MASM wrapper does this by applying a Poseidon2 linear
398    /// hash to the same range, with one absorption per chunk and `TAG` as the initial capacity
399    /// word. If the event and the VM hash different chunk sequences, the VM-computed digest
400    /// does not identify the host-registered node and cannot bind that registration into a
401    /// proof-relevant deferred claim.
402    ///
403    /// Inputs:
404    ///   Operand stack: [event_id, TAG, ptr, n_chunks, ...]
405    ///
406    /// Outputs:
407    ///   Operand stack:  unchanged
408    ///   Advice stack:   unchanged
409    ///   Deferred state: node registered and semantically evaluated
410    DeferredRegisterData,
411
412    // NON-MUTATING SYSTEM EVENTS
413    // --------------------------------------------------------------------------------------------
414    /// Signals an optional, read-only trace event to the host.
415    ///
416    /// Assembly programs emit trace events with `trace`, `trace.CONST`, or `trace.event("...")`.
417    /// When the underlying `emit` observes this system event ID at stack position 0, the VM
418    /// forwards the user trace event ID at stack position 1 to the host's trace handler. The
419    /// immediate form lowers to `push.<user_trace_id> push.<sys::trace_event> emit drop drop`.
420    /// Trace handlers can observe the processor state, but cannot mutate VM state or the advice
421    /// provider. If no handler is registered for the user trace event ID, the event is a no-op.
422    ///
423    /// Hosts are expected not to raise an error if they encounter a `user_trace_id` for which no
424    /// trace handler is registered.
425    ///
426    /// Inputs:
427    ///   Operand stack: [sys::trace_event, user_trace_id, ...]
428    ///
429    /// Outputs:
430    ///   Operand stack: unchanged
431    ///   Advice provider: unchanged
432    TraceEvent,
433}
434
435impl SystemEvent {
436    /// Attempts to convert an EventId into a SystemEvent by looking it up in the const table.
437    ///
438    /// Returns `Some(SystemEvent)` if the ID matches a known system event, `None` otherwise.
439    /// This uses a const lookup table with hardcoded EventIds, avoiding runtime hash computation.
440    pub const fn from_event_id(event_id: EventId) -> Option<Self> {
441        let lookup = Self::LOOKUP;
442        let mut i = 0;
443        while i < lookup.len() {
444            if lookup[i].id.as_u64() == event_id.as_u64() {
445                return Some(lookup[i].event);
446            }
447            i += 1;
448        }
449        None
450    }
451
452    /// Attempts to convert a name into a SystemEvent by looking it up in the const table.
453    ///
454    /// Returns `Some(SystemEvent)` if the name matches a known system event, `None` otherwise.
455    /// This uses const string comparison against the lookup table.
456    pub const fn from_name(name: &str) -> Option<Self> {
457        let lookup = Self::LOOKUP;
458        let mut i = 0;
459        while i < lookup.len() {
460            if str_eq(name, lookup[i].name) {
461                return Some(lookup[i].event);
462            }
463            i += 1;
464        }
465        None
466    }
467
468    /// Returns the human-readable name of this system event as an [`EventName`].
469    ///
470    /// System event names are prefixed with `sys::` to distinguish them from user-defined events.
471    pub const fn event_name(&self) -> EventName {
472        EventName::new(Self::LOOKUP[*self as usize].name)
473    }
474
475    /// Returns the [`EventId`] for this system event.
476    ///
477    /// The ID is looked up from the const LOOKUP table using the enum's discriminant
478    /// as the index. The discriminants are explicitly set to match the array indices.
479    pub const fn event_id(&self) -> EventId {
480        Self::LOOKUP[*self as usize].id
481    }
482
483    /// Returns an array of all system event variants.
484    pub const fn all() -> [Self; Self::COUNT] {
485        [
486            Self::MerkleNodeMerge,
487            Self::MerkleNodeToStack,
488            Self::MapValueToStack,
489            Self::MapValueCountToStack,
490            Self::MapValueToStackN0,
491            Self::MapValueToStackN4,
492            Self::MapValueToStackN8,
493            Self::HasMapKey,
494            Self::Ext2Inv,
495            Self::U32Clz,
496            Self::U32Ctz,
497            Self::U32Clo,
498            Self::U32Cto,
499            Self::ILog2,
500            Self::MemToMap,
501            Self::HdwordToMap,
502            Self::HdwordToMapWithDomain,
503            Self::HqwordToMap,
504            Self::HpermToMap,
505            Self::DeferredRegister,
506            Self::DeferredEvaluate,
507            Self::DeferredEvaluateTag,
508            Self::DeferredEvaluatePayload,
509            Self::DeferredRegisterData,
510            Self::TraceEvent,
511        ]
512    }
513}
514
515impl From<SystemEvent> for EventName {
516    fn from(system_event: SystemEvent) -> Self {
517        system_event.event_name()
518    }
519}
520
521impl crate::prettier::PrettyPrint for SystemEvent {
522    fn render(&self) -> crate::prettier::Document {
523        crate::prettier::display(self)
524    }
525}
526
527impl fmt::Display for SystemEvent {
528    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529        const PREFIX_LEN: usize = "sys::".len();
530
531        let (_prefix, rest) = Self::LOOKUP[*self as usize].name.split_at(PREFIX_LEN);
532        write!(f, "{rest}")
533    }
534}
535
536// LOOKUP TABLE
537// ================================================================================================
538
539/// An entry in the system event lookup table, containing all metadata for a system event.
540#[derive(Copy, Clone, Debug)]
541pub(crate) struct SystemEventEntry {
542    /// The unique event ID (hash of the name)
543    pub id: EventId,
544    /// The system event variant
545    pub event: SystemEvent,
546    /// The full event name string (e.g., "sys::merkle_node_merge")
547    pub name: &'static str,
548}
549
550impl SystemEvent {
551    /// The total number of system events.
552    pub const COUNT: usize = 25;
553
554    /// Lookup table mapping system events to their metadata.
555    ///
556    /// The enum variant order matches the indices in this table, allowing efficient const
557    /// lookup via array indexing using discriminants.
558    const LOOKUP: [SystemEventEntry; Self::COUNT] = [
559        SystemEventEntry {
560            id: EventId::from_u64(7243907139105902342),
561            event: SystemEvent::MerkleNodeMerge,
562            name: "sys::merkle_node_merge",
563        },
564        SystemEventEntry {
565            id: EventId::from_u64(6873007751276594108),
566            event: SystemEvent::MerkleNodeToStack,
567            name: "sys::merkle_node_to_stack",
568        },
569        SystemEventEntry {
570            id: EventId::from_u64(17843484659000820118),
571            event: SystemEvent::MapValueToStack,
572            name: "sys::map_value_to_stack",
573        },
574        SystemEventEntry {
575            id: EventId::from_u64(3470274154276391308),
576            event: SystemEvent::MapValueCountToStack,
577            name: "sys::map_value_count_to_stack",
578        },
579        SystemEventEntry {
580            id: EventId::from_u64(11775886982554463322),
581            event: SystemEvent::MapValueToStackN0,
582            name: "sys::map_value_to_stack_n_0",
583        },
584        SystemEventEntry {
585            id: EventId::from_u64(3443305460233942990),
586            event: SystemEvent::MapValueToStackN4,
587            name: "sys::map_value_to_stack_n_4",
588        },
589        SystemEventEntry {
590            id: EventId::from_u64(1741586542981559489),
591            event: SystemEvent::MapValueToStackN8,
592            name: "sys::map_value_to_stack_n_8",
593        },
594        SystemEventEntry {
595            id: EventId::from_u64(5642583036089175977),
596            event: SystemEvent::HasMapKey,
597            name: "sys::has_map_key",
598        },
599        SystemEventEntry {
600            id: EventId::from_u64(9660728691489438960),
601            event: SystemEvent::Ext2Inv,
602            name: "sys::ext2_inv",
603        },
604        SystemEventEntry {
605            id: EventId::from_u64(1503707361178382932),
606            event: SystemEvent::U32Clz,
607            name: "sys::u32_clz",
608        },
609        SystemEventEntry {
610            id: EventId::from_u64(10656887096526143429),
611            event: SystemEvent::U32Ctz,
612            name: "sys::u32_ctz",
613        },
614        SystemEventEntry {
615            id: EventId::from_u64(12846584985739176048),
616            event: SystemEvent::U32Clo,
617            name: "sys::u32_clo",
618        },
619        SystemEventEntry {
620            id: EventId::from_u64(6773574803673468616),
621            event: SystemEvent::U32Cto,
622            name: "sys::u32_cto",
623        },
624        SystemEventEntry {
625            id: EventId::from_u64(7444351342957461231),
626            event: SystemEvent::ILog2,
627            name: "sys::ilog2",
628        },
629        SystemEventEntry {
630            id: EventId::from_u64(5768534446586058686),
631            event: SystemEvent::MemToMap,
632            name: "sys::mem_to_map",
633        },
634        SystemEventEntry {
635            id: EventId::from_u64(5988159172915333521),
636            event: SystemEvent::HdwordToMap,
637            name: "sys::hdword_to_map",
638        },
639        SystemEventEntry {
640            id: EventId::from_u64(6143777601072385586),
641            event: SystemEvent::HdwordToMapWithDomain,
642            name: "sys::hdword_to_map_with_domain",
643        },
644        SystemEventEntry {
645            id: EventId::from_u64(11723176702659679401),
646            event: SystemEvent::HqwordToMap,
647            name: "sys::hqword_to_map",
648        },
649        SystemEventEntry {
650            id: EventId::from_u64(6190830263511605775),
651            event: SystemEvent::HpermToMap,
652            name: "sys::hperm_to_map",
653        },
654        SystemEventEntry {
655            id: EventId::from_u64(3200266522440553751),
656            event: SystemEvent::DeferredRegister,
657            name: "sys::adv::register_deferred",
658        },
659        SystemEventEntry {
660            id: EventId::from_u64(12566028600487412345),
661            event: SystemEvent::DeferredEvaluate,
662            name: "sys::adv::evaluate_deferred",
663        },
664        SystemEventEntry {
665            id: EventId::from_u64(15463062559264590613),
666            event: SystemEvent::DeferredEvaluateTag,
667            name: "sys::adv::evaluate_deferred_tag",
668        },
669        SystemEventEntry {
670            id: EventId::from_u64(8091749904895009326),
671            event: SystemEvent::DeferredEvaluatePayload,
672            name: "sys::adv::evaluate_deferred_payload",
673        },
674        SystemEventEntry {
675            id: EventId::from_u64(13021247594355482329),
676            event: SystemEvent::DeferredRegisterData,
677            name: "sys::adv::register_deferred_data",
678        },
679        SystemEventEntry {
680            id: EventId::from_u64(1768618069850226410),
681            event: SystemEvent::TraceEvent,
682            name: "sys::trace_event",
683        },
684    ];
685}
686
687// HELPERS
688// ================================================================================================
689
690/// Const-compatible string equality check.
691const fn str_eq(a: &str, b: &str) -> bool {
692    let a_bytes = a.as_bytes();
693    let b_bytes = b.as_bytes();
694
695    if a_bytes.len() != b_bytes.len() {
696        return false;
697    }
698
699    let mut i = 0;
700    while i < a_bytes.len() {
701        if a_bytes[i] != b_bytes[i] {
702            return false;
703        }
704        i += 1;
705    }
706    true
707}
708
709#[cfg(test)]
710mod test {
711    use super::*;
712
713    #[test]
714    fn test_system_events() {
715        // Comprehensive test verifying consistency between SystemEvent::all() and
716        // SystemEvent::LOOKUP. This ensures all() and LOOKUP are in sync, lookup table has
717        // correct IDs/names, and all variants are covered.
718
719        // Verify lengths match COUNT
720        assert_eq!(SystemEvent::all().len(), SystemEvent::COUNT);
721        assert_eq!(SystemEvent::LOOKUP.len(), SystemEvent::COUNT);
722
723        // Iterate through both all() and LOOKUP together, checking all invariants
724        for (i, (event, entry)) in
725            SystemEvent::all().iter().zip(SystemEvent::LOOKUP.iter()).enumerate()
726        {
727            // Verify LOOKUP entry matches the event at the same index
728            assert_eq!(
729                entry.event, *event,
730                "LOOKUP[{}].event ({:?}) doesn't match all()[{}] ({:?})",
731                i, entry.event, i, event
732            );
733
734            // Verify LOOKUP entry ID matches enum lookup.
735            let looked_up_id = event.event_id();
736            assert_eq!(
737                entry.id,
738                looked_up_id,
739                "LOOKUP[{}].id is EventId::from_u64({}), but {:?}.event_id() returns EventId::from_u64({})",
740                i,
741                entry.id.as_u64(),
742                event,
743                looked_up_id.as_u64()
744            );
745
746            // Verify name has correct "sys::" prefix
747            assert!(
748                entry.name.starts_with("sys::"),
749                "SystemEvent name should start with 'sys::': {}",
750                entry.name
751            );
752
753            // Verify from_event_id lookup works
754            let looked_up =
755                SystemEvent::from_event_id(entry.id).expect("SystemEvent should be found by ID");
756            assert_eq!(looked_up, *event);
757
758            // Verify from_name lookup works
759            let looked_up_by_name =
760                SystemEvent::from_name(entry.name).expect("SystemEvent should be found by name");
761            assert_eq!(looked_up_by_name, *event);
762
763            // Verify EventName conversion works
764            let event_name = event.event_name();
765            assert_eq!(event_name.as_str(), entry.name);
766            assert!(SystemEvent::from_name(event_name.as_str()).is_some());
767            let event_name_from_into: EventName = (*event).into();
768            assert_eq!(event_name_from_into.as_str(), entry.name);
769            assert!(SystemEvent::from_name(event_name_from_into.as_str()).is_some());
770
771            // Exhaustive match to ensure compile-time error when adding new variants
772            match event {
773                SystemEvent::MerkleNodeMerge
774                | SystemEvent::MerkleNodeToStack
775                | SystemEvent::MapValueToStack
776                | SystemEvent::MapValueCountToStack
777                | SystemEvent::MapValueToStackN0
778                | SystemEvent::MapValueToStackN4
779                | SystemEvent::MapValueToStackN8
780                | SystemEvent::HasMapKey
781                | SystemEvent::Ext2Inv
782                | SystemEvent::U32Clz
783                | SystemEvent::U32Ctz
784                | SystemEvent::U32Clo
785                | SystemEvent::U32Cto
786                | SystemEvent::ILog2
787                | SystemEvent::MemToMap
788                | SystemEvent::HdwordToMap
789                | SystemEvent::HdwordToMapWithDomain
790                | SystemEvent::HqwordToMap
791                | SystemEvent::HpermToMap
792                | SystemEvent::DeferredRegister
793                | SystemEvent::DeferredEvaluate
794                | SystemEvent::DeferredEvaluateTag
795                | SystemEvent::DeferredEvaluatePayload
796                | SystemEvent::DeferredRegisterData
797                | SystemEvent::TraceEvent => {},
798            }
799        }
800    }
801}