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 fundecl becomes inaccessible from `ctx` during the course of this function.
164    pub(crate) fn for_each_fun_decl(&mut self, mut f: impl FnMut(&mut Self, &mut FunDecl)) {
165        let fn_ids = self.translated.fun_decls.all_indices();
166        for id in fn_ids {
167            if let Some(mut decl) = self.translated.fun_decls.remove(id) {
168                let fun_decl_id = decl.def_id;
169                let is_local = decl.item_meta.is_local;
170                self.with_def_id(fun_decl_id, is_local, |ctx| f(ctx, &mut decl));
171                self.translated.fun_decls.set_slot(id, decl);
172            }
173        }
174    }
175
176    /// Iterate mutably over all items, keeping access to `self`. To make this work, we move out
177    /// each item before iterating over it. Items added during traversal will not be iterated over.
178    pub fn for_each_item_mut(&mut self, mut f: impl for<'a> FnMut(&'a mut Self, ItemRefMut<'a>)) {
179        for id in self.translated.all_ids() {
180            if let Some(mut decl) = self.translated.remove_item_temporarily(id) {
181                f(self, decl.as_mut());
182                self.translated.set_item_slot(id, decl);
183            }
184        }
185    }
186}
187
188impl<'a> IntoFormatter for &'a TransformCtx {
189    type C = FmtCtx<'a>;
190
191    fn into_fmt(self) -> Self::C {
192        self.translated.into_fmt()
193    }
194}
195
196impl fmt::Display for TransformCtx {
197    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198        self.translated.fmt(f)
199    }
200}
201
202/// A helper trait that captures common operations in body transformation.
203pub trait BodyTransformCtx: Sized {
204    fn get_crate(&self) -> &TranslatedCrate;
205    fn get_options(&self) -> &TranslateOptions;
206    fn get_params(&self) -> &GenericParams;
207    fn get_locals_mut(&mut self) -> &mut Locals;
208
209    fn insert_storage_live_stmt(&mut self, local: LocalId);
210    fn insert_storage_dead_stmt(&mut self, local: LocalId);
211    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue);
212
213    fn to_fmt(&self) -> FmtCtx<'_> {
214        self.get_crate().into_fmt()
215    }
216
217    /// Create a local & return the place pointing to it
218    fn fresh_var(&mut self, name: Option<String>, ty: Ty) -> Place {
219        let var = self.get_locals_mut().new_var(name, ty);
220        self.insert_storage_live_stmt(var.local_id().unwrap());
221        var
222    }
223
224    /// Assign an rvalue to a place, unless the rvalue is a move in which case we just use the
225    /// moved place.
226    fn rval_to_place(&mut self, rvalue: Rvalue, ty: Ty) -> Place {
227        if let Rvalue::Use(Operand::Move(place), WithRetag::No) = rvalue {
228            place
229        } else {
230            let var = self.fresh_var(None, ty);
231            self.insert_assn_stmt(var.clone(), rvalue);
232            var
233        }
234    }
235
236    /// When `from_end` is true, we need to compute `len(p) - last_arg` instead of just using `last_arg`.
237    /// Otherwise, we simply return `last_arg`.
238    /// New local variables are created as needed.
239    ///
240    /// The `last_arg` is either the `offset` for `Index` or the `to` for `Subslice` for the projections.
241    fn compute_subslice_end_idx(
242        &mut self,
243        len_place: &Place,
244        last_arg: Operand,
245        from_end: bool,
246    ) -> Operand {
247        if from_end {
248            // `storage_live(len_var)`
249            // `len_var = len(p)`
250            let len_var = self.fresh_var(None, Ty::mk_usize());
251            let len = match len_place.ty().kind() {
252                TyKind::Array(_, len) => Some(len.clone()),
253                TyKind::Slice(_) => None,
254                _ => panic!(
255                    "called `compute_subslice_end_idx` on something that isn't an array or slice: {:?}",
256                    len_place.ty()
257                ),
258            };
259            self.insert_assn_stmt(
260                len_var.clone(),
261                Rvalue::Len(len_place.clone(), len_place.ty().clone(), len),
262            );
263
264            // `storage_live(index_var)`
265            // `index_var = len_var - last_arg`
266            // `storage_dead(len_var)`
267            let index_var = self.fresh_var(None, Ty::mk_usize());
268            self.insert_assn_stmt(
269                index_var.clone(),
270                Rvalue::BinaryOp(
271                    BinOp::Sub(OverflowMode::UB),
272                    Operand::Copy(len_var.clone()),
273                    last_arg,
274                ),
275            );
276            self.insert_storage_dead_stmt(len_var.local_id().unwrap());
277            Operand::Copy(index_var)
278        } else {
279            last_arg
280        }
281    }
282
283    fn is_sized_type_var(&mut self, ty: &Ty) -> bool {
284        match ty.kind() {
285            TyKind::TypeVar(..) => {
286                if self.get_options().hide_marker_traits {
287                    // If we're hiding `Sized`, let's consider everything to be sized.
288                    return true;
289                }
290                let params = self.get_params();
291                for clause in &params.trait_clauses {
292                    let tref = clause.trait_.clone().erase();
293                    // Check if it is `Sized<T>`
294                    if tref.generics.types[0] == *ty
295                        && self
296                            .get_crate()
297                            .trait_decls
298                            .get(tref.id)
299                            .and_then(|decl| decl.item_meta.lang_item.clone())
300                            == Some("sized".into())
301                    {
302                        return true;
303                    }
304                }
305                false
306            }
307            _ => false,
308        }
309    }
310
311    /// Emit statements that compute the metadata of the given place. Returns an operand containing the
312    /// metadata value.
313    ///
314    /// E.g., for:
315    /// ```ignore
316    /// let x = &(*ptr).field;
317    /// ```
318    /// if `(*ptr).field` is a DST like `[i32]`, this will get the metadata from the appropriate
319    /// pointer:
320    /// ```ignore
321    /// let len = ptr.metadata;
322    /// ```
323    /// and return `Operand::Move(len)`.
324    ///
325    fn compute_place_metadata(&mut self, place: &Place) -> Operand {
326        /// Compute the metadata for a place. Return `None` if the place has no metadata.
327        fn compute_place_metadata_inner<T: BodyTransformCtx>(
328            ctx: &mut T,
329            place: &Place,
330            metadata_ty: &Ty,
331        ) -> Option<Operand> {
332            let (subplace, proj) = place.as_projection()?;
333            match proj {
334                // The outermost deref we encountered gives us the metadata of the place.
335                ProjectionElem::Deref => {
336                    let metadata_place = subplace
337                        .clone()
338                        .project(ProjectionElem::PtrMetadata, metadata_ty.clone());
339                    Some(Operand::Copy(metadata_place))
340                }
341                ProjectionElem::Field { .. } => {
342                    compute_place_metadata_inner(ctx, subplace, metadata_ty)
343                }
344                // Indexing for array & slice will only result in sized types, hence no metadata
345                ProjectionElem::Index { .. } => None,
346                // Ptr metadata is always sized.
347                ProjectionElem::PtrMetadata => None,
348                // Subslice must have metadata length, compute the metadata here as `to` - `from`
349                ProjectionElem::Subslice { from, to, from_end } => {
350                    let to_idx = ctx.compute_subslice_end_idx(subplace, *to.clone(), *from_end);
351                    let diff_place = ctx.fresh_var(None, Ty::mk_usize());
352                    ctx.insert_assn_stmt(
353                        diff_place.clone(),
354                        // Overflow is UB and should have been prevented by a bound check beforehand.
355                        Rvalue::BinaryOp(BinOp::Sub(OverflowMode::UB), to_idx, *from.clone()),
356                    );
357                    Some(Operand::Copy(diff_place))
358                }
359            }
360        }
361        trace!(
362            "getting ptr metadata for place: {}",
363            place.with_ctx(&self.to_fmt())
364        );
365        let metadata_ty = place.ty().get_ptr_metadata(self.get_crate()).into_type();
366        if metadata_ty.is_unit()
367            || matches!(metadata_ty.kind(), TyKind::PtrMetadata(ty) if self.is_sized_type_var(ty))
368        {
369            // If the type var is known to be `Sized`, then no metadata is needed
370            return Operand::mk_const_unit();
371        }
372        trace!(
373            "computed metadata type: {}",
374            metadata_ty.with_ctx(&self.to_fmt())
375        );
376        compute_place_metadata_inner(self, place, &metadata_ty)
377            .unwrap_or_else(Operand::mk_const_unit)
378    }
379
380    /// Create a `&` borrow of the place.
381    fn borrow(&mut self, place: Place, kind: BorrowKind) -> Rvalue {
382        let ptr_metadata = self.compute_place_metadata(&place);
383        Rvalue::Ref {
384            place,
385            kind,
386            ptr_metadata,
387        }
388    }
389    /// Create a `&raw` borrow of the place.
390    fn raw_borrow(&mut self, place: Place, kind: RefKind) -> Rvalue {
391        let ptr_metadata = self.compute_place_metadata(&place);
392        Rvalue::RawPtr {
393            place,
394            kind,
395            ptr_metadata,
396        }
397    }
398
399    /// Store a `&` borrow of the place into a new place.
400    fn borrow_to_new_var(&mut self, place: Place, kind: BorrowKind, name: Option<String>) -> Place {
401        let ref_ty = TyKind::Ref(Region::Erased, place.ty().clone(), kind.into()).into_ty();
402        let target_place = self.fresh_var(name, ref_ty);
403        let rvalue = self.borrow(place, kind);
404        self.insert_assn_stmt(target_place.clone(), rvalue);
405        target_place
406    }
407    /// Store a `&raw` borrow of the place into a new place.
408    fn raw_borrow_to_new_var(
409        &mut self,
410        place: Place,
411        kind: RefKind,
412        name: Option<String>,
413    ) -> Place {
414        let ref_ty = TyKind::RawPtr(place.ty().clone(), kind).into_ty();
415        let target_place = self.fresh_var(name, ref_ty);
416        let rvalue = self.raw_borrow(place, kind);
417        self.insert_assn_stmt(target_place.clone(), rvalue);
418        target_place
419    }
420}
421
422pub struct UllbcStatementTransformCtx<'a> {
423    pub ctx: &'a mut TransformCtx,
424    pub params: &'a GenericParams,
425    pub locals: &'a mut Locals,
426    /// Span of the statement being explored
427    pub span: Span,
428    /// Statements to prepend to the statement currently being explored.
429    pub statements: Vec<ullbc_ast::Statement>,
430}
431
432impl BodyTransformCtx for UllbcStatementTransformCtx<'_> {
433    fn get_crate(&self) -> &TranslatedCrate {
434        &self.ctx.translated
435    }
436    fn get_options(&self) -> &TranslateOptions {
437        &self.ctx.options
438    }
439    fn get_params(&self) -> &GenericParams {
440        self.params
441    }
442    fn get_locals_mut(&mut self) -> &mut Locals {
443        self.locals
444    }
445
446    fn insert_storage_live_stmt(&mut self, local: LocalId) {
447        self.statements.push(ullbc_ast::Statement::new(
448            self.span,
449            ullbc_ast::StatementKind::StorageLive(local),
450        ));
451    }
452
453    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
454        self.statements.push(ullbc_ast::Statement::new(
455            self.span,
456            ullbc_ast::StatementKind::Assign(place, rvalue),
457        ));
458    }
459
460    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
461        self.statements.push(ullbc_ast::Statement::new(
462            self.span,
463            ullbc_ast::StatementKind::StorageDead(local),
464        ));
465    }
466}
467
468pub struct LlbcStatementTransformCtx<'a> {
469    pub ctx: &'a mut TransformCtx,
470    pub params: &'a GenericParams,
471    pub locals: &'a mut Locals,
472    /// Span of the statement being explored
473    pub span: Span,
474    /// Statements to prepend to the statement currently being explored.
475    pub statements: Vec<llbc_ast::Statement>,
476}
477
478impl BodyTransformCtx for LlbcStatementTransformCtx<'_> {
479    fn get_crate(&self) -> &TranslatedCrate {
480        &self.ctx.translated
481    }
482    fn get_options(&self) -> &TranslateOptions {
483        &self.ctx.options
484    }
485    fn get_params(&self) -> &GenericParams {
486        self.params
487    }
488    fn get_locals_mut(&mut self) -> &mut Locals {
489        self.locals
490    }
491
492    fn insert_storage_live_stmt(&mut self, local: LocalId) {
493        self.statements.push(llbc_ast::Statement::new(
494            self.span,
495            llbc_ast::StatementKind::StorageLive(local),
496        ));
497    }
498
499    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
500        self.statements.push(llbc_ast::Statement::new(
501            self.span,
502            llbc_ast::StatementKind::Assign(place, rvalue),
503        ));
504    }
505
506    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
507        self.statements.push(llbc_ast::Statement::new(
508            self.span,
509            llbc_ast::StatementKind::StorageDead(local),
510        ));
511    }
512}
513
514impl FunDecl {
515    pub fn transform_ullbc_statements(
516        &mut self,
517        ctx: &mut TransformCtx,
518        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Statement),
519    ) {
520        if let Some(body) = self.body.as_unstructured_mut() {
521            let mut ctx = UllbcStatementTransformCtx {
522                ctx,
523                params: &self.generics,
524                locals: &mut body.locals,
525                span: self.item_meta.span,
526                statements: Vec::new(),
527            };
528            body.body.iter_mut().for_each(|block| {
529                ctx.statements = Vec::with_capacity(block.statements.len());
530                for mut st in mem::take(&mut block.statements) {
531                    ctx.span = st.span;
532                    f(&mut ctx, &mut st);
533                    ctx.statements.push(st);
534                }
535                block.statements = mem::take(&mut ctx.statements);
536            });
537        }
538    }
539
540    pub fn transform_ullbc_terminators(
541        &mut self,
542        ctx: &mut TransformCtx,
543        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Terminator),
544    ) {
545        if let Some(body) = self.body.as_unstructured_mut() {
546            let mut ctx = UllbcStatementTransformCtx {
547                ctx,
548                params: &self.generics,
549                locals: &mut body.locals,
550                span: self.item_meta.span,
551                statements: Vec::new(),
552            };
553            body.body.iter_mut().for_each(|block| {
554                ctx.span = block.terminator.span;
555                ctx.statements = mem::take(&mut block.statements);
556                f(&mut ctx, &mut block.terminator);
557                block.statements = mem::take(&mut ctx.statements);
558            });
559        }
560    }
561
562    pub fn transform_ullbc_operands(
563        &mut self,
564        ctx: &mut TransformCtx,
565        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut Operand),
566    ) {
567        self.transform_ullbc_statements(ctx, |ctx, st| {
568            st.kind.dyn_visit_in_body_mut(|op: &mut Operand| f(ctx, op));
569        });
570        self.transform_ullbc_terminators(ctx, |ctx, st| {
571            st.kind.dyn_visit_in_body_mut(|op: &mut Operand| f(ctx, op));
572        });
573    }
574
575    pub fn transform_llbc_statements(
576        &mut self,
577        ctx: &mut TransformCtx,
578        mut f: impl FnMut(&mut LlbcStatementTransformCtx, &mut llbc_ast::Statement),
579    ) {
580        if let Some(body) = self.body.as_structured_mut() {
581            let mut ctx = LlbcStatementTransformCtx {
582                ctx,
583                locals: &mut body.locals,
584                statements: Vec::new(),
585                span: self.item_meta.span,
586                params: &self.generics,
587            };
588            body.body.visit_blocks_bwd(|block: &mut llbc_ast::Block| {
589                ctx.statements = Vec::with_capacity(block.statements.len());
590                for mut st in mem::take(&mut block.statements) {
591                    ctx.span = st.span;
592                    f(&mut ctx, &mut st);
593                    ctx.statements.push(st);
594                }
595                block.statements = mem::take(&mut ctx.statements)
596            })
597        }
598    }
599}