Skip to main content

charon_lib/transform/normalize/
partial_monomorphization.rs

1//! This module implements partial monomorphization, which allows specializing generic items on
2//! some specific instanciation patterns. This is used by Aeneas to avoid nested mutable borrows:
3//! we transform `Iter<'a, &'b mut T>` to `{Iter::<_, &mut U>}<'a, 'b, T>`, where
4//! ```ignore
5//! struct {Iter::<'a, &'b mut U>}<'a, 'b, U> {
6//!   // the field of `Iter` but instantiated with `T -> &'b mut U`.
7//! }
8//! ```
9//!
10//! Note: We may need to partial-mono the same item multiple times: `Foo::<&mut A, B>`, `Foo::<A,
11//! &mut B>`. Note also that partial-mono is infectious: `Foo<Bar<&mut A>>` generates `Bar::<&mut
12//! A>` then `Foo::<Bar::<&mut A>>``.
13use std::collections::{HashMap, HashSet, VecDeque};
14use std::fmt::Display;
15use std::mem;
16
17use derive_generic_visitor::Visitor;
18use index_vec::Idx;
19
20use crate::ast::visitor::{VisitWithBinderDepth, VisitorWithBinderDepth};
21use crate::formatter::IntoFormatter;
22use crate::options::MonomorphizeMut;
23use crate::pretty::FmtWithCtx;
24use crate::register_error;
25use crate::transform::ctx::TransformPass;
26use crate::{transform::TransformCtx, ullbc_ast::*};
27
28type MutabilityShape = Binder<GenericArgs>;
29
30/// See the docs of `MutabilityShapeBuilder::compute_shape`.
31#[derive(Visitor)]
32struct MutabilityShapeBuilder<'pm, 'ctx> {
33    pm: &'pm PartialMonomorphizer<'ctx>,
34    /// The parameters that will constitute the final binder.
35    params: GenericParams,
36    /// The arguments to pass to the final binder to recover the input arguments.
37    extracted: GenericArgs,
38    /// Current depth under which we're visiting.
39    binder_depth: DeBruijnId,
40}
41
42impl<'pm, 'ctx> MutabilityShapeBuilder<'pm, 'ctx> {
43    /// Compute the mutability "shape" of a set of generic arguments by factoring out the minimal
44    /// amount of information that still allows reconstructing the original arguments while keeping the
45    /// "shape arguments" free of mutable borrows.
46    ///
47    /// For example, for input:
48    ///   <u32, &'a mut &'b A, Option::<&'a mut bool>>
49    /// we want to build:
50    ///   binder<'a, 'b, A, B, C> <A, &'a mut B, Option::<&'b mut C>>
51    /// from which we can recover the original arguments by instantiating it with:
52    ///   <'a, 'a, u32, &'b A, bool>
53    ///
54    /// Formally, given `let (shape, shape_args) = get_mutability_shape(args);`, we have the following:
55    /// - `shape.substitute(shape_args) == args`;
56    /// - `shape_args` contains no infected types;
57    /// - `shape` is as shallow as possible (i.e. takes just enough to get all the infected types
58    ///   and not more).
59    ///
60    /// Note: the input arguments are assumed to have been already partially monomorphized, in the
61    /// sense that we won't recurse inside ADT args because we assume any ADT applied to infected
62    /// args to have been replaced with a fresh infected ADT.
63    fn compute_shape(
64        pm: &'pm PartialMonomorphizer<'ctx>,
65        target_params: &GenericParams,
66        args: &GenericArgs,
67    ) -> (MutabilityShape, GenericArgs) {
68        // We start with the implicit parameters from the original item. We'll need to substitute
69        // them once we've figured out the mapping of explicit parameters, but we'll also be adding
70        // new trait clauses potentially so we can't leave the vector empty (the ids would be
71        // wrong).
72        let mut shape_contents = args.clone();
73        let mut builder = Self {
74            pm,
75            params: GenericParams {
76                regions: IndexVec::new(),
77                types: IndexVec::new(),
78                const_generics: IndexVec::new(),
79                ..target_params.clone()
80            },
81            extracted: GenericArgs {
82                regions: IndexVec::new(),
83                types: IndexVec::new(),
84                const_generics: IndexVec::new(),
85                trait_refs: mem::take(&mut shape_contents.trait_refs),
86            },
87            binder_depth: DeBruijnId::zero(),
88        };
89
90        // Traverse the generics and replace any non-infected type, region or const generic with a
91        // fresh variable.
92        let _ = VisitWithBinderDepth::new(&mut builder).visit(&mut shape_contents);
93
94        let shape_params = {
95            let mut shape_params = builder.params;
96            // Now the explicit params in `shape_params` are correct, and the implicit params are a mix
97            // of the old params and new trait clauses. The old params may refer to the old explicit
98            // params which is wrong and must be fixed up.
99            shape_params.trait_clauses = shape_params.trait_clauses.map_indexed(|i, x| {
100                if i.index() < target_params.trait_clauses.len() {
101                    x.substitute_explicits(&shape_contents)
102                } else {
103                    x
104                }
105            });
106            shape_params.trait_type_constraints =
107                shape_params.trait_type_constraints.map_indexed(|i, x| {
108                    if i.index() < target_params.trait_type_constraints.len() {
109                        x.substitute_explicits(&shape_contents)
110                    } else {
111                        x
112                    }
113                });
114            shape_params.regions_outlive = shape_params
115                .regions_outlive
116                .into_iter()
117                .enumerate()
118                .map(|(i, x)| {
119                    if i < target_params.regions_outlive.len() {
120                        x.substitute_explicits(&shape_contents)
121                    } else {
122                        x
123                    }
124                })
125                .collect();
126            shape_params.types_outlive = shape_params
127                .types_outlive
128                .into_iter()
129                .enumerate()
130                .map(|(i, x)| {
131                    if i < target_params.types_outlive.len() {
132                        x.substitute_explicits(&shape_contents)
133                    } else {
134                        x
135                    }
136                })
137                .collect();
138            shape_params
139        };
140
141        // The first half of the trait params correspond to the original item clauses so we can
142        // pass them unmodified.
143        shape_contents.trait_refs = shape_params.identity_args().trait_refs;
144        shape_contents
145            .trait_refs
146            .truncate(target_params.trait_clauses.len());
147
148        let shape_args = builder.extracted;
149        let shape = Binder::new(BinderKind::Other, shape_params, shape_contents);
150        (shape, shape_args)
151    }
152
153    /// Replace this value with a fresh variable, and record that we did so.
154    fn replace_with_fresh_var<Id, Param, Arg>(
155        &mut self,
156        val: &mut Arg,
157        mk_param: impl FnOnce(Id) -> Param,
158        mk_value: impl FnOnce(DeBruijnVar<Id>) -> Arg,
159    ) where
160        Id: Idx + Display,
161        Arg: TyVisitable + Clone,
162        GenericParams: HasIdxVecOf<Id, Output = Param>,
163        GenericArgs: HasIdxVecOf<Id, Output = Arg>,
164    {
165        let Some(shifted_val) = val.clone().move_from_under_binders(self.binder_depth) else {
166            // Give up on this value.
167            return;
168        };
169        // Record the mapping in the output `GenericArgs`.
170        self.extracted.get_idx_vec_mut().push(shifted_val);
171        // Put a fresh param in place of `val`.
172        let id = self.params.get_idx_vec_mut().push_with(mk_param);
173        *val = mk_value(DeBruijnVar::bound(self.binder_depth, id));
174    }
175}
176
177impl<'pm, 'ctx> VisitorWithBinderDepth for MutabilityShapeBuilder<'pm, 'ctx> {
178    fn binder_depth_mut(&mut self) -> &mut DeBruijnId {
179        &mut self.binder_depth
180    }
181}
182
183impl<'pm, 'ctx> VisitAstMut for MutabilityShapeBuilder<'pm, 'ctx> {
184    fn visit<T: AstVisitable>(&mut self, x: &mut T) -> ControlFlow<Self::Break> {
185        VisitWithBinderDepth::new(self).visit(x)
186    }
187
188    fn enter_ty(&mut self, ty: &mut Ty) {
189        if !self.pm.is_infected(ty) {
190            self.replace_with_fresh_var(
191                ty,
192                |id| TypeParam::new(id, format!("T{id}"), Variance::Unknown),
193                |v| v.into(),
194            );
195        }
196    }
197    fn exit_ty_kind(&mut self, kind: &mut TyKind) {
198        if let TyKind::Adt(TypeDeclRef { id, generics, .. }) = kind {
199            // Since the type was not replaced with a type var, it's an infected type. We've
200            // traversed it so we have its final explicit arguments. Now we need to satisfy its
201            // predicates. For that we add all its predicates to the new item, and pass those new
202            // trait clauses to it.
203            let Some(target_params) = self.pm.generic_params.get(&(*id).into()) else {
204                return;
205            };
206            let Some(shifted_generics) =
207                generics.clone().move_from_under_binders(self.binder_depth)
208            else {
209                // Give up on this value.
210                return;
211            };
212
213            // Add the target predicates (properly substituted) to the new item params.
214            let num_clauses_before_merge = self.params.trait_clauses.len();
215            self.params.merge_predicates_from(
216                target_params
217                    .clone()
218                    .substitute_explicits(&shifted_generics),
219            );
220
221            // Record the trait arguments in the output `GenericArgs`.
222            self.extracted
223                .trait_refs
224                .extend(shifted_generics.trait_refs);
225
226            // Replace each trait ref with a clause var.
227            for (target_clause_id, tref) in generics.trait_refs.iter_mut_enumerated() {
228                let clause_id = target_clause_id + num_clauses_before_merge;
229                *tref =
230                    self.params.trait_clauses[clause_id].identity_tref_at_depth(self.binder_depth);
231            }
232        }
233    }
234    fn enter_region(&mut self, r: &mut Region) {
235        self.replace_with_fresh_var(
236            r,
237            |id| RegionParam::new(id, None, Variance::Unknown),
238            |v| v.into(),
239        );
240    }
241    // TODO: we're missing type info for this
242    // fn enter_const_generic(&mut self, cg: &mut ConstGeneric) {
243    //     self.replace_with_fresh_var(cg, |id| {
244    //         ConstGenericParam::new(id, format!("N{id}"), cg.ty().clone())
245    //     });
246    // }
247    fn visit_trait_ref(&mut self, _tref: &mut TraitRef) -> ControlFlow<Self::Break> {
248        // We don't touch trait refs or we'd risk adding duplicated extra params. Instead, we fix
249        // them up in `exit_ty_kind` and `compute_shape`.
250        ControlFlow::Continue(())
251    }
252
253    fn visit_constant_expr(
254        &mut self,
255        _: &mut ConstantExpr,
256    ) -> ::std::ops::ControlFlow<Self::Break> {
257        ControlFlow::Continue(())
258    }
259}
260
261#[derive(Visitor)]
262struct PartialMonomorphizer<'a> {
263    ctx: &'a mut TransformCtx,
264    /// Tracks the closest span to emit useful errors.
265    span: Span,
266    /// Whether we partially-monomorphize type declarations.
267    specialize_adts: bool,
268    /// Types that contain mutable references.
269    infected_types: HashSet<TypeDeclId>,
270    /// Map of generic params for each item. We can't use `ctx.translated` because while iterating
271    /// over items the current item isn't available anymore, which would break recursive types.
272    /// This also makes it possible to record the generics of our to-be-added items without adding
273    /// them.
274    generic_params: HashMap<ItemId, GenericParams>,
275    /// Map of partial monomorphizations. The source item applied with the generic params gives the
276    /// target item. The resulting partially-monomorphized item will have the binder params as
277    /// generic params.
278    partial_mono_shapes: SeqHashMap<(ItemId, MutabilityShape), ItemId>,
279    /// Reverse of `partial_mono_shapes`.
280    reverse_shape_map: HashMap<ItemId, (ItemId, MutabilityShape)>,
281    /// Items that need to be processed.
282    to_process: VecDeque<ItemId>,
283}
284
285impl<'a> PartialMonomorphizer<'a> {
286    pub fn new(ctx: &'a mut TransformCtx, specialize_adts: bool) -> Self {
287        // Compute the types that contain `&mut` (even indirectly). We actually can ignore
288        // `&'static mut`, so we simply rely on our "lifetime mutability" computation.
289        let infected_types: HashSet<_> = ctx
290            .translated
291            .type_decls
292            .iter()
293            .filter(|tdecl| {
294                tdecl
295                    .generics
296                    .regions
297                    .iter()
298                    .any(|r| r.mutability.is_mutable())
299            })
300            .map(|tdecl| tdecl.def_id)
301            .collect();
302
303        // Record the generic params of all items.
304        let generic_params: HashMap<ItemId, GenericParams> = ctx
305            .translated
306            .all_items()
307            .map(|item| (item.id(), item.generic_params().clone()))
308            .collect();
309
310        // Enqueue all items to be processed.
311        let to_process = ctx.translated.all_ids().collect();
312        PartialMonomorphizer {
313            ctx,
314            span: Span::dummy(),
315            specialize_adts,
316            infected_types,
317            generic_params,
318            to_process,
319            partial_mono_shapes: SeqHashMap::default(),
320            reverse_shape_map: Default::default(),
321        }
322    }
323
324    /// Whether this type is or contains a `&mut`. This assumes that we've already visited this
325    /// type and partially monomorphized any ADT references.
326    fn is_infected(&self, ty: &Ty) -> bool {
327        match ty.kind() {
328            TyKind::Ref(_, _, RefKind::Mut) => true,
329            TyKind::Ref(_, ty, _)
330            | TyKind::RawPtr(ty, _)
331            | TyKind::Array(ty, ..)
332            | TyKind::Pattern(ty, _)
333            | TyKind::Slice(ty, _) => self.is_infected(ty),
334            TyKind::Adt(tref) => {
335                let ty_infected = self.infected_types.contains(&tref.id);
336                let args_infected = if self.specialize_adts {
337                    // Since we make sure to only call the method on a processed type, any type
338                    // with infected arguments would have been replaced with a fresh instantiated
339                    // (and infected type). Hence we don't need to check the arguments here, only
340                    // the type id.
341                    false
342                } else {
343                    tref.generics.types.iter().any(|ty| self.is_infected(ty))
344                };
345                ty_infected || args_infected
346            }
347            // A function pointer/item by itself doesn't carry any mutable reference, even if it
348            // uses some in its signature. Compare with closures: a closure without captures
349            // doesn't trigger partial mono regardless of its signature.
350            TyKind::FnDef(..) | TyKind::FnPtr(..) => false,
351            TyKind::DynTrait(_) => {
352                register_error!(
353                    self.ctx,
354                    self.span,
355                    "`dyn Trait` is unsupported with `--monomorphize-mut`"
356                );
357                false
358            }
359            TyKind::TypeVar(..)
360            | TyKind::Scalar(..)
361            | TyKind::Never
362            | TyKind::TraitType(..)
363            | TyKind::PtrMetadata(..)
364            | TyKind::Error(_) => false,
365        }
366    }
367
368    /// Given that `generics` apply to item `id`, if any of the generics is infected we generate a
369    /// reference to a new item obtained by partially instantiating item `id`. (That new item isn't
370    /// added immediately but is added to the `to_process` queue to be created later).
371    fn process_generics(&mut self, id: ItemId, generics: &GenericArgs) -> Option<DeclRef<ItemId>> {
372        if !generics.types.iter().any(|ty| self.is_infected(ty)) {
373            return None;
374        }
375
376        // If the type is already an instantiation, transform this reference into a reference to
377        // the original type so we don't instantiate the instantiation.
378        let mut new_generics;
379        let (id, generics) = if let Some(&(base_id, ref shape)) = self.reverse_shape_map.get(&id) {
380            new_generics = shape.clone().apply(generics);
381            let _ = self.visit(&mut new_generics); // New instantiation may require cleanup.
382            (base_id, &new_generics)
383        } else {
384            (id, generics)
385        };
386
387        // Split the args between the infected part and the non-infected part.
388        let item_params = self.generic_params.get(&id)?;
389        let (shape, shape_args) =
390            MutabilityShapeBuilder::compute_shape(self, item_params, generics);
391
392        // Create a new type id.
393        let new_params = shape.params.clone();
394        let key: (ItemId, MutabilityShape) = (id, shape);
395        let new_id = *self
396            .partial_mono_shapes
397            .entry(key.clone())
398            .or_insert_with(|| {
399                let new_id = match id {
400                    ItemId::Type(_) => {
401                        let new_id = self.ctx.translated.type_decls.reserve_slot();
402                        self.infected_types.insert(new_id);
403                        new_id.into()
404                    }
405                    ItemId::Fun(_) => self.ctx.translated.fun_decls.reserve_slot().into(),
406                    ItemId::Global(_) => self.ctx.translated.global_decls.reserve_slot().into(),
407                    ItemId::TraitDecl(_) => self.ctx.translated.trait_decls.reserve_slot().into(),
408                    ItemId::TraitImpl(_) => self.ctx.translated.trait_impls.reserve_slot().into(),
409                };
410                self.generic_params.insert(new_id, new_params);
411                self.reverse_shape_map.insert(new_id, key);
412                self.to_process.push_back(new_id);
413                new_id
414            });
415
416        let fmt_ctx = self.ctx.into_fmt();
417        trace!(
418            "processing {}{}\n output: {}{}",
419            id.with_ctx(&fmt_ctx),
420            generics.with_ctx(&fmt_ctx),
421            new_id.with_ctx(&fmt_ctx),
422            shape_args.with_ctx(&fmt_ctx),
423        );
424        Some(DeclRef {
425            id: new_id,
426            generics: Box::new(shape_args),
427            trait_ref: None,
428        })
429    }
430
431    /// Traverse the item, replacing any type instantiations we don't want with references to
432    /// soon-to-be-created partially-monomorphized types. This does not access the items in
433    /// `self.translated`, which may be missing since we took `item` out for processing.
434    pub fn process_item(&mut self, item: &mut ItemRefMut<'_>) {
435        let _ = item.drive_mut(self);
436    }
437
438    /// Creates the item corresponding to this id by instantiating the item it is based on.
439    ///
440    /// This accesses the items in `self.translated`, which must therefore all be there.
441    /// That's why items are created outside of `process_item`.
442    pub fn create_pending_instantiation(&mut self, new_id: ItemId) -> ItemByVal {
443        let (orig_id, shape) = &self.reverse_shape_map[&new_id];
444        let mut decl = self
445            .ctx
446            .translated
447            .get_item(*orig_id)
448            .unwrap()
449            .to_owned()
450            .substitute_with_self(&shape.skip_binder, &TraitRefKind::SelfId);
451
452        let mut decl_mut = decl.as_mut();
453        decl_mut.set_id(new_id);
454        *decl_mut.generic_params() = shape.params.clone();
455
456        let name_ref = &mut decl_mut.item_meta().name;
457        *name_ref = mem::take::<crate::ast::Name>(name_ref).instantiate(shape.clone());
458        self.ctx
459            .translated
460            .item_names
461            .insert(new_id, decl.as_ref().item_meta().name.clone());
462        if let (ItemId::TraitDecl(orig_trait_id), ItemId::TraitDecl(new_trait_id)) =
463            (*orig_id, new_id)
464        {
465            let names = self.ctx.translated.assoc_item_names[orig_trait_id].clone();
466            self.ctx
467                .translated
468                .assoc_item_names
469                .insert(new_trait_id, names);
470        }
471
472        decl
473    }
474}
475
476impl VisitorWithSpan for PartialMonomorphizer<'_> {
477    fn current_span(&mut self) -> &mut Span {
478        &mut self.span
479    }
480}
481impl VisitAstMut for PartialMonomorphizer<'_> {
482    fn visit<T: AstVisitable>(&mut self, x: &mut T) -> ControlFlow<Self::Break> {
483        // Track a useful enclosing span, for error messages.
484        VisitWithSpan::new(self).visit(x)
485    }
486
487    fn exit_type_decl_ref(&mut self, x: &mut TypeDeclRef) {
488        if x.is_tuple() && self.ctx.options.no_gen_tuple_structs {
489            return;
490        }
491        if self.specialize_adts
492            && let Some(new_decl_ref) = self.process_generics(x.id.into(), &x.generics)
493        {
494            x.id = new_decl_ref.id.try_into().unwrap();
495            x.generics = new_decl_ref.generics;
496        }
497    }
498    fn exit_fn_ptr(&mut self, x: &mut FnPtr) {
499        // TODO: methods. any `Trait::method<&mut A>` requires monomorphizing all the instances of
500        // that method just in case :>>>
501        if let FnPtrKind::Fun(id) = *x.kind
502            && let Some(new_decl_ref) = self.process_generics(id.into(), &x.generics)
503        {
504            *x = new_decl_ref.try_into().unwrap()
505        }
506    }
507    fn exit_fun_decl_ref(&mut self, x: &mut FunDeclRef) {
508        if let Some(new_decl_ref) = self.process_generics(x.id.into(), &x.generics) {
509            *x = new_decl_ref.try_into().unwrap()
510        }
511    }
512    fn exit_global_decl_ref(&mut self, x: &mut GlobalDeclRef) {
513        if let Some(new_decl_ref) = self.process_generics(x.id.into(), &x.generics) {
514            *x = new_decl_ref.try_into().unwrap()
515        }
516    }
517    fn exit_trait_decl_ref(&mut self, x: &mut TraitDeclRef) {
518        if let Some(new_decl_ref) = self.process_generics(x.id.into(), &x.generics) {
519            *x = new_decl_ref.try_into().unwrap()
520        }
521    }
522    fn exit_trait_impl_ref(&mut self, x: &mut TraitImplRef) {
523        if let Some(new_decl_ref) = self.process_generics(x.id.into(), &x.generics) {
524            *x = new_decl_ref.try_into().unwrap()
525        }
526    }
527}
528
529pub struct Transform;
530impl TransformPass for Transform {
531    fn transform_ctx(&self, ctx: &mut TransformCtx) {
532        let Some(include_types) = ctx.options.monomorphize_mut else {
533            return;
534        };
535        // TODO: test name matcher, also with methods
536        let mut visitor =
537            PartialMonomorphizer::new(ctx, matches!(include_types, MonomorphizeMut::All));
538        while let Some(id) = visitor.to_process.pop_front() {
539            // Get the item corresponding to this id, either by creating it or by getting an
540            // existing one.
541            let mut decl = if visitor.reverse_shape_map.contains_key(&id) {
542                // Create the required item by instantiating the item it's based on.
543                visitor.create_pending_instantiation(id)
544            } else {
545                // Take the item out so we can modify it. Warning: don't look up other items in the
546                // meantime as this would break in recursive cases.
547                match visitor.ctx.translated.remove_item_temporarily(id) {
548                    Some(decl) => decl,
549                    None => continue,
550                }
551            };
552            // Visit the item, replacing type instantiations with references to soon-to-be-created
553            // partially-monomorphized types.
554            visitor.process_item(&mut decl.as_mut());
555            // Put the item back.
556            visitor.ctx.translated.put_item_back(id, decl);
557        }
558    }
559}