miden_core/mast/mod.rs
1//! MAST forest: a collection of procedures represented as Merkle trees.
2//!
3//! # Deserializing from untrusted sources
4//!
5//! When loading a `MastForest` from bytes you don't fully trust (network, user upload, etc.),
6//! use [`UntrustedMastForest`] instead of calling `MastForest::read_from_bytes` directly:
7//!
8//! ```ignore
9//! use miden_core::mast::UntrustedMastForest;
10//!
11//! let forest = UntrustedMastForest::read_from_bytes(&bytes)?
12//! .validate()?;
13//! ```
14//!
15//! [`UntrustedMastForest::read_from_bytes`] applies default parsing and validation budgets derived
16//! from the input size. Use [`UntrustedMastForest::read_from_bytes_with_budget`] to tune only the
17//! wire-parsing budget, or [`UntrustedMastForest::read_from_bytes_with_budgets`] to tune both:
18//! the parsing budget limits allocations driven directly by wire counts while reading the payload,
19//! and the validation budget limits later helper allocations needed to materialize and check
20//! stripped or hashless payloads.
21//!
22//! ```ignore
23//! use miden_core::mast::UntrustedMastForest;
24//!
25//! // Parsing budget only
26//! let forest = UntrustedMastForest::read_from_bytes_with_budget(&bytes, bytes.len())?
27//! .validate()?;
28//!
29//! // Parsing budget plus explicit validation-allocation budget
30//! let forest = UntrustedMastForest::read_from_bytes_with_budgets(&bytes, bytes.len(), bytes.len() * 7)?
31//! .validate()?;
32//! ```
33//!
34//! This recomputes all node hashes and checks structural invariants before returning a usable
35//! `MastForest`. Direct deserialization via `MastForest::read_from_bytes` trusts the serialized
36//! hashes and should only be used for data from trusted sources (e.g. compiled locally).
37//!
38//! In practice, the public entry points split into three policies:
39//! - [`MastForest::read_from_bytes`]: trusted full deserialization; rejects hashless payloads and
40//! trusts serialized non-external digests.
41//! - [`SerializedMastForest::new`]: structural inspection path for local tooling; scans only the
42//! layout needed for random access and may accept full, stripped, or hashless payloads, but it is
43//! not an untrusted-validation entry point.
44//! - [`UntrustedMastForest::read_from_bytes`] and
45//! [`UntrustedMastForest::read_from_bytes_with_budgets`]: untrusted paths; parse with bounded
46//! readers and require [`UntrustedMastForest::validate`] before use.
47
48use alloc::{
49 collections::{BTreeMap, BTreeSet},
50 string::String,
51 sync::Arc,
52 vec::Vec,
53};
54use core::{
55 fmt,
56 ops::{Index, IndexMut},
57};
58
59use miden_utils_sync::OnceLockCompat;
60#[cfg(any(test, feature = "arbitrary"))]
61use proptest::prelude::*;
62#[cfg(feature = "serde")]
63use serde::{Deserialize, Serialize};
64
65mod node;
66#[cfg(any(test, feature = "arbitrary"))]
67pub use node::arbitrary;
68pub(crate) use node::collect_immediate_placements;
69pub use node::{
70 BasicBlockNode, BasicBlockNodeBuilder, CallNode, CallNodeBuilder, DecoratedOpLink,
71 DecoratorOpLinkIterator, DecoratorStore, DynNode, DynNodeBuilder, ExternalNode,
72 ExternalNodeBuilder, JoinNode, JoinNodeBuilder, LoopNode, LoopNodeBuilder,
73 MastForestContributor, MastNode, MastNodeBuilder, MastNodeExt, OP_BATCH_SIZE, OP_GROUP_SIZE,
74 OpBatch, OperationOrDecorator, SplitNode, SplitNodeBuilder,
75};
76
77#[cfg(feature = "serde")]
78use crate::serde::SliceReader;
79use crate::{
80 Felt, Word,
81 advice::AdviceMap,
82 operations::{AssemblyOp, DebugVarInfo, Decorator},
83 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
84 utils::{Idx, IndexVec, hash_string_to_word},
85};
86
87mod debuginfo;
88pub use debuginfo::{
89 AsmOpIndexError, DebugInfo, DebugVarId, DecoratedLinks, DecoratedLinksIter,
90 DecoratorIndexError, NodeToDecoratorIds, OpToAsmOpId, OpToDebugVarIds, OpToDecoratorIds,
91};
92
93mod serialization;
94pub use serialization::{MastForestView, MastNodeEntry, MastNodeInfo, SerializedMastForest};
95
96mod untrusted;
97pub use untrusted::UntrustedMastForest;
98
99mod merger;
100pub(crate) use merger::MastForestMerger;
101pub use merger::MastForestRootMap;
102
103mod multi_forest_node_iterator;
104pub(crate) use multi_forest_node_iterator::*;
105
106mod node_fingerprint;
107pub use node_fingerprint::{DecoratorFingerprint, MastNodeFingerprint};
108
109mod node_builder_utils;
110pub use node_builder_utils::build_node_with_remapped_ids;
111
112#[cfg(test)]
113mod tests;
114
115// MAST FOREST
116// ================================================================================================
117
118/// Represents one or more procedures, represented as a collection of [`MastNode`]s.
119///
120/// A [`MastForest`] does not have an entrypoint, and hence is not executable. A
121/// [`crate::program::Program`] can be built from a [`MastForest`] to specify an entrypoint.
122#[derive(Clone, Debug, Default)]
123#[cfg_attr(
124 all(feature = "arbitrary", test),
125 miden_test_serde_macros::serde_test(binary_serde(true))
126)]
127pub struct MastForest {
128 /// All of the nodes local to the trees comprising the MAST forest.
129 nodes: IndexVec<MastNodeId, MastNode>,
130
131 /// Roots of procedures defined within this MAST forest.
132 roots: Vec<MastNodeId>,
133
134 /// Advice map to be loaded into the VM prior to executing procedures from this MAST forest.
135 advice_map: AdviceMap,
136
137 /// Debug information including decorators and error codes.
138 /// Always present (as per issue #1821), but can be empty for stripped builds.
139 debug_info: DebugInfo,
140
141 /// Cached commitment to this MAST forest (commitment to all roots).
142 /// This is computed lazily on first access and invalidated on any mutation.
143 commitment_cache: OnceLockCompat<Word>,
144}
145
146// ------------------------------------------------------------------------------------------------
147/// Constructors
148impl MastForest {
149 /// Creates a new empty [`MastForest`].
150 pub fn new() -> Self {
151 Self {
152 nodes: IndexVec::new(),
153 roots: Vec::new(),
154 advice_map: AdviceMap::default(),
155 debug_info: DebugInfo::new(),
156 commitment_cache: OnceLockCompat::new(),
157 }
158 }
159}
160
161// ------------------------------------------------------------------------------------------------
162/// Equality implementations
163impl PartialEq for MastForest {
164 fn eq(&self, other: &Self) -> bool {
165 // Compare all fields except commitment_cache, which is derived data
166 self.nodes == other.nodes
167 && self.roots == other.roots
168 && self.advice_map == other.advice_map
169 && self.debug_info == other.debug_info
170 }
171}
172
173impl Eq for MastForest {}
174
175// ------------------------------------------------------------------------------------------------
176/// State mutators
177impl MastForest {
178 /// The maximum number of nodes that can be stored in a single MAST forest.
179 const MAX_NODES: usize = (1 << 30) - 1;
180
181 /// Marks the given [`MastNodeId`] as being the root of a procedure.
182 ///
183 /// If the specified node is already marked as a root, this will have no effect.
184 ///
185 /// # Panics
186 /// - if `new_root_id`'s internal index is larger than the number of nodes in this forest (i.e.
187 /// clearly doesn't belong to this MAST forest).
188 pub fn make_root(&mut self, new_root_id: MastNodeId) {
189 assert!(new_root_id.to_usize() < self.nodes.len());
190
191 if !self.roots.contains(&new_root_id) {
192 self.roots.push(new_root_id);
193 // Invalidate the cached commitment since we modified the roots
194 self.commitment_cache.take();
195 }
196 }
197
198 /// Removes all nodes in the provided set from the MAST forest. The nodes MUST be orphaned (i.e.
199 /// have no parent). Otherwise, this parent's reference is considered "dangling" after the
200 /// removal (i.e. will point to an incorrect node after the removal), and this removal operation
201 /// would result in an invalid [`MastForest`].
202 ///
203 /// It also returns the map from old node IDs to new node IDs. Any [`MastNodeId`] used in
204 /// reference to the old [`MastForest`] should be remapped using this map.
205 pub fn remove_nodes(
206 &mut self,
207 nodes_to_remove: &BTreeSet<MastNodeId>,
208 ) -> BTreeMap<MastNodeId, MastNodeId> {
209 if nodes_to_remove.is_empty() {
210 return BTreeMap::new();
211 }
212
213 let old_nodes = core::mem::replace(&mut self.nodes, IndexVec::new());
214 let old_root_ids = core::mem::take(&mut self.roots);
215 let (retained_nodes, id_remappings) = remove_nodes(old_nodes.into_inner(), nodes_to_remove);
216
217 self.remap_and_add_nodes(retained_nodes, &id_remappings);
218 self.remap_and_add_roots(old_root_ids, &id_remappings);
219
220 // Remap the asm_op_storage and debug_var_storage to use the new node IDs
221 self.debug_info.remap_asm_op_storage(&id_remappings);
222 self.debug_info.remap_debug_var_storage(&id_remappings);
223
224 // Invalidate the cached commitment since we modified the forest structure
225 self.commitment_cache.take();
226
227 id_remappings
228 }
229
230 /// Clears all [`DebugInfo`] from this forest: decorators, error codes, and procedure names.
231 ///
232 /// ```
233 /// # use miden_core::mast::MastForest;
234 /// let mut forest = MastForest::new();
235 /// forest.clear_debug_info();
236 /// assert!(forest.decorators().is_empty());
237 /// ```
238 pub fn clear_debug_info(&mut self) {
239 self.debug_info = DebugInfo::empty_for_nodes(self.nodes.len());
240 }
241
242 /// Compacts the forest by merging duplicate nodes.
243 ///
244 /// This operation performs node deduplication by merging the forest with itself.
245 /// The method assumes that debug info has already been cleared if that is desired.
246 /// This method consumes the forest and returns a new compacted forest.
247 ///
248 /// The process works by:
249 /// 1. Merging the forest with itself to deduplicate identical nodes
250 /// 2. Updating internal node references and remappings
251 /// 3. Returning the compacted forest and root map
252 ///
253 /// # Examples
254 ///
255 /// ```rust
256 /// use miden_core::mast::MastForest;
257 ///
258 /// let mut forest = MastForest::new();
259 /// // Add nodes to the forest
260 ///
261 /// // First clear debug info if needed
262 /// forest.clear_debug_info();
263 ///
264 /// // Then compact the forest (consumes the original)
265 /// let (compacted_forest, root_map) = forest.compact();
266 ///
267 /// // compacted_forest is now compacted with duplicate nodes merged
268 /// ```
269 pub fn compact(self) -> (MastForest, MastForestRootMap) {
270 // Merge with itself to deduplicate nodes
271 // Note: This cannot fail for a self-merge under normal conditions.
272 // The only possible failures (TooManyNodes, TooManyDecorators) would require the
273 // original forest to be at capacity limits, at which point compaction wouldn't help.
274 MastForest::merge([&self])
275 .expect("Failed to compact MastForest: this should never happen during self-merge")
276 }
277
278 /// Merges all `forests` into a new [`MastForest`].
279 ///
280 /// Merging two forests means combining all their constituent parts, i.e. [`MastNode`]s,
281 /// [`Decorator`]s and roots. During this process, any duplicate or
282 /// unreachable nodes are removed. Additionally, [`MastNodeId`]s of nodes as well as
283 /// [`DecoratorId`]s of decorators may change and references to them are remapped to their new
284 /// location.
285 ///
286 /// For example, consider this representation of a forest's nodes with all of these nodes being
287 /// roots:
288 ///
289 /// ```text
290 /// [Block(foo), Block(bar)]
291 /// ```
292 ///
293 /// If we merge another forest into it:
294 ///
295 /// ```text
296 /// [Block(bar), Call(0)]
297 /// ```
298 ///
299 /// then we would expect this forest:
300 ///
301 /// ```text
302 /// [Block(foo), Block(bar), Call(1)]
303 /// ```
304 ///
305 /// - The `Call` to the `bar` block was remapped to its new index (now 1, previously 0).
306 /// - The `Block(bar)` was deduplicated any only exists once in the merged forest.
307 ///
308 /// The function also returns a vector of [`MastForestRootMap`]s, whose length equals the number
309 /// of passed `forests`. The indices in the vector correspond to the ones in `forests`. The map
310 /// of a given forest contains the new locations of its roots in the merged forest. To
311 /// illustrate, the above example would return a vector of two maps:
312 ///
313 /// ```text
314 /// vec![{0 -> 0, 1 -> 1}
315 /// {0 -> 1, 1 -> 2}]
316 /// ```
317 ///
318 /// - The root locations of the original forest are unchanged.
319 /// - For the second forest, the `bar` block has moved from index 0 to index 1 in the merged
320 /// forest, and the `Call` has moved from index 1 to 2.
321 ///
322 /// If any forest being merged contains an `External(qux)` node and another forest contains a
323 /// node whose digest is `qux`, then the external node will be replaced with the `qux` node,
324 /// which is effectively deduplication. Decorators are ignored when it comes to merging
325 /// External nodes. This means that an External node with decorators may be replaced by a node
326 /// without decorators or vice versa.
327 pub fn merge<'forest>(
328 forests: impl IntoIterator<Item = &'forest MastForest>,
329 ) -> Result<(MastForest, MastForestRootMap), MastForestError> {
330 MastForestMerger::merge(forests)
331 }
332}
333
334// ------------------------------------------------------------------------------------------------
335/// Helpers
336impl MastForest {
337 /// Adds all provided nodes to the internal set of nodes, remapping all [`MastNodeId`]
338 /// references in those nodes.
339 ///
340 /// # Panics
341 /// - Panics if the internal set of nodes is not empty.
342 fn remap_and_add_nodes(
343 &mut self,
344 nodes_to_add: Vec<MastNode>,
345 id_remappings: &BTreeMap<MastNodeId, MastNodeId>,
346 ) {
347 assert!(self.nodes.is_empty());
348 // extract decorator information from the nodes by converting them into builders
349 let node_builders =
350 nodes_to_add.into_iter().map(|node| node.to_builder(self)).collect::<Vec<_>>();
351
352 // Clear decorator storage after extracting builders (builders contain decorator data)
353 self.debug_info.clear_mappings();
354
355 // Add each node to the new MAST forest, making sure to rewrite any outdated internal
356 // `MastNodeId`s
357 for live_node_builder in node_builders {
358 live_node_builder.remap_children(id_remappings).add_to_forest(self).unwrap();
359 }
360 }
361
362 /// Remaps and adds all old root ids to the internal set of roots.
363 ///
364 /// # Panics
365 /// - Panics if the internal set of roots is not empty.
366 fn remap_and_add_roots(
367 &mut self,
368 old_root_ids: Vec<MastNodeId>,
369 id_remappings: &BTreeMap<MastNodeId, MastNodeId>,
370 ) {
371 assert!(self.roots.is_empty());
372
373 for old_root_id in old_root_ids {
374 let new_root_id = id_remappings.get(&old_root_id).copied().unwrap_or(old_root_id);
375 self.make_root(new_root_id);
376 }
377 }
378}
379
380/// Returns the set of nodes that are live, as well as the mapping from "old ID" to "new ID" for all
381/// live nodes.
382fn remove_nodes(
383 mast_nodes: Vec<MastNode>,
384 nodes_to_remove: &BTreeSet<MastNodeId>,
385) -> (Vec<MastNode>, BTreeMap<MastNodeId, MastNodeId>) {
386 // Note: this allows us to safely use `usize as u32`, guaranteeing that it won't wrap around.
387 assert!(mast_nodes.len() < u32::MAX as usize);
388
389 let mut retained_nodes = Vec::with_capacity(mast_nodes.len());
390 let mut id_remappings = BTreeMap::new();
391
392 for (old_node_index, old_node) in mast_nodes.into_iter().enumerate() {
393 let old_node_id: MastNodeId = MastNodeId(old_node_index as u32);
394
395 if !nodes_to_remove.contains(&old_node_id) {
396 let new_node_id: MastNodeId = MastNodeId(retained_nodes.len() as u32);
397 id_remappings.insert(old_node_id, new_node_id);
398
399 retained_nodes.push(old_node);
400 }
401 }
402
403 (retained_nodes, id_remappings)
404}
405
406// ------------------------------------------------------------------------------------------------
407/// Public accessors
408impl MastForest {
409 /// Returns the [`MastNode`] associated with the provided [`MastNodeId`] if valid, or else
410 /// `None`.
411 ///
412 /// This is the fallible version of indexing (e.g. `mast_forest[node_id]`).
413 #[inline(always)]
414 pub fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> {
415 self.nodes.get(node_id)
416 }
417
418 /// Returns the [`MastNodeId`] of the procedure associated with a given digest, if any.
419 #[inline(always)]
420 pub fn find_procedure_root(&self, digest: Word) -> Option<MastNodeId> {
421 self.roots.iter().find(|&&root_id| self[root_id].digest() == digest).copied()
422 }
423
424 /// Returns true if a node with the specified ID is a root of a procedure in this MAST forest.
425 pub fn is_procedure_root(&self, node_id: MastNodeId) -> bool {
426 self.roots.contains(&node_id)
427 }
428
429 /// Returns an iterator over the digests of all procedures in this MAST forest.
430 pub fn procedure_digests(&self) -> impl Iterator<Item = Word> + '_ {
431 self.roots.iter().map(|&root_id| self[root_id].digest())
432 }
433
434 /// Returns an iterator over the digests of local procedures in this MAST forest.
435 ///
436 /// A local procedure is defined as a procedure which is not a single external node.
437 pub fn local_procedure_digests(&self) -> impl Iterator<Item = Word> + '_ {
438 self.roots.iter().filter_map(|&root_id| {
439 let node = &self[root_id];
440 if node.is_external() { None } else { Some(node.digest()) }
441 })
442 }
443
444 /// Returns an iterator over the IDs of the procedures in this MAST forest.
445 pub fn procedure_roots(&self) -> &[MastNodeId] {
446 &self.roots
447 }
448
449 /// Returns the number of procedures in this MAST forest.
450 pub fn num_procedures(&self) -> u32 {
451 self.roots
452 .len()
453 .try_into()
454 .expect("MAST forest contains more than 2^32 procedures.")
455 }
456
457 /// Returns the [Word] representing the content hash of a subset of [`MastNodeId`]s.
458 ///
459 /// # Panics
460 /// This function panics if any `node_ids` is not a node of this forest.
461 pub fn compute_nodes_commitment<'a>(
462 &self,
463 node_ids: impl IntoIterator<Item = &'a MastNodeId>,
464 ) -> Word {
465 let mut digests: Vec<Word> = node_ids.into_iter().map(|&id| self[id].digest()).collect();
466 digests.sort_unstable();
467 miden_crypto::hash::poseidon2::Poseidon2::merge_many(&digests)
468 }
469
470 /// Returns the commitment to this MAST forest.
471 ///
472 /// The commitment is computed as the sequential hash of all procedure roots in the forest.
473 /// This value is cached after the first computation and reused for subsequent calls,
474 /// unless the forest is mutated (in which case the cache is invalidated).
475 ///
476 /// The commitment uniquely identifies the forest's structure, as each root's digest
477 /// transitively includes all of its descendants. Therefore, a commitment to all roots
478 /// is a commitment to the entire forest.
479 pub fn commitment(&self) -> Word {
480 *self.commitment_cache.get_or_init(|| self.compute_nodes_commitment(&self.roots))
481 }
482
483 /// Returns the number of nodes in this MAST forest.
484 pub fn num_nodes(&self) -> u32 {
485 self.nodes.len() as u32
486 }
487
488 /// Returns the underlying nodes in this MAST forest.
489 pub fn nodes(&self) -> &[MastNode] {
490 self.nodes.as_slice()
491 }
492
493 pub fn advice_map(&self) -> &AdviceMap {
494 &self.advice_map
495 }
496
497 pub fn advice_map_mut(&mut self) -> &mut AdviceMap {
498 &mut self.advice_map
499 }
500
501 // SERIALIZATION
502 // --------------------------------------------------------------------------------------------
503
504 /// Serializes this MastForest without debug information.
505 ///
506 /// This produces a smaller output by omitting decorators, error codes, and procedure names.
507 /// The resulting bytes can be deserialized with the standard [`Deserializable`] impl,
508 /// which auto-detects the format and creates an empty [`DebugInfo`].
509 ///
510 /// Use this for production builds where debug info is not needed.
511 ///
512 /// # Example
513 ///
514 /// ```
515 /// use miden_core::{mast::MastForest, serde::Serializable};
516 ///
517 /// let forest = MastForest::new();
518 ///
519 /// // Full serialization (with debug info)
520 /// let full_bytes = forest.to_bytes();
521 ///
522 /// // Stripped serialization (without debug info)
523 /// let mut stripped_bytes = Vec::new();
524 /// forest.write_stripped(&mut stripped_bytes);
525 ///
526 /// // Both can be deserialized the same way
527 /// // let restored = MastForest::read_from_bytes(&stripped_bytes).unwrap();
528 /// ```
529 pub fn write_stripped<W: ByteWriter>(&self, target: &mut W) {
530 serialization::write_stripped_into(self, target);
531 }
532
533 /// Serializes this MastForest with the HASHLESS flag set.
534 ///
535 /// Hashless implies stripped: debug info is omitted, and digests must be recomputed during
536 /// validation. Trusted deserialization rejects this flag.
537 ///
538 /// Use this when producing data for untrusted validation.
539 pub fn write_hashless<W: ByteWriter>(&self, target: &mut W) {
540 serialization::write_hashless_into(self, target);
541 }
542
543 /// Returns the exact size of stripped serialization in bytes.
544 pub fn stripped_size_hint(&self) -> usize {
545 serialization::stripped_size_hint(self)
546 }
547}
548
549// ------------------------------------------------------------------------------------------------
550/// Decorator methods
551impl MastForest {
552 /// Returns a list of all decorators contained in this [MastForest].
553 pub fn decorators(&self) -> &[Decorator] {
554 self.debug_info.decorators()
555 }
556
557 /// Returns the [`Decorator`] associated with the provided [`DecoratorId`] if valid, or else
558 /// `None`.
559 ///
560 /// This is the fallible version of indexing (e.g. `mast_forest[decorator_id]`).
561 #[inline]
562 pub fn decorator_by_id(&self, decorator_id: DecoratorId) -> Option<&Decorator> {
563 self.debug_info.decorator(decorator_id)
564 }
565
566 /// Returns decorator indices for a specific operation within a node.
567 ///
568 /// This is the primary accessor for reading decorators from the centralized storage.
569 /// Returns a slice of decorator IDs for the given operation.
570 #[inline]
571 pub(crate) fn decorator_indices_for_op(
572 &self,
573 node_id: MastNodeId,
574 local_op_idx: usize,
575 ) -> &[DecoratorId] {
576 self.debug_info.decorators_for_operation(node_id, local_op_idx)
577 }
578
579 /// Returns an iterator over decorator references for a specific operation within a node.
580 ///
581 /// This is the preferred method for accessing decorators, as it provides direct
582 /// references to the decorator objects.
583 #[inline]
584 pub fn decorators_for_op<'a>(
585 &'a self,
586 node_id: MastNodeId,
587 local_op_idx: usize,
588 ) -> impl Iterator<Item = &'a Decorator> + 'a {
589 self.decorator_indices_for_op(node_id, local_op_idx)
590 .iter()
591 .map(move |&decorator_id| &self[decorator_id])
592 }
593
594 /// Returns the decorators to be executed before this node is executed.
595 #[inline]
596 pub fn before_enter_decorators(&self, node_id: MastNodeId) -> &[DecoratorId] {
597 self.debug_info.before_enter_decorators(node_id)
598 }
599
600 /// Returns the decorators to be executed after this node is executed.
601 #[inline]
602 pub fn after_exit_decorators(&self, node_id: MastNodeId) -> &[DecoratorId] {
603 self.debug_info.after_exit_decorators(node_id)
604 }
605
606 /// Returns decorator links for a node, including operation indices.
607 ///
608 /// This provides a flattened view of all decorators for a node with their operation indices.
609 #[inline]
610 pub(crate) fn decorator_links_for_node<'a>(
611 &'a self,
612 node_id: MastNodeId,
613 ) -> Result<DecoratedLinks<'a>, DecoratorIndexError> {
614 self.debug_info.decorator_links_for_node(node_id)
615 }
616
617 /// Adds a decorator to the forest, and returns the associated [`DecoratorId`].
618 pub fn add_decorator(&mut self, decorator: Decorator) -> Result<DecoratorId, MastForestError> {
619 self.debug_info.add_decorator(decorator)
620 }
621
622 /// Adds a debug variable to the forest, and returns the associated [`DebugVarId`].
623 pub fn add_debug_var(
624 &mut self,
625 debug_var: DebugVarInfo,
626 ) -> Result<DebugVarId, MastForestError> {
627 self.debug_info.add_debug_var(debug_var)
628 }
629
630 /// Returns debug variable IDs for a specific operation within a node.
631 pub fn debug_vars_for_operation(
632 &self,
633 node_id: MastNodeId,
634 local_op_idx: usize,
635 ) -> &[DebugVarId] {
636 self.debug_info.debug_vars_for_operation(node_id, local_op_idx)
637 }
638
639 /// Returns the debug variable with the given ID, if it exists.
640 pub fn debug_var(&self, debug_var_id: DebugVarId) -> Option<&DebugVarInfo> {
641 self.debug_info.debug_var(debug_var_id)
642 }
643
644 /// Adds decorator IDs for a node to the storage.
645 ///
646 /// Used when building nodes for efficient decorator access during execution.
647 ///
648 /// # Note
649 /// This method does not validate decorator IDs immediately. Validation occurs during
650 /// operations that need to access the actual decorator data (e.g., merging, serialization).
651 #[inline]
652 pub(crate) fn register_node_decorators(
653 &mut self,
654 node_id: MastNodeId,
655 before_enter: &[DecoratorId],
656 after_exit: &[DecoratorId],
657 ) {
658 if before_enter.is_empty() && after_exit.is_empty() {
659 return;
660 }
661
662 self.debug_info.register_node_decorators(node_id, before_enter, after_exit);
663 }
664
665 /// Returns the [`AssemblyOp`] associated with a node.
666 ///
667 /// For basic block nodes with a `target_op_idx`, returns the AssemblyOp for that operation.
668 /// For other nodes or when no `target_op_idx` is provided, returns the first AssemblyOp.
669 pub fn get_assembly_op(
670 &self,
671 node_id: MastNodeId,
672 target_op_idx: Option<usize>,
673 ) -> Option<&AssemblyOp> {
674 match target_op_idx {
675 Some(op_idx) => self.debug_info.asm_op_for_operation(node_id, op_idx),
676 None => self.debug_info.first_asm_op_for_node(node_id),
677 }
678 }
679}
680
681// ------------------------------------------------------------------------------------------------
682/// Validation methods
683impl MastForest {
684 fn validate_basic_block_invariants(&self) -> Result<(), MastForestError> {
685 for (node_id_idx, node) in self.nodes.iter().enumerate() {
686 let node_id =
687 MastNodeId::new_unchecked(node_id_idx.try_into().expect("too many nodes"));
688 if let MastNode::Block(basic_block) = node {
689 basic_block.validate_batch_invariants().map_err(|error_msg| {
690 MastForestError::InvalidBatchPadding(node_id, error_msg)
691 })?;
692
693 let num_operations = basic_block.num_operations() as usize;
694 let decorator_links = match self.decorator_links_for_node(node_id) {
695 Ok(decorator_links) => decorator_links,
696 Err(DecoratorIndexError::NodeIndex(_)) => continue,
697 Err(error) => return Err(MastForestError::DecoratorError(error)),
698 };
699 for (operation_idx, _) in decorator_links {
700 if operation_idx >= num_operations {
701 return Err(MastForestError::DecoratorOpIndexOutOfBounds {
702 operation_idx,
703 num_operations,
704 });
705 }
706 }
707 }
708 }
709
710 Ok(())
711 }
712
713 fn validate_procedure_name_digests(&self) -> Result<(), MastForestError> {
714 for (digest, _) in self.debug_info.procedure_names() {
715 if self.find_procedure_root(digest).is_none() {
716 return Err(MastForestError::InvalidProcedureNameDigest(digest));
717 }
718 }
719
720 Ok(())
721 }
722
723 /// Validates that all BasicBlockNodes in this forest satisfy the core invariants:
724 /// 1. Power-of-two number of groups in each batch
725 /// 2. No operation group ends with an operation requiring an immediate value
726 /// 3. The last operation group in a batch cannot contain operations requiring immediate values
727 /// 4. OpBatch structural consistency (num_groups <= BATCH_SIZE, group size <= GROUP_SIZE,
728 /// indptr integrity, bounds checking)
729 ///
730 /// This also validates that each stored procedure-name digest resolves to a procedure root in
731 /// the forest.
732 ///
733 /// This addresses the gap created by PR 2094, where padding NOOPs are now inserted
734 /// at assembly time rather than dynamically during execution, and adds comprehensive
735 /// structural validation to prevent deserialization-time panics.
736 pub fn validate(&self) -> Result<(), MastForestError> {
737 self.validate_basic_block_invariants()?;
738 self.validate_procedure_name_digests()
739 }
740
741 /// Validates that stored node digests match the hashes implied by local structure.
742 ///
743 /// For `External` nodes the digest is accepted as-is because it is externally provided and
744 /// cannot be reconstructed from local structure alone.
745 fn validate_node_hashes(&self) -> Result<(), MastForestError> {
746 let computed_hashes = self.compute_node_hashes()?;
747 for (node_idx, (node, computed_digest)) in
748 self.nodes.iter().zip(computed_hashes).enumerate()
749 {
750 let expected_digest = node.digest();
751 if expected_digest != computed_digest {
752 return Err(MastForestError::HashMismatch {
753 node_id: MastNodeId::new_unchecked(node_idx as u32),
754 expected: expected_digest,
755 computed: computed_digest,
756 });
757 }
758 }
759
760 Ok(())
761 }
762
763 /// Computes node hashes in topological order.
764 ///
765 /// The returned vector is aligned with node indices, so `digests[node_id as usize]` is the
766 /// digest of that node.
767 ///
768 /// For `External` nodes, the existing digest is returned unchanged.
769 ///
770 /// Returns [`MastForestError::ForwardReference`] if nodes are not in topological order.
771 fn compute_node_hashes(&self) -> Result<Vec<Word>, MastForestError> {
772 use crate::chiplets::hasher;
773
774 /// Checks that child_id references a node that appears before node_id in topological order.
775 fn check_no_forward_ref(
776 node_id: MastNodeId,
777 child_id: MastNodeId,
778 ) -> Result<(), MastForestError> {
779 if child_id.0 >= node_id.0 {
780 return Err(MastForestError::ForwardReference(node_id, child_id));
781 }
782 Ok(())
783 }
784
785 let mut computed_hashes = Vec::with_capacity(self.nodes.len());
786 for (node_idx, node) in self.nodes.iter().enumerate() {
787 let node_id = MastNodeId::new_unchecked(node_idx as u32);
788
789 // Check topological ordering and compute digest.
790 let computed_digest = match node {
791 MastNode::Block(block) => {
792 let op_groups: Vec<Felt> =
793 block.op_batches().iter().flat_map(|batch| *batch.groups()).collect();
794 hasher::hash_elements(&op_groups)
795 },
796 MastNode::Join(join) => {
797 let left_id = join.first();
798 let right_id = join.second();
799 check_no_forward_ref(node_id, left_id)?;
800 check_no_forward_ref(node_id, right_id)?;
801
802 let left_digest = computed_hashes[left_id.0 as usize];
803 let right_digest = computed_hashes[right_id.0 as usize];
804 hasher::merge_in_domain(&[left_digest, right_digest], JoinNode::DOMAIN)
805 },
806 MastNode::Split(split) => {
807 let true_id = split.on_true();
808 let false_id = split.on_false();
809 check_no_forward_ref(node_id, true_id)?;
810 check_no_forward_ref(node_id, false_id)?;
811
812 let true_digest = computed_hashes[true_id.0 as usize];
813 let false_digest = computed_hashes[false_id.0 as usize];
814 hasher::merge_in_domain(&[true_digest, false_digest], SplitNode::DOMAIN)
815 },
816 MastNode::Loop(loop_node) => {
817 let body_id = loop_node.body();
818 check_no_forward_ref(node_id, body_id)?;
819
820 let body_digest = computed_hashes[body_id.0 as usize];
821 hasher::merge_in_domain(&[body_digest, Word::default()], LoopNode::DOMAIN)
822 },
823 MastNode::Call(call) => {
824 let callee_id = call.callee();
825 check_no_forward_ref(node_id, callee_id)?;
826
827 let callee_digest = computed_hashes[callee_id.0 as usize];
828 let domain = if call.is_syscall() {
829 CallNode::SYSCALL_DOMAIN
830 } else {
831 CallNode::CALL_DOMAIN
832 };
833 hasher::merge_in_domain(&[callee_digest, Word::default()], domain)
834 },
835 MastNode::Dyn(dyn_node) => {
836 if dyn_node.is_dyncall() {
837 DynNode::DYNCALL_DEFAULT_DIGEST
838 } else {
839 DynNode::DYN_DEFAULT_DIGEST
840 }
841 },
842 MastNode::External(_) => {
843 // External nodes have externally-provided digests that cannot be recomputed.
844 node.digest()
845 },
846 };
847
848 computed_hashes.push(computed_digest);
849 }
850
851 Ok(computed_hashes)
852 }
853}
854
855// ------------------------------------------------------------------------------------------------
856/// Error message methods
857impl MastForest {
858 /// Given an error code as a Felt, resolves it to its corresponding error message.
859 pub fn resolve_error_message(&self, code: Felt) -> Option<Arc<str>> {
860 let key = code.as_canonical_u64();
861 self.debug_info.error_message(key)
862 }
863
864 /// Registers an error message in the MAST Forest and returns the corresponding error code as a
865 /// Felt.
866 pub fn register_error(&mut self, msg: Arc<str>) -> Felt {
867 let code: Felt = error_code_from_msg(&msg);
868 // we use u64 as keys for the map
869 self.debug_info.insert_error_code(code.as_canonical_u64(), msg);
870 code
871 }
872}
873
874// ------------------------------------------------------------------------------------------------
875/// Procedure name methods
876impl MastForest {
877 /// Returns the procedure name for the given MAST root digest, if present.
878 pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
879 self.debug_info.procedure_name(digest)
880 }
881
882 /// Returns an iterator over all (digest, name) pairs of procedure names.
883 pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
884 self.debug_info.procedure_names()
885 }
886
887 /// Inserts a procedure name for the given MAST root digest.
888 pub fn insert_procedure_name(&mut self, digest: Word, name: Arc<str>) {
889 assert!(
890 self.find_procedure_root(digest).is_some(),
891 "attempted to insert procedure name for digest that is not a procedure root"
892 );
893 self.debug_info.insert_procedure_name(digest, name);
894 }
895
896 /// Returns a reference to the debug info for this forest.
897 pub fn debug_info(&self) -> &DebugInfo {
898 &self.debug_info
899 }
900
901 /// Returns a mutable reference to the debug info.
902 ///
903 /// This is intended for use by the assembler to register AssemblyOps and other debug
904 /// information during compilation.
905 pub fn debug_info_mut(&mut self) -> &mut DebugInfo {
906 &mut self.debug_info
907 }
908}
909
910// TEST HELPERS
911// ================================================================================================
912
913#[cfg(test)]
914impl MastForest {
915 /// Returns all decorators for a given node as a vector of (position, DecoratorId) tuples.
916 ///
917 /// This helper method combines before_enter, operation-indexed, and after_exit decorators
918 /// into a single collection, which is useful for testing decorator positions and ordering.
919 ///
920 /// **Performance Warning**: This method performs multiple allocations through collect() calls
921 /// and should not be relied upon for performance-critical code. It is intended for testing
922 /// only.
923 pub fn all_decorators(&self, node_id: MastNodeId) -> Vec<(usize, DecoratorId)> {
924 let node = &self[node_id];
925
926 // For non-basic blocks, just get before_enter and after_exit decorators at position 0
927 if !node.is_basic_block() {
928 let before_enter_decorators: Vec<_> = self
929 .before_enter_decorators(node_id)
930 .iter()
931 .map(|&deco_id| (0, deco_id))
932 .collect();
933
934 let after_exit_decorators: Vec<_> = self
935 .after_exit_decorators(node_id)
936 .iter()
937 .map(|&deco_id| (1, deco_id))
938 .collect();
939
940 return [before_enter_decorators, after_exit_decorators].concat();
941 }
942
943 // For basic blocks, we need to handle operation-indexed decorators with proper positioning
944 let block = node.unwrap_basic_block();
945
946 // Before-enter decorators are at position 0
947 let before_enter_decorators: Vec<_> = self
948 .before_enter_decorators(node_id)
949 .iter()
950 .map(|&deco_id| (0, deco_id))
951 .collect();
952
953 // Operation-indexed decorators with their actual positions
954 let op_indexed_decorators: Vec<_> =
955 self.decorator_links_for_node(node_id).unwrap().into_iter().collect();
956
957 // After-exit decorators are positioned after all operations
958 let after_exit_decorators: Vec<_> = self
959 .after_exit_decorators(node_id)
960 .iter()
961 .map(|&deco_id| (block.num_operations() as usize, deco_id))
962 .collect();
963
964 [before_enter_decorators, op_indexed_decorators, after_exit_decorators].concat()
965 }
966}
967
968// MAST FOREST INDEXING
969// ------------------------------------------------------------------------------------------------
970
971impl Index<MastNodeId> for MastForest {
972 type Output = MastNode;
973
974 #[inline(always)]
975 fn index(&self, node_id: MastNodeId) -> &Self::Output {
976 &self.nodes[node_id]
977 }
978}
979
980impl IndexMut<MastNodeId> for MastForest {
981 #[inline(always)]
982 fn index_mut(&mut self, node_id: MastNodeId) -> &mut Self::Output {
983 &mut self.nodes[node_id]
984 }
985}
986
987impl Index<DecoratorId> for MastForest {
988 type Output = Decorator;
989
990 #[inline(always)]
991 fn index(&self, decorator_id: DecoratorId) -> &Self::Output {
992 self.debug_info.decorator(decorator_id).expect("DecoratorId out of bounds")
993 }
994}
995
996impl IndexMut<DecoratorId> for MastForest {
997 #[inline(always)]
998 fn index_mut(&mut self, decorator_id: DecoratorId) -> &mut Self::Output {
999 self.debug_info.decorator_mut(decorator_id).expect("DecoratorId out of bounds")
1000 }
1001}
1002
1003// MAST NODE ID
1004// ================================================================================================
1005
1006/// An opaque handle to a [`MastNode`] in some [`MastForest`]. It is the responsibility of the user
1007/// to use a given [`MastNodeId`] with the corresponding [`MastForest`].
1008///
1009/// Note that the [`MastForest`] does *not* ensure that equal [`MastNode`]s have equal
1010/// [`MastNodeId`] handles. Hence, [`MastNodeId`] equality must not be used to test for equality of
1011/// the underlying [`MastNode`].
1012#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1013#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1014#[cfg_attr(feature = "serde", serde(transparent))]
1015#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
1016pub struct MastNodeId(u32);
1017
1018/// Operations that mutate a MAST often produce this mapping between old and new NodeIds.
1019pub type Remapping = BTreeMap<MastNodeId, MastNodeId>;
1020
1021impl MastNodeId {
1022 /// Returns a new `MastNodeId` with the provided inner value, or an error if the provided
1023 /// `value` is greater than the number of nodes in the forest.
1024 ///
1025 /// For use in deserialization.
1026 pub fn from_u32_safe(
1027 value: u32,
1028 mast_forest: &MastForest,
1029 ) -> Result<Self, DeserializationError> {
1030 Self::from_u32_with_node_count(value, mast_forest.nodes.len())
1031 }
1032
1033 /// Returns a new [`MastNodeId`] with the provided `node_id`, or an error if `node_id` is
1034 /// greater than the number of nodes in the [`MastForest`] for which this ID is being
1035 /// constructed.
1036 pub fn from_usize_safe(
1037 node_id: usize,
1038 mast_forest: &MastForest,
1039 ) -> Result<Self, DeserializationError> {
1040 let node_id: u32 = node_id.try_into().map_err(|_| {
1041 DeserializationError::InvalidValue(format!(
1042 "node id '{node_id}' does not fit into a u32"
1043 ))
1044 })?;
1045 MastNodeId::from_u32_safe(node_id, mast_forest)
1046 }
1047
1048 /// Returns a new [`MastNodeId`] from the given `value` without checking its validity.
1049 pub fn new_unchecked(value: u32) -> Self {
1050 Self(value)
1051 }
1052
1053 /// Returns a new [`MastNodeId`] with the provided `id`, or an error if `id` is greater or equal
1054 /// to `node_count`. The `node_count` is the total number of nodes in the [`MastForest`] for
1055 /// which this ID is being constructed.
1056 ///
1057 /// This function can be used when deserializing an id whose corresponding node is not yet in
1058 /// the forest and [`Self::from_u32_safe`] would fail. For instance, when deserializing the ids
1059 /// referenced by the Join node in this forest:
1060 ///
1061 /// ```text
1062 /// [Join(1, 2), Block(foo), Block(bar)]
1063 /// ```
1064 ///
1065 /// Since it is less safe than [`Self::from_u32_safe`] and usually not needed it is not public.
1066 pub(super) fn from_u32_with_node_count(
1067 id: u32,
1068 node_count: usize,
1069 ) -> Result<Self, DeserializationError> {
1070 if (id as usize) < node_count {
1071 Ok(Self(id))
1072 } else {
1073 Err(DeserializationError::InvalidValue(format!(
1074 "Invalid deserialized MAST node ID '{id}', but {node_count} is the number of nodes in the forest",
1075 )))
1076 }
1077 }
1078
1079 /// Remap the NodeId to its new position using the given [`Remapping`].
1080 pub fn remap(&self, remapping: &Remapping) -> Self {
1081 *remapping.get(self).unwrap_or(self)
1082 }
1083}
1084
1085impl From<u32> for MastNodeId {
1086 fn from(value: u32) -> Self {
1087 MastNodeId::new_unchecked(value)
1088 }
1089}
1090
1091impl Idx for MastNodeId {}
1092
1093impl From<MastNodeId> for u32 {
1094 fn from(value: MastNodeId) -> Self {
1095 value.0
1096 }
1097}
1098
1099impl fmt::Display for MastNodeId {
1100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101 write!(f, "MastNodeId({})", self.0)
1102 }
1103}
1104
1105#[cfg(any(test, feature = "arbitrary"))]
1106impl Arbitrary for MastNodeId {
1107 type Parameters = ();
1108
1109 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1110 use proptest::prelude::*;
1111 any::<u32>().prop_map(MastNodeId).boxed()
1112 }
1113
1114 type Strategy = BoxedStrategy<Self>;
1115}
1116
1117// ITERATOR
1118
1119/// Iterates over all the nodes a root depends on, in pre-order. The iteration can include other
1120/// roots in the same forest.
1121pub struct SubtreeIterator<'a> {
1122 forest: &'a MastForest,
1123 discovered: Vec<MastNodeId>,
1124 unvisited: Vec<MastNodeId>,
1125}
1126impl<'a> SubtreeIterator<'a> {
1127 pub fn new(root: &MastNodeId, forest: &'a MastForest) -> Self {
1128 let discovered = vec![];
1129 let unvisited = vec![*root];
1130 SubtreeIterator { forest, discovered, unvisited }
1131 }
1132}
1133impl Iterator for SubtreeIterator<'_> {
1134 type Item = MastNodeId;
1135 fn next(&mut self) -> Option<MastNodeId> {
1136 while let Some(id) = self.unvisited.pop() {
1137 let node = &self.forest[id];
1138 if !node.has_children() {
1139 return Some(id);
1140 } else {
1141 self.discovered.push(id);
1142 node.append_children_to(&mut self.unvisited);
1143 }
1144 }
1145 self.discovered.pop()
1146 }
1147}
1148
1149// DECORATOR ID
1150// ================================================================================================
1151
1152/// An opaque handle to a [`Decorator`] in some [`MastForest`]. It is the responsibility of the user
1153/// to use a given [`DecoratorId`] with the corresponding [`MastForest`].
1154#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1155#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1156#[cfg_attr(feature = "serde", serde(transparent))]
1157#[cfg_attr(
1158 all(feature = "arbitrary", test),
1159 miden_test_serde_macros::serde_test(binary_serde(true))
1160)]
1161pub struct DecoratorId(u32);
1162
1163impl DecoratorId {
1164 /// Returns a new `DecoratorId` with the provided inner value, or an error if the provided
1165 /// `value` is greater than the number of nodes in the forest.
1166 ///
1167 /// For use in deserialization.
1168 pub fn from_u32_safe(
1169 value: u32,
1170 mast_forest: &MastForest,
1171 ) -> Result<Self, DeserializationError> {
1172 Self::from_u32_bounded(value, mast_forest.debug_info.num_decorators())
1173 }
1174
1175 /// Returns a new `DecoratorId` with the provided inner value, or an error if the provided
1176 /// `value` is greater than or equal to `bound`.
1177 ///
1178 /// For use in deserialization when the bound is known without needing the full MastForest.
1179 pub fn from_u32_bounded(value: u32, bound: usize) -> Result<Self, DeserializationError> {
1180 if (value as usize) < bound {
1181 Ok(Self(value))
1182 } else {
1183 Err(DeserializationError::InvalidValue(format!(
1184 "Invalid deserialized MAST decorator id '{value}', but allows only {bound} decorators",
1185 )))
1186 }
1187 }
1188
1189 /// Creates a new [`DecoratorId`] without checking its validity.
1190 pub(crate) fn new_unchecked(value: u32) -> Self {
1191 Self(value)
1192 }
1193}
1194
1195impl From<u32> for DecoratorId {
1196 fn from(value: u32) -> Self {
1197 DecoratorId::new_unchecked(value)
1198 }
1199}
1200
1201impl Idx for DecoratorId {}
1202
1203impl From<DecoratorId> for u32 {
1204 fn from(value: DecoratorId) -> Self {
1205 value.0
1206 }
1207}
1208
1209impl fmt::Display for DecoratorId {
1210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1211 write!(f, "DecoratorId({})", self.0)
1212 }
1213}
1214
1215#[cfg(feature = "arbitrary")]
1216impl Arbitrary for DecoratorId {
1217 type Parameters = ();
1218 type Strategy = BoxedStrategy<Self>;
1219
1220 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1221 any::<u32>().prop_map(Self::from).boxed()
1222 }
1223}
1224
1225impl Serializable for DecoratorId {
1226 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1227 self.0.write_into(target)
1228 }
1229}
1230
1231impl Deserializable for DecoratorId {
1232 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1233 let value = u32::read_from(source)?;
1234 Ok(Self(value))
1235 }
1236}
1237
1238// ASM OP ID
1239// ================================================================================================
1240
1241/// Unique identifier for an [`AssemblyOp`] within a [`MastForest`].
1242///
1243/// Unlike decorators (which are executed at runtime), AssemblyOps are metadata
1244/// used only for error context and debugging tools.
1245#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1246#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1247#[cfg_attr(feature = "serde", serde(transparent))]
1248#[cfg_attr(
1249 all(feature = "arbitrary", test),
1250 miden_test_serde_macros::serde_test(binary_serde(true))
1251)]
1252pub struct AsmOpId(u32);
1253
1254impl AsmOpId {
1255 /// Creates a new [`AsmOpId`] with the provided inner value.
1256 pub const fn new(value: u32) -> Self {
1257 Self(value)
1258 }
1259}
1260
1261impl From<u32> for AsmOpId {
1262 fn from(value: u32) -> Self {
1263 AsmOpId::new(value)
1264 }
1265}
1266
1267impl Idx for AsmOpId {}
1268
1269impl From<AsmOpId> for u32 {
1270 fn from(id: AsmOpId) -> Self {
1271 id.0
1272 }
1273}
1274
1275impl fmt::Display for AsmOpId {
1276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1277 write!(f, "AsmOpId({})", self.0)
1278 }
1279}
1280
1281#[cfg(feature = "arbitrary")]
1282impl Arbitrary for AsmOpId {
1283 type Parameters = ();
1284 type Strategy = BoxedStrategy<Self>;
1285
1286 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1287 any::<u32>().prop_map(Self::from).boxed()
1288 }
1289}
1290
1291impl Serializable for AsmOpId {
1292 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1293 self.0.write_into(target)
1294 }
1295}
1296
1297impl Deserializable for AsmOpId {
1298 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1299 let value = u32::read_from(source)?;
1300 Ok(Self(value))
1301 }
1302}
1303
1304/// Derives an error code from an error message by hashing the message and returning the 0th element
1305/// of the resulting [`Word`].
1306pub fn error_code_from_msg(msg: impl AsRef<str>) -> Felt {
1307 // hash the message and return 0th felt of the resulting Word
1308 hash_string_to_word(msg.as_ref())[0]
1309}
1310
1311// MAST FOREST ERROR
1312// ================================================================================================
1313
1314/// Represents the types of errors that can occur when dealing with MAST forest.
1315#[derive(Debug, thiserror::Error, PartialEq, Eq)]
1316pub enum MastForestError {
1317 #[error("MAST forest decorator count exceeds the maximum of {} decorators", u32::MAX)]
1318 TooManyDecorators,
1319 #[error("MAST forest node count exceeds the maximum of {} nodes", MastForest::MAX_NODES)]
1320 TooManyNodes,
1321 #[error("node id {0} is greater than or equal to forest length {1}")]
1322 NodeIdOverflow(MastNodeId, usize),
1323 #[error("decorator id {0} is greater than or equal to decorator count {1}")]
1324 DecoratorIdOverflow(DecoratorId, usize),
1325 #[error("basic block cannot be created from an empty list of operations")]
1326 EmptyBasicBlock,
1327 #[error(
1328 "decorator operation index {operation_idx} is greater than or equal to operation count {num_operations}"
1329 )]
1330 DecoratorOpIndexOutOfBounds {
1331 operation_idx: usize,
1332 num_operations: usize,
1333 },
1334 #[error(
1335 "decorator root of child with node id {0} is missing but is required for fingerprint computation"
1336 )]
1337 ChildFingerprintMissing(MastNodeId),
1338 #[error("advice map key {0} already exists when merging forests")]
1339 AdviceMapKeyCollisionOnMerge(Word),
1340 #[error("decorator storage error: {0}")]
1341 DecoratorError(DecoratorIndexError),
1342 #[error("digest is required for deserialization")]
1343 DigestRequiredForDeserialization,
1344 #[error("invalid batch in basic block node {0:?}: {1}")]
1345 InvalidBatchPadding(MastNodeId, String),
1346 #[error("procedure name references digest that is not a procedure root: {0:?}")]
1347 InvalidProcedureNameDigest(Word),
1348 #[error(
1349 "node {0:?} references child {1:?} which comes after it in the forest (forward reference)"
1350 )]
1351 ForwardReference(MastNodeId, MastNodeId),
1352 #[error("hash mismatch for node {node_id:?}: expected {expected:?}, computed {computed:?}")]
1353 HashMismatch {
1354 node_id: MastNodeId,
1355 expected: Word,
1356 computed: Word,
1357 },
1358 #[error("deserialization failed: {0}")]
1359 Deserialization(DeserializationError),
1360}
1361
1362// Custom serde implementations for MastForest that handle linked decorators properly
1363// by delegating to the existing miden-crypto serialization which already handles
1364// the conversion between linked and owned decorator formats.
1365#[cfg(feature = "serde")]
1366impl Serialize for MastForest {
1367 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1368 where
1369 S: serde::Serializer,
1370 {
1371 // Use the existing miden-crypto serialization which already handles linked decorators
1372 let bytes = Serializable::to_bytes(self);
1373 serializer.serialize_bytes(&bytes)
1374 }
1375}
1376
1377#[cfg(feature = "serde")]
1378impl<'de> Deserialize<'de> for MastForest {
1379 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1380 where
1381 D: serde::Deserializer<'de>,
1382 {
1383 // Deserialize bytes, then use miden-crypto Deserializable
1384 let bytes = Vec::<u8>::deserialize(deserializer)?;
1385 let mut slice_reader = SliceReader::new(&bytes);
1386 Deserializable::read_from(&mut slice_reader).map_err(serde::de::Error::custom)
1387 }
1388}