miden_core/mast/merger/mod.rs
1use alloc::{collections::BTreeMap, vec::Vec};
2
3use crate::{
4 Word,
5 mast::{
6 DenseMastForestBuilder, MastForest, MastForestContributor, MastForestError, MastNode,
7 MastNodeBuilder, MastNodeId, MultiMastForestIteratorItem, MultiMastForestNodeIter,
8 },
9 utils::{DenseIdMap, IndexVec},
10};
11
12#[cfg(test)]
13mod tests;
14
15/// A type that allows merging [`MastForest`]s.
16///
17/// This functionality is exposed via [`MastForest::merge`]. See its documentation for more details.
18pub(crate) struct MastForestMerger {
19 mast_forest: DenseMastForestBuilder,
20 // Internal indices needed for efficient duplicate checking.
21 //
22 // These are always in-sync with the nodes in `mast_forest`, i.e. all nodes added to the
23 // `mast_forest` are also added to the indices.
24 node_id_by_hash: BTreeMap<Word, MastNodeId>,
25 hash_by_node_id: IndexVec<MastNodeId, Word>,
26 /// Mappings from previous `MastNodeId`s to their new ids.
27 ///
28 /// Any `MastNodeId` in `mast_forest` is present as the target of some mapping in this map.
29 node_id_mappings: Vec<DenseIdMap<MastNodeId, MastNodeId>>,
30}
31
32impl MastForestMerger {
33 /// Creates a new merger with an initially empty forest and merges all provided [`MastForest`]s
34 /// into it.
35 ///
36 /// # Normalizing Behavior
37 ///
38 /// This function performs normalization of the merged forest, which:
39 /// - Remaps all node IDs to maintain the invariant that child node IDs < parent node IDs
40 /// - Creates a clean, deduplicated forest structure
41 /// - Provides consistent node ordering regardless of input
42 ///
43 /// This normalization is idempotent, but it means that even for single-forest merges, the
44 /// resulting forest may have different node IDs and digests than the input. See assembly
45 /// test `issue_1644_single_forest_merge_identity` for detailed explanation of this
46 /// behavior.
47 pub(crate) fn merge<'forest>(
48 forests: impl IntoIterator<Item = &'forest MastForest>,
49 ) -> Result<(MastForest, MastForestRootMap), MastForestError> {
50 let forests = forests.into_iter().collect::<Vec<_>>();
51
52 let node_id_mappings =
53 forests.iter().map(|f| DenseIdMap::with_len(f.nodes().len())).collect();
54
55 let mut merger = Self {
56 node_id_by_hash: BTreeMap::new(),
57 hash_by_node_id: IndexVec::new(),
58 mast_forest: DenseMastForestBuilder::new(),
59 node_id_mappings,
60 };
61
62 merger.merge_inner(forests.clone())?;
63
64 let Self { mast_forest, node_id_mappings, .. } = merger;
65 let (mast_forest, final_id_remapping) = mast_forest.finish_with_id_map()?;
66 let node_id_mappings =
67 Self::remap_finalized_node_ids(node_id_mappings, &final_id_remapping);
68
69 let root_maps = MastForestRootMap::from_node_id_map(node_id_mappings, forests);
70
71 Ok((mast_forest, root_maps))
72 }
73
74 /// Merges all `forests` into self.
75 ///
76 /// It does this in three steps:
77 ///
78 /// 1. Merge all advice maps, checking for key collisions.
79 /// 2. Merge all nodes of forests.
80 /// - Node indices might move during merging, so the merger keeps a node id mapping as it
81 /// merges nodes.
82 /// - This is a depth-first traversal over all forests to ensure all children are processed
83 /// before their parents. See the documentation of [`MultiMastForestNodeIter`] for details
84 /// on this traversal.
85 /// - Because all parents are processed after their children, we can use the node id mapping
86 /// to remap all [`MastNodeId`]s of the children to their potentially new id in the merged
87 /// forest.
88 /// - If any external node is encountered during this traversal with a digest `foo` for which
89 /// a `replacement` node exists in another forest with digest `foo`, then the external node
90 /// will be replaced by that node. In particular, it means we do not want to add the
91 /// external node to the merged forest, so it is never yielded from the iterator.
92 /// - Assuming the simple case, where the `replacement` was not visited yet and is just a
93 /// single node (not a tree), the iterator would first yield the `replacement` node which
94 /// means it is going to be merged into the forest.
95 /// - Next the iterator yields [`MultiMastForestIteratorItem::ExternalNodeReplacement`]
96 /// which signals that an external node was replaced by another node. In this example,
97 /// the `replacement_*` indices contained in that variant would point to the
98 /// `replacement` node. Now we can simply add a mapping from the external node to the
99 /// `replacement` node in our node id mapping which means all nodes that referenced the
100 /// external node will point to the `replacement` instead.
101 /// 3. Finally, we merge all roots of all forests. Here we map the existing root indices to
102 /// their potentially new indices in the merged forest and add them to the forest,
103 /// deduplicating in the process, too.
104 fn merge_inner(&mut self, forests: Vec<&MastForest>) -> Result<(), MastForestError> {
105 for other_forest in forests.iter() {
106 self.merge_advice_map(other_forest)?;
107 }
108 let iterator = MultiMastForestNodeIter::new(forests.clone());
109 for item in iterator {
110 match item {
111 MultiMastForestIteratorItem::Node { forest_idx, node_id } => {
112 let node = forests[forest_idx][node_id].clone();
113 self.merge_node(forest_idx, node_id, node, &forests)?;
114 },
115 MultiMastForestIteratorItem::ExternalNodeReplacement {
116 // forest index of the node which replaces the external node
117 replacement_forest_idx,
118 // ID of the node that replaces the external node
119 replacement_mast_node_id,
120 // forest index of the external node
121 replaced_forest_idx,
122 // ID of the external node
123 replaced_mast_node_id,
124 } => {
125 // The iterator is not aware of the merged forest, so the node indices it yields
126 // are for the existing forests. That means we have to map the ID of the
127 // replacement to its new location, since it was previously merged and its IDs
128 // have very likely changed.
129 let mapped_replacement = self.node_id_mappings[replacement_forest_idx]
130 .get(replacement_mast_node_id)
131 .expect("every merged node id should be mapped");
132
133 // SAFETY: The iterator only yields valid forest indices, so it is safe to index
134 // directly.
135 self.node_id_mappings[replaced_forest_idx]
136 .insert(replaced_mast_node_id, mapped_replacement);
137 },
138 }
139 }
140
141 for (forest_idx, forest) in forests.iter().enumerate() {
142 self.merge_roots(forest_idx, forest);
143 }
144
145 Ok(())
146 }
147
148 fn merge_advice_map(&mut self, other_forest: &MastForest) -> Result<(), MastForestError> {
149 self.mast_forest.merge_advice_map(other_forest.advice_map())
150 }
151
152 fn merge_node(
153 &mut self,
154 forest_idx: usize,
155 merging_id: MastNodeId,
156 node: MastNode,
157 original_forests: &[&MastForest],
158 ) -> Result<(), MastForestError> {
159 // We need to remap the node prior to computing the node fingerprint since child IDs may
160 // have changed in the merged forest.
161 //
162 // Remapping at this point is guaranteed to be "complete", meaning all IDs of children
163 // will be present in the node id mapping since the DFS iteration guarantees
164 // that all children of this `node` have been processed before this node and
165 // their indices have been added to the mappings.
166 let remapped_builder = self.build_with_remapped_children(
167 merging_id,
168 node,
169 original_forests[forest_idx],
170 &self.node_id_mappings[forest_idx],
171 )?;
172
173 let node_fingerprint =
174 remapped_builder.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)?;
175
176 match self.lookup_node_by_fingerprint(&node_fingerprint) {
177 Some(matching_node_id) => {
178 // If a node with a matching fingerprint exists, then the merging node is a
179 // duplicate and we remap it to the existing node.
180 self.node_id_mappings[forest_idx].insert(merging_id, matching_node_id);
181 },
182 None => {
183 // If no node with a matching fingerprint exists, then the merging node is
184 // unique and we can add it to the merged forest using builders.
185 let new_node_id = self.mast_forest.push_node(remapped_builder)?;
186 self.node_id_mappings[forest_idx].insert(merging_id, new_node_id);
187
188 self.node_id_by_hash.insert(node_fingerprint, new_node_id);
189 let returned_id = self
190 .hash_by_node_id
191 .push(node_fingerprint)
192 .map_err(|_| MastForestError::TooManyNodes)?;
193 debug_assert_eq!(
194 returned_id, new_node_id,
195 "hash_by_node_id push() should return the same node IDs as node_id_by_hash"
196 );
197 },
198 }
199
200 Ok(())
201 }
202
203 fn merge_roots(&mut self, forest_idx: usize, other_forest: &MastForest) {
204 for root_id in other_forest.roots.iter() {
205 // Map the previous root to its possibly new id.
206 let new_root = self.node_id_mappings[forest_idx]
207 .get(*root_id)
208 .expect("all node ids should have an entry");
209 // This takes O(n) where n is the number of roots in the merged forest every time to
210 // check if the root already exists. As the number of roots is relatively low generally,
211 // this should be okay.
212 self.mast_forest.mark_root(new_root);
213 }
214 }
215
216 // HELPERS
217 // ================================================================================================
218
219 /// Returns the ID of the node in the merged forest that matches the given
220 /// fingerprint, if any.
221 fn lookup_node_by_fingerprint(&self, fingerprint: &Word) -> Option<MastNodeId> {
222 self.node_id_by_hash.get(fingerprint).copied()
223 }
224
225 /// Builds a new node with remapped children using the provided mappings.
226 fn build_with_remapped_children(
227 &self,
228 merging_id: MastNodeId,
229 src: MastNode,
230 original_forest: &MastForest,
231 nmap: &DenseIdMap<MastNodeId, MastNodeId>,
232 ) -> Result<MastNodeBuilder, MastForestError> {
233 super::build_node_with_remapped_ids(merging_id, src, original_forest, nmap)
234 }
235
236 /// Remaps each source forest's node IDs from merger-local IDs to finalized dense IDs.
237 fn remap_finalized_node_ids(
238 mut node_id_mappings: Vec<DenseIdMap<MastNodeId, MastNodeId>>,
239 final_id_remapping: &DenseIdMap<MastNodeId, MastNodeId>,
240 ) -> Vec<DenseIdMap<MastNodeId, MastNodeId>> {
241 for node_id_mapping in &mut node_id_mappings {
242 for source_index in 0..node_id_mapping.len() {
243 let source_id = MastNodeId::new_unchecked(
244 source_index.try_into().expect("source node index exceeds u32"),
245 );
246 if let Some(builder_id) = node_id_mapping.get(source_id) {
247 let finalized_id = final_id_remapping
248 .get(builder_id)
249 .expect("every builder node id should map to a finalized node id");
250 node_id_mapping.insert(source_id, finalized_id);
251 }
252 }
253 }
254
255 node_id_mappings
256 }
257}
258
259// MAST FOREST ROOT MAP
260// ================================================================================================
261
262/// A mapping for the new location of the roots of a [`MastForest`] after a merge.
263///
264/// It maps the roots ([`MastNodeId`]s) of a forest to their new [`MastNodeId`] in the merged
265/// forest. See [`MastForest::merge`] for more details.
266#[derive(Debug, Clone, Default, PartialEq, Eq)]
267pub struct MastForestRootMap {
268 node_maps: Vec<BTreeMap<MastNodeId, MastNodeId>>,
269 root_maps: Vec<BTreeMap<MastNodeId, MastNodeId>>,
270}
271
272impl MastForestRootMap {
273 fn from_node_id_map(
274 id_map: Vec<DenseIdMap<MastNodeId, MastNodeId>>,
275 forests: Vec<&MastForest>,
276 ) -> Self {
277 let mut node_maps = vec![BTreeMap::new(); forests.len()];
278 let mut root_maps = vec![BTreeMap::new(); forests.len()];
279
280 for (forest_idx, forest) in forests.into_iter().enumerate() {
281 for (node_idx, _) in forest.nodes().iter().enumerate() {
282 let node_id = MastNodeId::new_unchecked(
283 node_idx.try_into().expect("MastForest node index exceeds u32"),
284 );
285 if let Some(new_id) = id_map[forest_idx].get(node_id) {
286 node_maps[forest_idx].insert(node_id, new_id);
287 }
288 }
289 for root in forest.procedure_roots() {
290 let new_id = id_map[forest_idx]
291 .get(*root)
292 .expect("every node id should be mapped to its new id");
293 root_maps[forest_idx].insert(*root, new_id);
294 }
295 }
296
297 Self { node_maps, root_maps }
298 }
299
300 /// Maps any node from the given input forest to its new location in the merged forest.
301 ///
302 /// This includes non-root nodes, which is required when remapping package-owned source/debug
303 /// graphs after [`MastForest::merge`].
304 pub fn map_node(&self, forest_index: usize, node: &MastNodeId) -> Option<MastNodeId> {
305 self.node_maps.get(forest_index).and_then(|map| map.get(node)).copied()
306 }
307
308 /// Maps the given root to its new location in the merged forest, if such a mapping exists.
309 ///
310 /// It is guaranteed that every root of the map's corresponding forest is contained in the map.
311 pub fn map_root(&self, forest_index: usize, root: &MastNodeId) -> Option<MastNodeId> {
312 self.root_maps.get(forest_index).and_then(|map| map.get(root)).copied()
313 }
314}