Skip to main content

charon_lib/transform/
ctx.rs

1use crate::ast::*;
2use crate::errors::{ErrorCtx, Level};
3use crate::formatter::{FmtCtx, IntoFormatter};
4use crate::llbc_ast;
5use crate::options::TranslateOptions;
6use crate::pretty::FmtWithCtx;
7use crate::transform::CowBox;
8use crate::ullbc_ast;
9use std::cell::RefCell;
10use std::{fmt, mem};
11
12/// Simpler context used for rustc-independent code transformation. This only depends on rustc for
13/// its error reporting machinery.
14pub struct TransformCtx {
15    /// The options that control transformation.
16    pub options: TranslateOptions,
17    /// The translated data.
18    pub translated: TranslatedCrate,
19    /// Context for tracking and reporting errors.
20    pub errors: RefCell<ErrorCtx>,
21}
22
23/// A pass that modifies ullbc bodies and can be fused with previous passes so that we run all of
24/// them on a given body.
25pub trait UllbcPass: Sync {
26    /// Whether the pass should run.
27    fn should_run(&self, _options: &TranslateOptions) -> bool {
28        true
29    }
30
31    /// Transform a body.
32    fn transform_body(&self, _ctx: &mut TransformCtx, _body: &mut ullbc_ast::ExprBody) {}
33
34    /// Transform a function declaration. This forwards to `transform_body` by default.
35    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
36        if let Some(body) = decl.body.as_unstructured_mut() {
37            self.transform_body(ctx, body)
38        }
39    }
40
41    /// Transform an item. This forwards to `transform_function` by default.
42    fn transform_item(&self, ctx: &mut TransformCtx, item: ItemRefMut<'_>) {
43        if let ItemRefMut::Fun(decl) = item {
44            self.transform_function(ctx, decl);
45        }
46    }
47
48    /// Some passes carry function bodies, which must also be transformed. This is called before
49    /// the batch of passes starts, with all the passes that come before this one. Rougly only
50    /// useful for passes that inline some functions into others.
51    fn apply_preceding_passes(
52        &mut self,
53        _ctx: &mut TransformCtx,
54        _passes: &[CowBox<dyn UllbcPass>],
55    ) {
56    }
57
58    /// Run after all the fused passes in the current block are done.
59    fn finalize(&self, _ctx: &mut TransformCtx) {}
60
61    /// The name of the pass, used for debug logging. The default implementation uses the type
62    /// name.
63    fn name(&self) -> &str {
64        std::any::type_name::<Self>()
65    }
66}
67
68/// A pass that modifies llbc bodies.
69pub trait LlbcPass: Sync {
70    /// Whether the pass should run.
71    fn should_run(&self, _options: &TranslateOptions) -> bool {
72        true
73    }
74
75    /// Transform a body.
76    fn transform_body(&self, _ctx: &mut TransformCtx, _body: &mut llbc_ast::ExprBody) {}
77
78    /// Transform a function declaration. This forwards to `transform_body` by default.
79    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
80        if let Some(body) = decl.body.as_structured_mut() {
81            self.transform_body(ctx, body)
82        }
83    }
84
85    /// The name of the pass, used for debug logging. The default implementation uses the type
86    /// name.
87    fn name(&self) -> &str {
88        std::any::type_name::<Self>()
89    }
90}
91
92/// A pass that transforms the crate data.
93pub trait TransformPass: Sync {
94    /// Whether the pass should run.
95    fn should_run(&self, _options: &TranslateOptions) -> bool {
96        true
97    }
98
99    fn transform_ctx(&self, ctx: &mut TransformCtx);
100
101    /// The name of the pass, used for debug logging. The default implementation uses the type
102    /// name.
103    fn name(&self) -> &str {
104        std::any::type_name::<Self>()
105    }
106}
107
108impl TransformCtx {
109    pub(crate) fn has_errors(&self) -> bool {
110        self.errors.borrow().has_errors()
111    }
112
113    /// Span an error and register the error.
114    pub(crate) fn span_err(&self, span: Span, msg: &str, level: Level) -> Error {
115        self.errors
116            .borrow_mut()
117            .span_err(&self.translated, span, msg, level)
118    }
119
120    pub(crate) fn opacity_for_name(&self, name: &Name) -> ItemOpacity {
121        self.options.opacity_for_name(&self.translated, name)
122    }
123
124    pub(crate) fn with_def_id<F, T>(
125        &mut self,
126        def_id: impl Into<ItemId>,
127        def_id_is_local: bool,
128        f: F,
129    ) -> T
130    where
131        F: FnOnce(&mut Self) -> T,
132    {
133        let mut errors = self.errors.borrow_mut();
134        let current_def_id = errors.def_id.replace(def_id.into());
135        let current_def_id_is_local = mem::replace(&mut errors.def_id_is_local, def_id_is_local);
136        drop(errors); // important: release the refcell "lock"
137        let ret = f(self);
138        let mut errors = self.errors.borrow_mut();
139        errors.def_id = current_def_id;
140        errors.def_id_is_local = current_def_id_is_local;
141        ret
142    }
143
144    /// Mutably iterate over the bodies.
145    /// Warning: we replace each body with `Err(Opaque)` while inspecting it so we can keep access
146    /// to the rest of the crate.
147    pub(crate) fn for_each_body(&mut self, mut f: impl FnMut(&mut Self, &mut Body)) {
148        let fn_ids = self.translated.fun_decls.all_indices();
149        for id in fn_ids {
150            if let Some(decl) = self.translated.fun_decls.get_mut(id)
151                && decl.body.has_contents()
152            {
153                let mut body = mem::replace(&mut decl.body, Body::Opaque);
154                let fun_decl_id = decl.def_id;
155                let is_local = decl.item_meta.is_local;
156                self.with_def_id(fun_decl_id, is_local, |ctx| f(ctx, &mut body));
157                self.translated.fun_decls[id].body = body;
158            }
159        }
160    }
161
162    /// Mutably iterate over the function declarations.
163    /// Warning: each inspected function declaration becomes inaccessible from `ctx` during the
164    /// course of this function.
165    pub(crate) fn for_each_fun_decl(&mut self, mut f: impl FnMut(&mut Self, &mut FunDecl)) {
166        let fn_ids = self.translated.fun_decls.all_indices();
167        for id in fn_ids {
168            if let Some(mut decl) = self.translated.fun_decls.remove(id) {
169                let fun_decl_id = decl.def_id;
170                let is_local = decl.item_meta.is_local;
171                self.with_def_id(fun_decl_id, is_local, |ctx| f(ctx, &mut decl));
172                self.translated.fun_decls.set_slot(id, decl);
173            }
174        }
175    }
176
177    /// Mutably iterate over the type declarations.
178    /// Warning: each inspected type declaration becomes inaccessible from `ctx` during the course
179    /// of this function.
180    pub(crate) fn for_each_type_decl(&mut self, mut f: impl FnMut(&mut Self, &mut TypeDecl)) {
181        let type_ids = self.translated.type_decls.all_indices();
182        for id in type_ids {
183            if let Some(mut decl) = self.translated.type_decls.remove(id) {
184                let type_decl_id = decl.def_id;
185                let is_local = decl.item_meta.is_local;
186                self.with_def_id(type_decl_id, is_local, |ctx| f(ctx, &mut decl));
187                self.translated.type_decls.set_slot(id, decl);
188            }
189        }
190    }
191
192    /// Iterate mutably over all items, keeping access to `self`. To make this work, we move out
193    /// each item before iterating over it. Items added during traversal will not be iterated over.
194    pub fn for_each_item_mut(&mut self, mut f: impl for<'a> FnMut(&'a mut Self, ItemRefMut<'a>)) {
195        for id in self.translated.all_ids() {
196            if let Some(mut decl) = self.translated.remove_item_temporarily(id) {
197                let is_local = decl.as_ref().item_meta().is_local;
198                self.with_def_id(id, is_local, |ctx| f(ctx, decl.as_mut()));
199                if self.errors.borrow().item_has_errors(id) {
200                    decl.as_mut().item_meta().has_errors = true;
201                }
202                self.translated.put_item_back(id, decl);
203            }
204        }
205    }
206}
207
208impl<'a> IntoFormatter for &'a TransformCtx {
209    type C = FmtCtx<'a>;
210
211    fn into_fmt(self) -> Self::C {
212        self.translated.into_fmt()
213    }
214}
215
216impl fmt::Display for TransformCtx {
217    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218        self.translated.fmt(f)
219    }
220}
221
222/// A helper trait that captures common operations in body transformation.
223pub trait BodyTransformCtx: Sized {
224    fn get_crate(&self) -> &TranslatedCrate;
225    fn get_options(&self) -> &TranslateOptions;
226    fn get_params(&self) -> &GenericParams;
227    fn get_locals_mut(&mut self) -> &mut Locals;
228
229    fn insert_storage_live_stmt(&mut self, local: LocalId);
230    fn insert_storage_dead_stmt(&mut self, local: LocalId);
231    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue);
232
233    fn to_fmt(&self) -> FmtCtx<'_> {
234        self.get_crate().into_fmt()
235    }
236
237    /// Create a local & return the place pointing to it
238    fn fresh_var(&mut self, name: Option<String>, ty: Ty) -> Place {
239        let var = self.get_locals_mut().new_var(name, ty);
240        self.insert_storage_live_stmt(var.local_id().unwrap());
241        var
242    }
243
244    /// Assign an rvalue to a place, unless the rvalue is a move in which case we just use the
245    /// moved place.
246    fn rval_to_place(&mut self, rvalue: Rvalue, ty: Ty) -> Place {
247        if let Rvalue::Use(Operand::Move(place), WithRetag::No) = rvalue {
248            place
249        } else {
250            let var = self.fresh_var(None, ty);
251            self.insert_assn_stmt(var.clone(), rvalue);
252            var
253        }
254    }
255
256    /// When `from_end` is true, we need to compute `len(p) - last_arg` instead of just using `last_arg`.
257    /// Otherwise, we simply return `last_arg`.
258    /// New local variables are created as needed.
259    ///
260    /// The `last_arg` is either the `offset` for `Index` or the `to` for `Subslice` for the projections.
261    fn compute_subslice_end_idx(
262        &mut self,
263        len_place: &Place,
264        last_arg: Operand,
265        from_end: bool,
266    ) -> Operand {
267        if from_end {
268            // `storage_live(len_var)`
269            // `len_var = len(p)`
270            let len_var = self.fresh_var(None, Ty::mk_usize());
271            let len = match len_place.ty().kind() {
272                TyKind::Array(_, len, _) => Some(len.clone()),
273                TyKind::Slice(..) => None,
274                _ => panic!(
275                    "called `compute_subslice_end_idx` on something that isn't an array or slice: {:?}",
276                    len_place.ty()
277                ),
278            };
279            self.insert_assn_stmt(
280                len_var.clone(),
281                Rvalue::Len(len_place.clone(), len_place.ty().clone(), len),
282            );
283
284            // `storage_live(index_var)`
285            // `index_var = len_var - last_arg`
286            // `storage_dead(len_var)`
287            let index_var = self.fresh_var(None, Ty::mk_usize());
288            self.insert_assn_stmt(
289                index_var.clone(),
290                Rvalue::BinaryOp(
291                    BinOp::Sub(OverflowMode::UB),
292                    Operand::Copy(len_var.clone()),
293                    last_arg,
294                ),
295            );
296            self.insert_storage_dead_stmt(len_var.local_id().unwrap());
297            Operand::Copy(index_var)
298        } else {
299            last_arg
300        }
301    }
302
303    fn is_sized_type_var(&mut self, ty: &Ty) -> bool {
304        match ty.kind() {
305            TyKind::TypeVar(..) => {
306                if self.get_options().hide_marker_traits {
307                    // If we're hiding `Sized`, let's consider everything to be sized.
308                    return true;
309                }
310                let params = self.get_params();
311                for clause in &params.trait_clauses {
312                    let tref = clause.trait_.clone().erase();
313                    // Check if it is `Sized<T>`
314                    if tref.generics.types[0] == *ty
315                        && self
316                            .get_crate()
317                            .trait_decls
318                            .get(tref.id)
319                            .and_then(|decl| decl.item_meta.lang_item.as_ref())
320                            == Some(&from_rustc::LangItem::Sized)
321                    {
322                        return true;
323                    }
324                }
325                false
326            }
327            _ => false,
328        }
329    }
330
331    /// Emit statements that compute the metadata of the given place. Returns an operand containing the
332    /// metadata value.
333    ///
334    /// E.g., for:
335    /// ```ignore
336    /// let x = &(*ptr).field;
337    /// ```
338    /// if `(*ptr).field` is a DST like `[i32]`, this will get the metadata from the appropriate
339    /// pointer:
340    /// ```ignore
341    /// let len = ptr.metadata;
342    /// ```
343    /// and return `Operand::Move(len)`.
344    ///
345    fn compute_place_metadata(&mut self, place: &Place) -> Operand {
346        /// Compute the metadata for a place. Return `None` if the place has no metadata.
347        fn compute_place_metadata_inner<T: BodyTransformCtx>(
348            ctx: &mut T,
349            place: &Place,
350            metadata_ty: &Ty,
351        ) -> Option<Operand> {
352            let (subplace, proj) = match &place.kind {
353                PlaceKind::Local(_) => return None,
354                PlaceKind::Global(gref) => {
355                    let global = ctx.get_crate().global_decls.get(gref.id)?;
356                    return Some(global.ptr_metadata.clone());
357                }
358                PlaceKind::Projection(subplace, proj) => (subplace, proj),
359            };
360            match proj {
361                // The outermost deref we encountered gives us the metadata of the place.
362                ProjectionElem::Deref => {
363                    let metadata_place = subplace
364                        .clone()
365                        .project(ProjectionElem::PtrMetadata, metadata_ty.clone());
366                    Some(Operand::Copy(metadata_place))
367                }
368                ProjectionElem::Field { .. } => {
369                    compute_place_metadata_inner(ctx, subplace, metadata_ty)
370                }
371                // Indexing for array & slice will only result in sized types, hence no metadata
372                ProjectionElem::Index { .. } => None,
373                // Ptr metadata is always sized.
374                ProjectionElem::PtrMetadata => None,
375                // Subslice must have metadata length, compute the metadata here as `to` - `from`
376                ProjectionElem::Subslice { from, to, from_end } => {
377                    let to_idx = ctx.compute_subslice_end_idx(subplace, *to.clone(), *from_end);
378                    let diff_place = ctx.fresh_var(None, Ty::mk_usize());
379                    ctx.insert_assn_stmt(
380                        diff_place.clone(),
381                        // Overflow is UB and should have been prevented by a bound check beforehand.
382                        Rvalue::BinaryOp(BinOp::Sub(OverflowMode::UB), to_idx, *from.clone()),
383                    );
384                    Some(Operand::Copy(diff_place))
385                }
386            }
387        }
388        trace!(
389            "getting ptr metadata for place: {}",
390            place.with_ctx(&self.to_fmt())
391        );
392        let metadata_ty = place.ty().get_ptr_metadata(self.get_crate()).into_type();
393        if metadata_ty.is_unit()
394            || matches!(metadata_ty.kind(), TyKind::PtrMetadata(ty) if self.is_sized_type_var(ty))
395        {
396            // If the type var is known to be `Sized`, then no metadata is needed
397            return Operand::mk_const_unit();
398        }
399        trace!(
400            "computed metadata type: {}",
401            metadata_ty.with_ctx(&self.to_fmt())
402        );
403        compute_place_metadata_inner(self, place, &metadata_ty)
404            .unwrap_or_else(Operand::mk_const_unit)
405    }
406
407    /// Create a `&` borrow of the place.
408    fn borrow(&mut self, place: Place, kind: BorrowKind) -> Rvalue {
409        let ptr_metadata = self.compute_place_metadata(&place);
410        Rvalue::Ref {
411            place,
412            kind,
413            ptr_metadata,
414        }
415    }
416    /// Create a `&raw` borrow of the place.
417    fn raw_borrow(&mut self, place: Place, kind: RefKind) -> Rvalue {
418        let ptr_metadata = self.compute_place_metadata(&place);
419        Rvalue::RawPtr {
420            place,
421            kind,
422            ptr_metadata,
423        }
424    }
425
426    /// Store a `&` borrow of the place into a new place.
427    fn borrow_to_new_var(&mut self, place: Place, kind: BorrowKind, name: Option<String>) -> Place {
428        let ref_ty = TyKind::Ref(Region::Erased, place.ty().clone(), kind.into()).into_ty();
429        let target_place = self.fresh_var(name, ref_ty);
430        let rvalue = self.borrow(place, kind);
431        self.insert_assn_stmt(target_place.clone(), rvalue);
432        target_place
433    }
434    /// Store a `&raw` borrow of the place into a new place.
435    fn raw_borrow_to_new_var(
436        &mut self,
437        place: Place,
438        kind: RefKind,
439        name: Option<String>,
440    ) -> Place {
441        let ref_ty = TyKind::RawPtr(place.ty().clone(), kind).into_ty();
442        let target_place = self.fresh_var(name, ref_ty);
443        let rvalue = self.raw_borrow(place, kind);
444        self.insert_assn_stmt(target_place.clone(), rvalue);
445        target_place
446    }
447}
448
449pub struct UllbcStatementTransformCtx<'a> {
450    pub ctx: &'a mut TransformCtx,
451    pub params: &'a GenericParams,
452    pub def_id: &'a FunDeclId,
453    pub locals: &'a mut Locals,
454    /// Span of the statement being explored
455    pub span: Span,
456    /// Statements to prepend to the statement currently being explored.
457    pub statements: Vec<ullbc_ast::Statement>,
458}
459
460impl BodyTransformCtx for UllbcStatementTransformCtx<'_> {
461    fn get_crate(&self) -> &TranslatedCrate {
462        &self.ctx.translated
463    }
464    fn get_options(&self) -> &TranslateOptions {
465        &self.ctx.options
466    }
467    fn get_params(&self) -> &GenericParams {
468        self.params
469    }
470    fn get_locals_mut(&mut self) -> &mut Locals {
471        self.locals
472    }
473
474    fn insert_storage_live_stmt(&mut self, local: LocalId) {
475        self.statements.push(ullbc_ast::Statement::new(
476            self.span,
477            ullbc_ast::StatementKind::StorageLive(local),
478        ));
479    }
480
481    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
482        self.statements.push(ullbc_ast::Statement::new(
483            self.span,
484            ullbc_ast::StatementKind::Assign(place, rvalue),
485        ));
486    }
487
488    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
489        self.statements.push(ullbc_ast::Statement::new(
490            self.span,
491            ullbc_ast::StatementKind::StorageDead(local),
492        ));
493    }
494}
495
496pub struct LlbcStatementTransformCtx<'a> {
497    pub ctx: &'a mut TransformCtx,
498    pub params: &'a GenericParams,
499    pub locals: &'a mut Locals,
500    /// Span of the statement being explored
501    pub span: Span,
502    /// Statements to prepend to the statement currently being explored.
503    pub statements: Vec<llbc_ast::Statement>,
504}
505
506impl BodyTransformCtx for LlbcStatementTransformCtx<'_> {
507    fn get_crate(&self) -> &TranslatedCrate {
508        &self.ctx.translated
509    }
510    fn get_options(&self) -> &TranslateOptions {
511        &self.ctx.options
512    }
513    fn get_params(&self) -> &GenericParams {
514        self.params
515    }
516    fn get_locals_mut(&mut self) -> &mut Locals {
517        self.locals
518    }
519
520    fn insert_storage_live_stmt(&mut self, local: LocalId) {
521        self.statements.push(llbc_ast::Statement::new(
522            self.span,
523            llbc_ast::StatementKind::StorageLive(local),
524        ));
525    }
526
527    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
528        self.statements.push(llbc_ast::Statement::new(
529            self.span,
530            llbc_ast::StatementKind::Assign(place, rvalue),
531        ));
532    }
533
534    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
535        self.statements.push(llbc_ast::Statement::new(
536            self.span,
537            llbc_ast::StatementKind::StorageDead(local),
538        ));
539    }
540}
541
542impl FunDecl {
543    pub fn transform_ullbc_statements(
544        &mut self,
545        ctx: &mut TransformCtx,
546        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Statement),
547    ) {
548        if let Some(body) = self.body.as_unstructured_mut() {
549            let mut ctx = UllbcStatementTransformCtx {
550                ctx,
551                params: &self.generics,
552                def_id: &self.def_id,
553                locals: &mut body.locals,
554                span: self.item_meta.span,
555                statements: Vec::new(),
556            };
557            body.body.iter_mut().for_each(|block| {
558                ctx.statements = Vec::with_capacity(block.statements.len());
559                for mut st in mem::take(&mut block.statements) {
560                    ctx.span = st.span;
561                    f(&mut ctx, &mut st);
562                    ctx.statements.push(st);
563                }
564                block.statements = mem::take(&mut ctx.statements);
565            });
566        }
567    }
568
569    pub fn transform_ullbc_terminators(
570        &mut self,
571        ctx: &mut TransformCtx,
572        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Terminator),
573    ) {
574        if let Some(body) = self.body.as_unstructured_mut() {
575            let mut ctx = UllbcStatementTransformCtx {
576                ctx,
577                params: &self.generics,
578                def_id: &self.def_id,
579                locals: &mut body.locals,
580                span: self.item_meta.span,
581                statements: Vec::new(),
582            };
583            body.body.iter_mut().for_each(|block| {
584                ctx.span = block.terminator.span;
585                ctx.statements = mem::take(&mut block.statements);
586                f(&mut ctx, &mut block.terminator);
587                block.statements = mem::take(&mut ctx.statements);
588            });
589        }
590    }
591
592    pub fn transform_ullbc_operands(
593        &mut self,
594        ctx: &mut TransformCtx,
595        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut Operand),
596    ) {
597        self.transform_ullbc_statements(ctx, |ctx, st| {
598            st.kind.dyn_visit_in_body_mut(|op: &mut Operand| f(ctx, op));
599        });
600        self.transform_ullbc_terminators(ctx, |ctx, st| {
601            st.kind.dyn_visit_in_body_mut(|op: &mut Operand| f(ctx, op));
602        });
603    }
604
605    pub fn transform_llbc_statements(
606        &mut self,
607        ctx: &mut TransformCtx,
608        mut f: impl FnMut(&mut LlbcStatementTransformCtx, &mut llbc_ast::Statement),
609    ) {
610        if let Some(body) = self.body.as_structured_mut() {
611            let mut ctx = LlbcStatementTransformCtx {
612                ctx,
613                locals: &mut body.locals,
614                statements: Vec::new(),
615                span: self.item_meta.span,
616                params: &self.generics,
617            };
618            body.body.visit_blocks_bwd(|block: &mut llbc_ast::Block| {
619                ctx.statements = Vec::with_capacity(block.statements.len());
620                for mut st in mem::take(&mut block.statements) {
621                    ctx.span = st.span;
622                    f(&mut ctx, &mut st);
623                    ctx.statements.push(st);
624                }
625                block.statements = mem::take(&mut ctx.statements)
626            })
627        }
628    }
629}