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 /// When `emit` observes this system event ID at stack position 0, the VM forwards the user
417 /// trace event ID at stack position 1 to the host's trace handler. This is typically emitted
418 /// as `push.<user_trace_id> push.<sys::trace_event> emit`. Trace handlers can observe
419 /// the processor state, but cannot mutate VM state or the advice provider. If no handler is
420 /// registered for the user trace event ID, the event is a no-op.
421 ///
422 /// Hosts are expected to not raise an error if they encounter a `user_trace_id` for which no
423 /// trace handler is registered.
424 ///
425 /// Inputs:
426 /// Operand stack: [sys::trace_event, user_trace_id, ...]
427 ///
428 /// Outputs:
429 /// Operand stack: unchanged
430 /// Advice provider: unchanged
431 TraceEvent,
432}
433
434impl SystemEvent {
435 /// Attempts to convert an EventId into a SystemEvent by looking it up in the const table.
436 ///
437 /// Returns `Some(SystemEvent)` if the ID matches a known system event, `None` otherwise.
438 /// This uses a const lookup table with hardcoded EventIds, avoiding runtime hash computation.
439 pub const fn from_event_id(event_id: EventId) -> Option<Self> {
440 let lookup = Self::LOOKUP;
441 let mut i = 0;
442 while i < lookup.len() {
443 if lookup[i].id.as_u64() == event_id.as_u64() {
444 return Some(lookup[i].event);
445 }
446 i += 1;
447 }
448 None
449 }
450
451 /// Attempts to convert a name into a SystemEvent by looking it up in the const table.
452 ///
453 /// Returns `Some(SystemEvent)` if the name matches a known system event, `None` otherwise.
454 /// This uses const string comparison against the lookup table.
455 pub const fn from_name(name: &str) -> Option<Self> {
456 let lookup = Self::LOOKUP;
457 let mut i = 0;
458 while i < lookup.len() {
459 if str_eq(name, lookup[i].name) {
460 return Some(lookup[i].event);
461 }
462 i += 1;
463 }
464 None
465 }
466
467 /// Returns the human-readable name of this system event as an [`EventName`].
468 ///
469 /// System event names are prefixed with `sys::` to distinguish them from user-defined events.
470 pub const fn event_name(&self) -> EventName {
471 EventName::new(Self::LOOKUP[*self as usize].name)
472 }
473
474 /// Returns the [`EventId`] for this system event.
475 ///
476 /// The ID is looked up from the const LOOKUP table using the enum's discriminant
477 /// as the index. The discriminants are explicitly set to match the array indices.
478 pub const fn event_id(&self) -> EventId {
479 Self::LOOKUP[*self as usize].id
480 }
481
482 /// Returns an array of all system event variants.
483 pub const fn all() -> [Self; Self::COUNT] {
484 [
485 Self::MerkleNodeMerge,
486 Self::MerkleNodeToStack,
487 Self::MapValueToStack,
488 Self::MapValueCountToStack,
489 Self::MapValueToStackN0,
490 Self::MapValueToStackN4,
491 Self::MapValueToStackN8,
492 Self::HasMapKey,
493 Self::Ext2Inv,
494 Self::U32Clz,
495 Self::U32Ctz,
496 Self::U32Clo,
497 Self::U32Cto,
498 Self::ILog2,
499 Self::MemToMap,
500 Self::HdwordToMap,
501 Self::HdwordToMapWithDomain,
502 Self::HqwordToMap,
503 Self::HpermToMap,
504 Self::DeferredRegister,
505 Self::DeferredEvaluate,
506 Self::DeferredEvaluateTag,
507 Self::DeferredEvaluatePayload,
508 Self::DeferredRegisterData,
509 Self::TraceEvent,
510 ]
511 }
512}
513
514impl From<SystemEvent> for EventName {
515 fn from(system_event: SystemEvent) -> Self {
516 system_event.event_name()
517 }
518}
519
520impl crate::prettier::PrettyPrint for SystemEvent {
521 fn render(&self) -> crate::prettier::Document {
522 crate::prettier::display(self)
523 }
524}
525
526impl fmt::Display for SystemEvent {
527 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528 const PREFIX_LEN: usize = "sys::".len();
529
530 let (_prefix, rest) = Self::LOOKUP[*self as usize].name.split_at(PREFIX_LEN);
531 write!(f, "{rest}")
532 }
533}
534
535// LOOKUP TABLE
536// ================================================================================================
537
538/// An entry in the system event lookup table, containing all metadata for a system event.
539#[derive(Copy, Clone, Debug)]
540pub(crate) struct SystemEventEntry {
541 /// The unique event ID (hash of the name)
542 pub id: EventId,
543 /// The system event variant
544 pub event: SystemEvent,
545 /// The full event name string (e.g., "sys::merkle_node_merge")
546 pub name: &'static str,
547}
548
549impl SystemEvent {
550 /// The total number of system events.
551 pub const COUNT: usize = 25;
552
553 /// Lookup table mapping system events to their metadata.
554 ///
555 /// The enum variant order matches the indices in this table, allowing efficient const
556 /// lookup via array indexing using discriminants.
557 const LOOKUP: [SystemEventEntry; Self::COUNT] = [
558 SystemEventEntry {
559 id: EventId::from_u64(7243907139105902342),
560 event: SystemEvent::MerkleNodeMerge,
561 name: "sys::merkle_node_merge",
562 },
563 SystemEventEntry {
564 id: EventId::from_u64(6873007751276594108),
565 event: SystemEvent::MerkleNodeToStack,
566 name: "sys::merkle_node_to_stack",
567 },
568 SystemEventEntry {
569 id: EventId::from_u64(17843484659000820118),
570 event: SystemEvent::MapValueToStack,
571 name: "sys::map_value_to_stack",
572 },
573 SystemEventEntry {
574 id: EventId::from_u64(3470274154276391308),
575 event: SystemEvent::MapValueCountToStack,
576 name: "sys::map_value_count_to_stack",
577 },
578 SystemEventEntry {
579 id: EventId::from_u64(11775886982554463322),
580 event: SystemEvent::MapValueToStackN0,
581 name: "sys::map_value_to_stack_n_0",
582 },
583 SystemEventEntry {
584 id: EventId::from_u64(3443305460233942990),
585 event: SystemEvent::MapValueToStackN4,
586 name: "sys::map_value_to_stack_n_4",
587 },
588 SystemEventEntry {
589 id: EventId::from_u64(1741586542981559489),
590 event: SystemEvent::MapValueToStackN8,
591 name: "sys::map_value_to_stack_n_8",
592 },
593 SystemEventEntry {
594 id: EventId::from_u64(5642583036089175977),
595 event: SystemEvent::HasMapKey,
596 name: "sys::has_map_key",
597 },
598 SystemEventEntry {
599 id: EventId::from_u64(9660728691489438960),
600 event: SystemEvent::Ext2Inv,
601 name: "sys::ext2_inv",
602 },
603 SystemEventEntry {
604 id: EventId::from_u64(1503707361178382932),
605 event: SystemEvent::U32Clz,
606 name: "sys::u32_clz",
607 },
608 SystemEventEntry {
609 id: EventId::from_u64(10656887096526143429),
610 event: SystemEvent::U32Ctz,
611 name: "sys::u32_ctz",
612 },
613 SystemEventEntry {
614 id: EventId::from_u64(12846584985739176048),
615 event: SystemEvent::U32Clo,
616 name: "sys::u32_clo",
617 },
618 SystemEventEntry {
619 id: EventId::from_u64(6773574803673468616),
620 event: SystemEvent::U32Cto,
621 name: "sys::u32_cto",
622 },
623 SystemEventEntry {
624 id: EventId::from_u64(7444351342957461231),
625 event: SystemEvent::ILog2,
626 name: "sys::ilog2",
627 },
628 SystemEventEntry {
629 id: EventId::from_u64(5768534446586058686),
630 event: SystemEvent::MemToMap,
631 name: "sys::mem_to_map",
632 },
633 SystemEventEntry {
634 id: EventId::from_u64(5988159172915333521),
635 event: SystemEvent::HdwordToMap,
636 name: "sys::hdword_to_map",
637 },
638 SystemEventEntry {
639 id: EventId::from_u64(6143777601072385586),
640 event: SystemEvent::HdwordToMapWithDomain,
641 name: "sys::hdword_to_map_with_domain",
642 },
643 SystemEventEntry {
644 id: EventId::from_u64(11723176702659679401),
645 event: SystemEvent::HqwordToMap,
646 name: "sys::hqword_to_map",
647 },
648 SystemEventEntry {
649 id: EventId::from_u64(6190830263511605775),
650 event: SystemEvent::HpermToMap,
651 name: "sys::hperm_to_map",
652 },
653 SystemEventEntry {
654 id: EventId::from_u64(3200266522440553751),
655 event: SystemEvent::DeferredRegister,
656 name: "sys::adv::register_deferred",
657 },
658 SystemEventEntry {
659 id: EventId::from_u64(12566028600487412345),
660 event: SystemEvent::DeferredEvaluate,
661 name: "sys::adv::evaluate_deferred",
662 },
663 SystemEventEntry {
664 id: EventId::from_u64(15463062559264590613),
665 event: SystemEvent::DeferredEvaluateTag,
666 name: "sys::adv::evaluate_deferred_tag",
667 },
668 SystemEventEntry {
669 id: EventId::from_u64(8091749904895009326),
670 event: SystemEvent::DeferredEvaluatePayload,
671 name: "sys::adv::evaluate_deferred_payload",
672 },
673 SystemEventEntry {
674 id: EventId::from_u64(13021247594355482329),
675 event: SystemEvent::DeferredRegisterData,
676 name: "sys::adv::register_deferred_data",
677 },
678 SystemEventEntry {
679 id: EventId::from_u64(1768618069850226410),
680 event: SystemEvent::TraceEvent,
681 name: "sys::trace_event",
682 },
683 ];
684}
685
686// HELPERS
687// ================================================================================================
688
689/// Const-compatible string equality check.
690const fn str_eq(a: &str, b: &str) -> bool {
691 let a_bytes = a.as_bytes();
692 let b_bytes = b.as_bytes();
693
694 if a_bytes.len() != b_bytes.len() {
695 return false;
696 }
697
698 let mut i = 0;
699 while i < a_bytes.len() {
700 if a_bytes[i] != b_bytes[i] {
701 return false;
702 }
703 i += 1;
704 }
705 true
706}
707
708#[cfg(test)]
709mod test {
710 use super::*;
711
712 #[test]
713 fn test_system_events() {
714 // Comprehensive test verifying consistency between SystemEvent::all() and
715 // SystemEvent::LOOKUP. This ensures all() and LOOKUP are in sync, lookup table has
716 // correct IDs/names, and all variants are covered.
717
718 // Verify lengths match COUNT
719 assert_eq!(SystemEvent::all().len(), SystemEvent::COUNT);
720 assert_eq!(SystemEvent::LOOKUP.len(), SystemEvent::COUNT);
721
722 // Iterate through both all() and LOOKUP together, checking all invariants
723 for (i, (event, entry)) in
724 SystemEvent::all().iter().zip(SystemEvent::LOOKUP.iter()).enumerate()
725 {
726 // Verify LOOKUP entry matches the event at the same index
727 assert_eq!(
728 entry.event, *event,
729 "LOOKUP[{}].event ({:?}) doesn't match all()[{}] ({:?})",
730 i, entry.event, i, event
731 );
732
733 // Verify LOOKUP entry ID matches enum lookup.
734 let looked_up_id = event.event_id();
735 assert_eq!(
736 entry.id,
737 looked_up_id,
738 "LOOKUP[{}].id is EventId::from_u64({}), but {:?}.event_id() returns EventId::from_u64({})",
739 i,
740 entry.id.as_u64(),
741 event,
742 looked_up_id.as_u64()
743 );
744
745 // Verify name has correct "sys::" prefix
746 assert!(
747 entry.name.starts_with("sys::"),
748 "SystemEvent name should start with 'sys::': {}",
749 entry.name
750 );
751
752 // Verify from_event_id lookup works
753 let looked_up =
754 SystemEvent::from_event_id(entry.id).expect("SystemEvent should be found by ID");
755 assert_eq!(looked_up, *event);
756
757 // Verify from_name lookup works
758 let looked_up_by_name =
759 SystemEvent::from_name(entry.name).expect("SystemEvent should be found by name");
760 assert_eq!(looked_up_by_name, *event);
761
762 // Verify EventName conversion works
763 let event_name = event.event_name();
764 assert_eq!(event_name.as_str(), entry.name);
765 assert!(SystemEvent::from_name(event_name.as_str()).is_some());
766 let event_name_from_into: EventName = (*event).into();
767 assert_eq!(event_name_from_into.as_str(), entry.name);
768 assert!(SystemEvent::from_name(event_name_from_into.as_str()).is_some());
769
770 // Exhaustive match to ensure compile-time error when adding new variants
771 match event {
772 SystemEvent::MerkleNodeMerge
773 | SystemEvent::MerkleNodeToStack
774 | SystemEvent::MapValueToStack
775 | SystemEvent::MapValueCountToStack
776 | SystemEvent::MapValueToStackN0
777 | SystemEvent::MapValueToStackN4
778 | SystemEvent::MapValueToStackN8
779 | SystemEvent::HasMapKey
780 | SystemEvent::Ext2Inv
781 | SystemEvent::U32Clz
782 | SystemEvent::U32Ctz
783 | SystemEvent::U32Clo
784 | SystemEvent::U32Cto
785 | SystemEvent::ILog2
786 | SystemEvent::MemToMap
787 | SystemEvent::HdwordToMap
788 | SystemEvent::HdwordToMapWithDomain
789 | SystemEvent::HqwordToMap
790 | SystemEvent::HpermToMap
791 | SystemEvent::DeferredRegister
792 | SystemEvent::DeferredEvaluate
793 | SystemEvent::DeferredEvaluateTag
794 | SystemEvent::DeferredEvaluatePayload
795 | SystemEvent::DeferredRegisterData
796 | SystemEvent::TraceEvent => {},
797 }
798 }
799 }
800}