Skip to main content

charon_lib/export/
multi_target.rs

1//! Merging multiple [`CrateData`]s from different compilation targets into one.
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::mem;
6
7use itertools::Itertools;
8
9use crate::errors::ErrorCtx;
10use crate::ids::IndexVec;
11use crate::options::TranslateOptions;
12use crate::transform::TransformCtx;
13use crate::transform::ctx::TransformPass;
14use crate::{ast::*, options::CliOpts};
15
16use super::{CharonVersion, CrateData};
17
18/// Merge per-target [`CrateData`]s into a single [`CrateData`].
19pub fn merge(options: CliOpts, krates: Vec<CrateData>) -> CrateData {
20    let mut error_ctx = ErrorCtx::new();
21    let tr_options = TranslateOptions::new(&mut error_ctx, &options);
22
23    let mut merged = CrateMerger::process(options, krates);
24
25    ItemDeduplicator::dedup(&mut merged.translated, &mut error_ctx);
26
27    // Recompute declaration order on the merged crate.
28    let mut ctx = TransformCtx {
29        options: tr_options,
30        translated: merged.translated,
31        errors: RefCell::new(error_ctx),
32    };
33    crate::transform::add_missing_info::reorder_decls::Transform.transform_ctx(&mut ctx);
34    merged.translated = ctx.translated;
35
36    merged
37}
38
39// =============================================================================================
40// Step 1: Merge a set of crates into one, remembering the source target in the names.
41// =============================================================================================
42
43struct CrateMerger {
44    merged: CrateData,
45    file_name_to_id: HashMap<FileName, FileId>,
46}
47
48impl CrateMerger {
49    fn process(options: CliOpts, krates: Vec<CrateData>) -> CrateData {
50        let mut translated = TranslatedCrate::default();
51        translated.options = options;
52        let mut merger = CrateMerger {
53            merged: CrateData {
54                charon_version: CharonVersion(crate::VERSION.to_owned()),
55                translated,
56                has_errors: false,
57            },
58            file_name_to_id: HashMap::new(),
59        };
60        for krate in krates {
61            merger.add_one(krate);
62        }
63
64        merger.merged
65    }
66
67    fn add_one(&mut self, krate: CrateData) {
68        let CrateData {
69            charon_version: _, // Checked by deserialization already
70            translated: mut krate,
71            has_errors,
72        } = krate;
73        self.merged.has_errors |= has_errors;
74        let target = krate
75            .target_information
76            .keys()
77            .exactly_one()
78            .ok()
79            .unwrap()
80            .clone();
81
82        // Remap all ids inside `krate`.
83        krate.drive_mut(&mut {
84            let file_id_map = krate.files.map_ref(|file| {
85                if let Some(&existing_id) = self.file_name_to_id.get(&file.name) {
86                    existing_id
87                } else {
88                    let new_id = self.merged.translated.files.push_with(|new_id| {
89                        let mut file = file.clone();
90                        file.id = new_id;
91                        file
92                    });
93                    self.file_name_to_id.insert(file.name.clone(), new_id);
94                    new_id
95                }
96            });
97
98            #[derive(Visitor)]
99            struct RemapIdsVisitor {
100                target: TargetTriple,
101                file_id_map: IndexVec<FileId, FileId>,
102                type_offset: usize,
103                fun_offset: usize,
104                global_offset: usize,
105                trait_decl_offset: usize,
106                trait_impl_offset: usize,
107            }
108
109            impl VisitAstMut for RemapIdsVisitor {
110                fn enter_file_id(&mut self, id: &mut FileId) {
111                    *id = self.file_id_map[*id];
112                }
113                fn enter_type_decl_id(&mut self, id: &mut TypeDeclId) {
114                    *id += self.type_offset;
115                }
116                fn enter_fun_decl_id(&mut self, id: &mut FunDeclId) {
117                    *id += self.fun_offset;
118                }
119                fn enter_global_decl_id(&mut self, id: &mut GlobalDeclId) {
120                    *id += self.global_offset;
121                }
122                fn enter_trait_decl_id(&mut self, id: &mut TraitDeclId) {
123                    *id += self.trait_decl_offset;
124                }
125                fn enter_trait_impl_id(&mut self, id: &mut TraitImplId) {
126                    *id += self.trait_impl_offset;
127                }
128                fn enter_name(&mut self, name: &mut Name) {
129                    name.name.push(PathElem::Target(self.target.clone()));
130                }
131            }
132
133            RemapIdsVisitor {
134                target,
135                file_id_map,
136                type_offset: self.merged.translated.type_decls.slot_count(),
137                fun_offset: self.merged.translated.fun_decls.slot_count(),
138                global_offset: self.merged.translated.global_decls.slot_count(),
139                trait_decl_offset: self.merged.translated.trait_decls.slot_count(),
140                trait_impl_offset: self.merged.translated.trait_impls.slot_count(),
141            }
142        });
143
144        let TranslatedCrate {
145            crate_name,
146            options: _, // We discard the per-target options we made
147            target_information,
148            item_names,
149            assoc_item_names,
150            short_names: _, // TODO
151            files: _,       // Done above
152            type_decls,
153            fun_decls,
154            global_decls,
155            trait_decls,
156            trait_impls,
157            ordered_decls: _, // Recomputed on the merged crate
158        } = krate;
159        if self.merged.translated.crate_name.is_empty() {
160            self.merged.translated.crate_name = crate_name;
161        }
162        self.merged
163            .translated
164            .target_information
165            .extend(target_information);
166        self.merged.translated.item_names.extend(item_names);
167        self.merged
168            .translated
169            .assoc_item_names
170            .extend_from_other(assoc_item_names);
171        self.merged
172            .translated
173            .type_decls
174            .extend_from_other(type_decls);
175        self.merged
176            .translated
177            .fun_decls
178            .extend_from_other(fun_decls);
179        self.merged
180            .translated
181            .global_decls
182            .extend_from_other(global_decls);
183        self.merged
184            .translated
185            .trait_decls
186            .extend_from_other(trait_decls);
187        self.merged
188            .translated
189            .trait_impls
190            .extend_from_other(trait_impls);
191    }
192}
193
194// =============================================================================================
195// Step 2: Deduplicates items that don't differ across targets and create façades for
196// target-dependent functions
197// =============================================================================================
198
199generate_index_type!(TargetGroupId, "TargetGroup");
200
201/// A set of items that share the same base name and item kind.
202/// These are candidates for merging into a single cross-target item.
203struct TargetGroup {
204    ids: SeqHashMap<TargetTriple, ItemId>,
205}
206
207/// How a `TargetGroup` should be merged.
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209enum MergeDecision {
210    /// Don't merge this group.
211    Skip,
212    /// All the items are the same; merge them into one.
213    Dedup,
214    /// Function signatures match but bodies diffe; create a façade that dispatches to the
215    /// per-target items.
216    Facade,
217}
218
219impl TargetGroup {
220    /// Deterministically chosen representative id.
221    fn canonical_id(&self) -> ItemId {
222        self.ids.values().next().copied().unwrap()
223    }
224
225    /// Whether this group is a group of function items.
226    fn is_function_group(&self) -> bool {
227        self.canonical_id().is_fun()
228    }
229
230    /// Compare the items of this group under the provided id mapping.
231    fn decide_merge(
232        &self,
233        krate: &TranslatedCrate,
234        remap: &HashMap<ItemId, ItemId>,
235    ) -> MergeDecision {
236        let canonical_id = self.canonical_id();
237        let items: Vec<Option<ItemByVal>> = self
238            .ids
239            .values()
240            .map(|&id| {
241                krate
242                    .get_item(id)
243                    .map(|item| normalize_item(item.to_owned(), canonical_id, remap))
244            })
245            .collect_vec();
246
247        // Items that don't exist in the crate can't be compared; if they're all missing we can
248        // still merge them tho.
249        if items.iter().all(|i| i.is_none()) {
250            return MergeDecision::Dedup;
251        }
252        let items: Vec<_> = match items.into_iter().collect::<Option<Vec<_>>>() {
253            Some(items) => items,
254            None => return MergeDecision::Skip,
255        };
256
257        if items.iter().all_equal() {
258            MergeDecision::Dedup
259        } else if self.is_function_group()
260            && items
261                .iter()
262                .map(|item| item.as_fun().unwrap())
263                .map(|d| (&d.item_meta.name, &d.generics, &d.signature))
264                .all_equal()
265        {
266            MergeDecision::Facade
267        } else {
268            MergeDecision::Skip
269        }
270    }
271
272    /// Yields `(non_canonical_id, canonical_id)` pairs for building an ID remap.
273    fn remap_entries<'a>(&'a self) -> impl Iterator<Item = (ItemId, ItemId)> + 'a {
274        self.ids.values().map(|&id| (id, self.canonical_id()))
275    }
276    fn into_remap_entries(self) -> impl Iterator<Item = (ItemId, ItemId)> {
277        let canonical_id = self.canonical_id();
278        self.ids.into_values().map(move |id| (id, canonical_id))
279    }
280
281    /// Build a façade `FunDecl` for a group of functions with matching signatures but different
282    /// bodies. The `def_id` is set to a placeholder and must be fixed up on insertion.
283    fn build_facade_decl(&self, def_id: FunDeclId, krate: &TranslatedCrate) -> FunDecl {
284        let canonical_fun_id = *self.canonical_id().as_fun().unwrap();
285        let canonical = krate.fun_decls.get(canonical_fun_id).unwrap();
286
287        let dispatch_map = self
288            .ids
289            .iter()
290            .map(|(target, &id)| {
291                let fun_decl_ref = FunDeclRef {
292                    id: *id.as_fun().unwrap(),
293                    generics: Box::new(canonical.generics.identity_args()),
294                };
295                (target.clone(), fun_decl_ref)
296            })
297            .collect();
298
299        let mut item_meta = canonical.item_meta.clone();
300        // Remove the target suffix (and do a little sanity check).
301        item_meta.name.name.pop().unwrap().as_target().unwrap();
302
303        FunDecl {
304            def_id,
305            item_meta,
306            generics: canonical.generics.clone(),
307            signature: canonical.signature.clone(),
308            src: canonical.src.clone(),
309            is_global_initializer: canonical.is_global_initializer,
310            body: Body::TargetDispatch(dispatch_map),
311        }
312    }
313}
314
315/// Normalize a name for grouping across targets; returns the target.
316fn normalize_name_for_grouping(
317    name: &Name,
318    krate: &TranslatedCrate,
319) -> Option<(Name, TargetTriple)> {
320    let (mut name, target) = name.strip_target_suffix()?;
321    for elem in &mut name.name {
322        if let PathElem::Impl(ImplElem::Trait(id)) = elem {
323            // Replace ipl block references with something that contains the implemented trait
324            // predicate instead. That way, comparing names for equality compares trait predicates
325            // instead.
326            if let Some(timpl) = krate.trait_impls.get(*id) {
327                let mut params = GenericParams::default();
328                params.trait_clauses.push(TraitParam {
329                    clause_id: TraitClauseId::ZERO,
330                    span: None,
331                    origin: PredicateOrigin::WhereClauseOnImpl,
332                    trait_: RegionBinder::empty(timpl.impl_trait.clone()),
333                });
334                *elem = PathElem::Impl(ImplElem::Ty(Box::new(Binder {
335                    params,
336                    skip_binder: Ty::mk_unit(),
337                    kind: BinderKind::Other,
338                })));
339            }
340        }
341    }
342    Some((name, target))
343}
344
345/// Orchestrates deduplication of items across compilation targets.
346struct ItemDeduplicator<'a> {
347    krate: &'a mut TranslatedCrate,
348    groups: IndexVec<TargetGroupId, TargetGroup>,
349}
350
351impl<'a> ItemDeduplicator<'a> {
352    /// Entrypoint: deduplicate items that are the same across targets.
353    pub fn dedup(krate: &'a mut TranslatedCrate, errors: &mut ErrorCtx) {
354        let groups = Self::discover_groups(krate, errors);
355        if groups.is_empty() {
356            return;
357        }
358        let mut this = Self { krate, groups };
359        let decisions = this.decide_group_mergings();
360        this.apply_merge_decisions(decisions);
361    }
362
363    /// Group items by (base_name, item_kind). Each group contains the versions of that item
364    /// across all targets where it exists.
365    fn discover_groups(
366        krate: &TranslatedCrate,
367        _errors: &mut ErrorCtx,
368    ) -> IndexVec<TargetGroupId, TargetGroup> {
369        let mut groups_map: SeqHashMap<
370            (Name, std::mem::Discriminant<ItemId>),
371            SeqHashMap<TargetTriple, ItemId>,
372        > = SeqHashMap::new();
373        for (&item_id, name) in &krate.item_names {
374            if let Some((base_name, target)) = normalize_name_for_grouping(name, krate) {
375                let key = (base_name, std::mem::discriminant(&item_id));
376                let per_target = groups_map.entry(key).or_default();
377                if per_target.contains_key(&target) {
378                    // Name collision within the same target: skip this group entirely.
379                    per_target.clear();
380                } else {
381                    per_target.insert(target, item_id);
382                }
383            }
384        }
385        // We do a fixpoint: merging a group may lead to detecting that some names are actually the
386        // same (because the names refer to impls/types).
387        loop {
388            let prev_len = groups_map.len();
389            let remap: HashMap<ItemId, ItemId> = groups_map
390                .values()
391                .filter(|ids| !ids.is_empty())
392                .cloned()
393                .map(|ids| TargetGroup { ids })
394                .flat_map(|g| g.into_remap_entries())
395                .filter(|(x, y)| x != y)
396                .collect();
397            for ((mut name, kind), ids) in mem::take(&mut groups_map) {
398                name.drive_mut(&mut IdRefMapperVisitor::new(&remap));
399                let key = (name, kind);
400                let per_target = groups_map.entry(key).or_default();
401                for (target, item_id) in ids {
402                    if per_target.contains_key(&target) {
403                        // Name collision within the same target: skip this group entirely.
404                        per_target.clear();
405                        break;
406                    } else {
407                        per_target.insert(target, item_id);
408                    }
409                }
410            }
411            // Remove empty groups (from collisions) and check for convergence.
412            groups_map.retain(|_, v| !v.is_empty());
413            if prev_len == groups_map.len() {
414                break;
415            }
416        }
417        let groups: IndexVec<TargetGroupId, TargetGroup> = groups_map
418            .into_values()
419            .map(|ids| TargetGroup { ids })
420            .collect();
421        groups
422    }
423
424    /// Decide how to merge each group. Skipped groups are not included in the output.
425    fn decide_group_mergings(&self) -> Vec<(TargetGroupId, MergeDecision)> {
426        // Start with all groups as candidates.
427        let mut candidates: Vec<(TargetGroupId, MergeDecision)> = self
428            .groups
429            .indices()
430            .map(|id| (id, MergeDecision::Skip))
431            .collect();
432
433        // Fixpoint: assume that all included groups are mapped to a single item; keep the groups
434        // that can be merged under such a mapping. Iterate until fixpoint.
435        loop {
436            let remap = self.build_remap(candidates.iter().map(|(id, _)| id));
437            let prev_len = candidates.len();
438            candidates.retain_mut(|(idx, decision)| {
439                *decision = self.groups[*idx].decide_merge(self.krate, &remap);
440                *decision != MergeDecision::Skip
441            });
442            if candidates.len() == prev_len {
443                break;
444            }
445        }
446
447        candidates
448    }
449
450    /// Build an id remap: for each candidate group, map non-canonical IDs → canonical ID.
451    fn build_remap<'b>(
452        &self,
453        candidate_indices: impl IntoIterator<Item = &'b TargetGroupId>,
454    ) -> HashMap<ItemId, ItemId> {
455        candidate_indices
456            .into_iter()
457            .flat_map(|&idx| self.groups[idx].remap_entries())
458            .filter(|(x, y)| x != y)
459            .collect()
460    }
461
462    fn apply_merge_decisions(&mut self, decisions: Vec<(TargetGroupId, MergeDecision)>) {
463        if decisions.is_empty() {
464            return;
465        }
466
467        let mut remap = HashMap::new();
468        let mut facade_decls: Vec<FunDecl> = Vec::new();
469        for &(idx, decision) in &decisions {
470            let mut group = &self.groups[idx];
471            let target_id = match decision {
472                MergeDecision::Skip => unreachable!(),
473                MergeDecision::Dedup => {
474                    self.dedup_group(idx); // takes mutable borrow; invalidates `group`
475                    group = &self.groups[idx];
476                    group.canonical_id()
477                }
478                MergeDecision::Facade => {
479                    let facade_id = self.krate.fun_decls.reserve_slot();
480                    // Insert facade decls later because the id remapping would mess up the
481                    // dispatch maps.
482                    facade_decls.push(group.build_facade_decl(facade_id, self.krate));
483                    // Mark per-target functions as target-dependent.
484                    for &id in group.ids.values() {
485                        let fun_id = *id.as_fun().unwrap();
486                        if let Some(fun_decl) = self.krate.fun_decls.get_mut(fun_id) {
487                            fun_decl.src = ItemSource::TargetDependent {
488                                dispatcher: FunDeclRef {
489                                    id: facade_id,
490                                    generics: Box::new(fun_decl.generics.identity_args()),
491                                },
492                            };
493                        }
494                    }
495                    ItemId::Fun(facade_id)
496                }
497            };
498            for &id in group.ids.values() {
499                if id != target_id {
500                    remap.insert(id, target_id);
501                }
502            }
503        }
504
505        // Remap all ids.
506        self.krate.drive_mut(&mut IdRefMapperVisitor::new(&remap));
507
508        for decl in facade_decls {
509            self.krate
510                .set_new_item_slot(ItemId::Fun(decl.def_id), ItemByVal::Fun(decl));
511        }
512    }
513
514    fn dedup_group(&mut self, idx: TargetGroupId) {
515        let group = &self.groups[idx];
516        let canonical = group.canonical_id();
517
518        // Remove the target suffix (and do a little sanity check).
519        let mut name = self.krate.item_names.get(&canonical).cloned().unwrap();
520        name.name.pop().unwrap().as_target().unwrap();
521        if let Some(mut canonical_item) = self.krate.get_item_mut(canonical) {
522            canonical_item.item_meta().name = name.clone();
523        }
524        self.krate.item_names.insert(canonical, name);
525
526        // Merge per-target layouts into the canonical type.
527        if let ItemId::Type(canonical_type_id) = canonical {
528            let layouts = group
529                .ids
530                .values()
531                .map(|&id| *id.as_type().unwrap())
532                .flat_map(|id| {
533                    self.krate
534                        .type_decls
535                        .get_mut(id)
536                        .map(|tdecl| mem::take(&mut tdecl.layout))
537                        .into_iter()
538                        .flatten()
539                })
540                .collect();
541            if let Some(dest) = self.krate.type_decls.get_mut(canonical_type_id) {
542                dest.layout = layouts;
543            }
544        }
545
546        // Remove non-canonical copies.
547        for &id in group.ids.values() {
548            if id != canonical {
549                self.krate.remove_item(id);
550            }
551        }
552    }
553}
554
555// =============================================================================================
556// Utilities
557// =============================================================================================
558
559/// Normalize an item for cross-target comparison.
560fn normalize_item(
561    mut item: ItemByVal,
562    canonical_id: ItemId,
563    remap: &HashMap<ItemId, ItemId>,
564) -> ItemByVal {
565    item.as_mut().drive_mut(&mut IdRefMapperVisitor::new(remap));
566    item.as_mut().set_id(canonical_id);
567    item.as_mut()
568        .item_meta()
569        .name
570        .name
571        .retain(|elem| !matches!(elem, PathElem::Target(_)));
572
573    strip_unstable_attributes(&mut item);
574    // Don't compare source text: span is enough, and it can have OS-specific line endings.
575    item.as_mut().item_meta().source_text = None;
576    if let ItemByVal::Type(ty_decl) = &mut item {
577        // Layouts are allowed to differ per-target.
578        ty_decl.layout.clear();
579    }
580    item
581}
582
583/// Strip attributes such as `rustc_diagnostic_item` whose arguments can vary across targets in a
584/// way that doesn't matter to us.
585fn strip_unstable_attributes(item: &mut ItemByVal) {
586    fn strip_in_vec(attr_info: &mut AttrInfo) {
587        attr_info.attributes.retain(
588            |attr| !matches!(attr, Attribute::Unknown(attr) if attr.path.starts_with("rustc_")),
589        );
590    }
591
592    strip_in_vec(&mut item.as_mut().item_meta().attr_info);
593
594    if let ItemByVal::TraitDecl(d) = item {
595        for method in &mut d.methods {
596            strip_in_vec(&mut method.skip_binder.attr_info);
597        }
598        for cst in &mut d.consts {
599            strip_in_vec(&mut cst.attr_info);
600        }
601        for ty in &mut d.types {
602            strip_in_vec(&mut ty.skip_binder.attr_info);
603        }
604    }
605}
606
607/// Visitor that remaps references to the given items.
608#[derive(Visitor)]
609struct IdRefMapperVisitor<'a> {
610    map: &'a HashMap<ItemId, ItemId>,
611}
612
613impl<'a> IdRefMapperVisitor<'a> {
614    fn new(remap: &'a HashMap<ItemId, ItemId>) -> Self {
615        Self { map: remap }
616    }
617
618    fn map<Id>(&self, id: &mut Id)
619    where
620        Id: Copy,
621        Id: Into<ItemId>,
622        ItemId: TryInto<Id, Error: Debug>,
623    {
624        if let Some(&new) = self.map.get(&(*id).into()) {
625            *id = new.try_into().unwrap();
626        }
627    }
628}
629
630impl VisitAstMut for IdRefMapperVisitor<'_> {
631    fn enter_type_decl_ref(&mut self, x: &mut TypeDeclRef) {
632        if let TypeId::Adt(id) = &mut x.id {
633            self.map(id);
634        }
635    }
636    fn enter_fun_decl_ref(&mut self, x: &mut FunDeclRef) {
637        self.map(&mut x.id);
638    }
639    fn enter_global_decl_ref(&mut self, x: &mut GlobalDeclRef) {
640        self.map(&mut x.id);
641    }
642    fn enter_trait_decl_ref(&mut self, x: &mut TraitDeclRef) {
643        self.map(&mut x.id);
644    }
645    fn enter_trait_impl_ref(&mut self, x: &mut TraitImplRef) {
646        self.map(&mut x.id);
647    }
648
649    fn enter_projection_elem(&mut self, x: &mut ProjectionElem) {
650        if let ProjectionElem::Field(proj, _) = x
651            && let FieldProjKind::Adt(id, _) = proj
652        {
653            self.map(id);
654        }
655    }
656    fn enter_fn_ptr(&mut self, x: &mut FnPtr) {
657        match &mut *x.kind {
658            FnPtrKind::Fun(FunId::Regular(id)) => self.map(id),
659            FnPtrKind::Trait(_, _, id) => self.map(id),
660            _ => {}
661        }
662    }
663    fn enter_global_decl(&mut self, x: &mut GlobalDecl) {
664        self.map(&mut x.init);
665    }
666    fn enter_fun_decl(&mut self, x: &mut FunDecl) {
667        if let Some(id) = &mut x.is_global_initializer {
668            self.map(id);
669        }
670    }
671    fn enter_impl_elem(&mut self, x: &mut ImplElem) {
672        if let ImplElem::Trait(id) = x {
673            self.map(id);
674        }
675    }
676}