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    /// Transform a body.
25    fn transform_body(&self, _ctx: &mut TransformCtx, _body: &mut ullbc_ast::ExprBody) {}
26
27    /// Transform a function declaration. This forwards to `transform_body` by default.
28    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
29        if let Ok(body) = &mut decl.body {
30            self.transform_body(ctx, body.as_unstructured_mut().unwrap())
31        }
32    }
33
34    /// Transform the given context. This forwards to the other methods by default.
35    fn transform_ctx(&self, ctx: &mut TransformCtx) {
36        ctx.for_each_fun_decl(|ctx, decl| {
37            let body = decl
38                .body
39                .as_mut()
40                .map(|body| body.as_unstructured_mut().unwrap())
41                .map_err(|opaque| *opaque);
42            self.log_before_body(ctx, &decl.item_meta.name, body.as_deref());
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(
55        &self,
56        ctx: &TransformCtx,
57        name: &Name,
58        body: Result<&ullbc_ast::ExprBody, &Opaque>,
59    ) {
60        let fmt_ctx = &ctx.into_fmt();
61        let body_str = if let Ok(body) = body {
62            body.to_string_with_ctx(fmt_ctx)
63        } else {
64            "<opaque>".to_owned()
65        };
66        trace!(
67            "# About to run pass [{}] on `{}`:\n{}",
68            self.name(),
69            name.with_ctx(fmt_ctx),
70            body_str,
71        );
72    }
73}
74
75/// A pass that modifies llbc bodies.
76pub trait LlbcPass: Sync {
77    /// Transform a body.
78    fn transform_body(&self, _ctx: &mut TransformCtx, _body: &mut llbc_ast::ExprBody) {}
79
80    /// Transform a function declaration. This forwards to `transform_body` by default.
81    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
82        if let Ok(body) = &mut decl.body {
83            self.transform_body(ctx, body.as_structured_mut().unwrap())
84        }
85    }
86
87    /// Transform the given context. This forwards to the other methods by default.
88    fn transform_ctx(&self, ctx: &mut TransformCtx) {
89        ctx.for_each_fun_decl(|ctx, decl| {
90            let body = decl
91                .body
92                .as_mut()
93                .map(|body| body.as_structured_mut().unwrap())
94                .map_err(|opaque| *opaque);
95            self.log_before_body(ctx, &decl.item_meta.name, body.as_deref());
96            self.transform_function(ctx, decl);
97        });
98    }
99
100    /// The name of the pass, used for debug logging. The default implementation uses the type
101    /// name.
102    fn name(&self) -> &str {
103        std::any::type_name::<Self>()
104    }
105
106    /// Log that the pass is about to be run on this body.
107    fn log_before_body(
108        &self,
109        ctx: &TransformCtx,
110        name: &Name,
111        body: Result<&llbc_ast::ExprBody, &Opaque>,
112    ) {
113        let fmt_ctx = &ctx.into_fmt();
114        let body_str = if let Ok(body) = body {
115            body.to_string_with_ctx(fmt_ctx)
116        } else {
117            "<opaque>".to_owned()
118        };
119        trace!(
120            "# About to run pass [{}] on `{}`:\n{}",
121            self.name(),
122            name.with_ctx(fmt_ctx),
123            body_str,
124        );
125    }
126}
127
128/// A pass that transforms the crate data.
129pub trait TransformPass: Sync {
130    fn transform_ctx(&self, ctx: &mut TransformCtx);
131
132    /// The name of the pass, used for debug logging. The default implementation uses the type
133    /// name.
134    fn name(&self) -> &str {
135        std::any::type_name::<Self>()
136    }
137}
138
139impl<'ctx> TransformCtx {
140    pub(crate) fn has_errors(&self) -> bool {
141        self.errors.borrow().has_errors()
142    }
143
144    /// Span an error and register the error.
145    pub(crate) fn span_err(&self, span: Span, msg: &str, level: Level) -> Error {
146        self.errors
147            .borrow_mut()
148            .span_err(&self.translated, span, msg, level)
149    }
150
151    pub(crate) fn opacity_for_name(&self, name: &Name) -> ItemOpacity {
152        self.options.opacity_for_name(&self.translated, name)
153    }
154
155    pub(crate) fn with_def_id<F, T>(
156        &mut self,
157        def_id: impl Into<ItemId>,
158        def_id_is_local: bool,
159        f: F,
160    ) -> T
161    where
162        F: FnOnce(&mut Self) -> T,
163    {
164        let mut errors = self.errors.borrow_mut();
165        let current_def_id = mem::replace(&mut errors.def_id, Some(def_id.into()));
166        let current_def_id_is_local = mem::replace(&mut errors.def_id_is_local, def_id_is_local);
167        drop(errors); // important: release the refcell "lock"
168        let ret = f(self);
169        let mut errors = self.errors.borrow_mut();
170        errors.def_id = current_def_id;
171        errors.def_id_is_local = current_def_id_is_local;
172        ret
173    }
174
175    /// Mutably iterate over the bodies.
176    /// Warning: we replace each body with `Err(Opaque)` while inspecting it so we can keep access
177    /// to the rest of the crate.
178    pub(crate) fn for_each_body(&mut self, mut f: impl FnMut(&mut Self, &mut Body)) {
179        let fn_ids = self.translated.fun_decls.all_indices();
180        for id in fn_ids {
181            if let Some(decl) = self.translated.fun_decls.get_mut(id) {
182                if let Ok(mut body) = mem::replace(&mut decl.body, Err(Opaque)) {
183                    let fun_decl_id = decl.def_id;
184                    let is_local = decl.item_meta.is_local;
185                    self.with_def_id(fun_decl_id, is_local, |ctx| f(ctx, &mut body));
186                    self.translated.fun_decls[id].body = Ok(body);
187                }
188            }
189        }
190    }
191
192    /// Mutably iterate over the function declarations.
193    /// Warning: each inspected fundecl becomes inaccessible from `ctx` during the course of this function.
194    pub(crate) fn for_each_fun_decl(&mut self, mut f: impl FnMut(&mut Self, &mut FunDecl)) {
195        let fn_ids = self.translated.fun_decls.all_indices();
196        for id in fn_ids {
197            if let Some(mut decl) = self.translated.fun_decls.remove(id) {
198                let fun_decl_id = decl.def_id;
199                let is_local = decl.item_meta.is_local;
200                self.with_def_id(fun_decl_id, is_local, |ctx| f(ctx, &mut decl));
201                self.translated.fun_decls.set_slot(id, decl);
202            }
203        }
204    }
205
206    /// Iterate mutably over all items, keeping access to `self`. To make this work, we move out
207    /// each item before iterating over it.
208    pub fn for_each_item_mut(&mut self, mut f: impl for<'a> FnMut(&'a mut Self, ItemRefMut<'a>)) {
209        macro_rules! for_each {
210            ($vector:ident, $kind:ident) => {
211                for id in self.translated.$vector.all_indices() {
212                    if let Some(mut decl) = self.translated.$vector.remove(id) {
213                        f(self, ItemRefMut::$kind(&mut decl));
214                        self.translated.$vector.set_slot(id, decl);
215                    }
216                }
217            };
218        }
219        for_each!(type_decls, Type);
220        for_each!(fun_decls, Fun);
221        for_each!(global_decls, Global);
222        for_each!(trait_decls, TraitDecl);
223        for_each!(trait_impls, TraitImpl);
224    }
225}
226
227impl<'a> IntoFormatter for &'a TransformCtx {
228    type C = FmtCtx<'a>;
229
230    fn into_fmt(self) -> Self::C {
231        self.translated.into_fmt()
232    }
233}
234
235impl fmt::Display for TransformCtx {
236    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
237        self.translated.fmt(f)
238    }
239}
240
241/// A helper trait that captures the usual operation in body transformation.
242pub trait BodyTransformCtx: Sized {
243    fn get_ctx(&self) -> &TransformCtx;
244    fn get_params(&self) -> &GenericParams;
245    fn get_locals_mut(&mut self) -> &mut Locals;
246
247    fn insert_storage_live_stmt(&mut self, local: LocalId);
248    fn insert_storage_dead_stmt(&mut self, local: LocalId);
249    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue);
250
251    /// Create a local & return the place pointing to it
252    fn fresh_var(&mut self, name: Option<String>, ty: Ty) -> Place {
253        let var = self.get_locals_mut().new_var(name, ty);
254        self.insert_storage_live_stmt(var.local_id().unwrap());
255        var
256    }
257
258    /// When `from_end` is true, we need to compute `len(p) - last_arg` instead of just using `last_arg`.
259    /// Otherwise, we simply return `last_arg`.
260    /// New local variables are created as needed.
261    ///
262    /// The `last_arg` is either the `offset` for `Index` or the `to` for `Subslice` for the projections.
263    fn compute_subslice_end_idx(
264        &mut self,
265        len_place: &Place,
266        last_arg: Operand,
267        from_end: bool,
268    ) -> Operand {
269        if from_end {
270            // `storage_live(len_var)`
271            // `len_var = len(p)`
272            let len_var = self.fresh_var(None, Ty::mk_usize());
273            self.insert_assn_stmt(
274                len_var.clone(),
275                Rvalue::Len(
276                    len_place.clone(),
277                    len_place.ty().clone(),
278                    len_place
279                        .ty()
280                        .as_adt()
281                        .unwrap()
282                        .generics
283                        .const_generics
284                        .get(0.into())
285                        .cloned(),
286                ),
287            );
288
289            // `storage_live(index_var)`
290            // `index_var = len_var - last_arg`
291            // `storage_dead(len_var)`
292            let index_var = self.fresh_var(None, Ty::mk_usize());
293            self.insert_assn_stmt(
294                index_var.clone(),
295                Rvalue::BinaryOp(
296                    BinOp::Sub(OverflowMode::UB),
297                    Operand::Copy(len_var.clone()),
298                    last_arg,
299                ),
300            );
301            self.insert_storage_dead_stmt(len_var.local_id().unwrap());
302            Operand::Copy(index_var)
303        } else {
304            last_arg
305        }
306    }
307
308    fn is_sized_type_var(&mut self, ty: &Ty) -> bool {
309        match ty.kind() {
310            TyKind::TypeVar(..) => {
311                if self.get_ctx().options.hide_marker_traits {
312                    // If we're hiding `Sized`, let's consider everything to be sized.
313                    return true;
314                }
315                let params = self.get_params();
316                for clause in &params.trait_clauses {
317                    let tref = clause.trait_.clone().erase();
318                    // Check if it is `Sized<T>`
319                    if tref.generics.types[0] == *ty
320                        && self
321                            .get_ctx()
322                            .translated
323                            .trait_decls
324                            .get(tref.id)
325                            .and_then(|decl| decl.item_meta.lang_item.clone())
326                            == Some("sized".into())
327                    {
328                        return true;
329                    }
330                }
331                false
332            }
333            _ => false,
334        }
335    }
336
337    /// Emit statements that compute the metadata of the given place. Returns an operand containing the
338    /// metadata value.
339    fn compute_place_metadata(&mut self, place: &Place) -> Operand {
340        /// No metadata. We use the `unit_metadata` global to avoid having to define unit locals
341        /// everywhere.
342        fn no_metadata<T: BodyTransformCtx>(ctx: &T) -> Operand {
343            let unit_meta = ctx.get_ctx().translated.unit_metadata.clone().unwrap();
344            Operand::Copy(Place::new_global(unit_meta, Ty::mk_unit()))
345        }
346
347        /// Compute the metadata for a place. Return `None` if the place has no metadata.
348        fn compute_place_metadata_inner<T: BodyTransformCtx>(
349            ctx: &mut T,
350            place: &Place,
351            metadata_ty: &Ty,
352        ) -> Option<Operand> {
353            let (subplace, proj) = place.as_projection()?;
354            match proj {
355                // The outermost deref we encountered gives us the metadata of the place.
356                ProjectionElem::Deref => {
357                    let metadata_place = subplace
358                        .clone()
359                        .project(ProjectionElem::PtrMetadata, metadata_ty.clone());
360                    Some(Operand::Copy(metadata_place))
361                }
362                ProjectionElem::Field { .. } => {
363                    compute_place_metadata_inner(ctx, subplace, metadata_ty)
364                }
365                // Indexing for array & slice will only result in sized types, hence no metadata
366                ProjectionElem::Index { .. } => None,
367                // Ptr metadata is always sized.
368                ProjectionElem::PtrMetadata { .. } => None,
369                // Subslice must have metadata length, compute the metadata here as `to` - `from`
370                ProjectionElem::Subslice { from, to, from_end } => {
371                    let to_idx = ctx.compute_subslice_end_idx(subplace, *to.clone(), *from_end);
372                    let diff_place = ctx.fresh_var(None, Ty::mk_usize());
373                    ctx.insert_assn_stmt(
374                        diff_place.clone(),
375                        // Overflow is UB and should have been prevented by a bound check beforehand.
376                        Rvalue::BinaryOp(BinOp::Sub(OverflowMode::UB), to_idx, *from.clone()),
377                    );
378                    Some(Operand::Copy(diff_place))
379                }
380            }
381        }
382        trace!(
383            "getting ptr metadata for place: {}",
384            place.with_ctx(&self.get_ctx().into_fmt())
385        );
386        let metadata_ty = place
387            .ty()
388            .get_ptr_metadata(&self.get_ctx().translated)
389            .into_type();
390        if metadata_ty.is_unit()
391            || matches!(metadata_ty.kind(), TyKind::PtrMetadata(ty) if self.is_sized_type_var(ty))
392        {
393            // If the type var is known to be `Sized`, then no metadata is needed
394            return no_metadata(self);
395        }
396        trace!(
397            "computed metadata type: {}",
398            metadata_ty.with_ctx(&self.get_ctx().into_fmt())
399        );
400        compute_place_metadata_inner(self, place, &metadata_ty).unwrap_or_else(|| no_metadata(self))
401    }
402}
403
404pub struct UllbcStatementTransformCtx<'a> {
405    pub ctx: &'a mut TransformCtx,
406    pub params: &'a GenericParams,
407    pub locals: &'a mut Locals,
408    /// Span of the statement being explored
409    pub span: Span,
410    /// Statements to prepend to the statement currently being explored.
411    pub statements: Vec<ullbc_ast::Statement>,
412}
413
414impl BodyTransformCtx for UllbcStatementTransformCtx<'_> {
415    fn get_ctx(&self) -> &TransformCtx {
416        self.ctx
417    }
418    fn get_params(&self) -> &GenericParams {
419        self.params
420    }
421    fn get_locals_mut(&mut self) -> &mut Locals {
422        self.locals
423    }
424
425    fn insert_storage_live_stmt(&mut self, local: LocalId) {
426        self.statements.push(ullbc_ast::Statement::new(
427            self.span,
428            ullbc_ast::StatementKind::StorageLive(local),
429        ));
430    }
431
432    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
433        self.statements.push(ullbc_ast::Statement::new(
434            self.span,
435            ullbc_ast::StatementKind::Assign(place, rvalue),
436        ));
437    }
438
439    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
440        self.statements.push(ullbc_ast::Statement::new(
441            self.span,
442            ullbc_ast::StatementKind::StorageDead(local),
443        ));
444    }
445}
446
447impl FunDecl {
448    pub fn transform_ullbc_terminators(
449        &mut self,
450        ctx: &mut TransformCtx,
451        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Terminator),
452    ) {
453        if let Ok(body) = &mut self.body {
454            let params = &self.signature.generics;
455            let body = body.as_unstructured_mut().unwrap();
456            body.body.iter_mut().for_each(|block| {
457                let span = block.terminator.span;
458                let mut ctx = UllbcStatementTransformCtx {
459                    ctx,
460                    params,
461                    locals: &mut body.locals,
462                    span,
463                    statements: std::mem::take(&mut block.statements),
464                };
465                f(&mut ctx, &mut block.terminator);
466                block.statements = ctx.statements;
467            });
468        }
469    }
470
471    pub fn transform_ullbc_statements(
472        &mut self,
473        ctx: &mut TransformCtx,
474        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Statement),
475    ) {
476        if let Ok(body) = &mut self.body {
477            let params = &self.signature.generics;
478            let body = body.as_unstructured_mut().unwrap();
479            body.body.iter_mut().for_each(|block| {
480                block.transform(|st: &mut ullbc_ast::Statement| {
481                    let mut ctx = UllbcStatementTransformCtx {
482                        ctx,
483                        params,
484                        locals: &mut body.locals,
485                        span: st.span,
486                        statements: Vec::new(),
487                    };
488                    f(&mut ctx, st);
489                    ctx.statements
490                });
491            });
492        }
493    }
494}