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