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. Items added during traversal will not be iterated over.
208    pub fn for_each_item_mut(&mut self, mut f: impl for<'a> FnMut(&'a mut Self, ItemRefMut<'a>)) {
209        for id in self.translated.all_ids() {
210            if let Some(mut decl) = self.translated.remove_item(id) {
211                f(self, decl.as_mut());
212                self.translated.set_item_slot(id, decl);
213            }
214        }
215    }
216}
217
218impl<'a> IntoFormatter for &'a TransformCtx {
219    type C = FmtCtx<'a>;
220
221    fn into_fmt(self) -> Self::C {
222        self.translated.into_fmt()
223    }
224}
225
226impl fmt::Display for TransformCtx {
227    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228        self.translated.fmt(f)
229    }
230}
231
232/// A helper trait that captures the usual operation in body transformation.
233pub trait BodyTransformCtx: Sized {
234    fn get_ctx(&self) -> &TransformCtx;
235    fn get_params(&self) -> &GenericParams;
236    fn get_locals_mut(&mut self) -> &mut Locals;
237
238    fn insert_storage_live_stmt(&mut self, local: LocalId);
239    fn insert_storage_dead_stmt(&mut self, local: LocalId);
240    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue);
241
242    /// Create a local & return the place pointing to it
243    fn fresh_var(&mut self, name: Option<String>, ty: Ty) -> Place {
244        let var = self.get_locals_mut().new_var(name, ty);
245        self.insert_storage_live_stmt(var.local_id().unwrap());
246        var
247    }
248
249    /// When `from_end` is true, we need to compute `len(p) - last_arg` instead of just using `last_arg`.
250    /// Otherwise, we simply return `last_arg`.
251    /// New local variables are created as needed.
252    ///
253    /// The `last_arg` is either the `offset` for `Index` or the `to` for `Subslice` for the projections.
254    fn compute_subslice_end_idx(
255        &mut self,
256        len_place: &Place,
257        last_arg: Operand,
258        from_end: bool,
259    ) -> Operand {
260        if from_end {
261            // `storage_live(len_var)`
262            // `len_var = len(p)`
263            let len_var = self.fresh_var(None, Ty::mk_usize());
264            self.insert_assn_stmt(
265                len_var.clone(),
266                Rvalue::Len(
267                    len_place.clone(),
268                    len_place.ty().clone(),
269                    len_place
270                        .ty()
271                        .as_adt()
272                        .unwrap()
273                        .generics
274                        .const_generics
275                        .get(0.into())
276                        .cloned(),
277                ),
278            );
279
280            // `storage_live(index_var)`
281            // `index_var = len_var - last_arg`
282            // `storage_dead(len_var)`
283            let index_var = self.fresh_var(None, Ty::mk_usize());
284            self.insert_assn_stmt(
285                index_var.clone(),
286                Rvalue::BinaryOp(
287                    BinOp::Sub(OverflowMode::UB),
288                    Operand::Copy(len_var.clone()),
289                    last_arg,
290                ),
291            );
292            self.insert_storage_dead_stmt(len_var.local_id().unwrap());
293            Operand::Copy(index_var)
294        } else {
295            last_arg
296        }
297    }
298
299    fn is_sized_type_var(&mut self, ty: &Ty) -> bool {
300        match ty.kind() {
301            TyKind::TypeVar(..) => {
302                if self.get_ctx().options.hide_marker_traits {
303                    // If we're hiding `Sized`, let's consider everything to be sized.
304                    return true;
305                }
306                let params = self.get_params();
307                for clause in &params.trait_clauses {
308                    let tref = clause.trait_.clone().erase();
309                    // Check if it is `Sized<T>`
310                    if tref.generics.types[0] == *ty
311                        && self
312                            .get_ctx()
313                            .translated
314                            .trait_decls
315                            .get(tref.id)
316                            .and_then(|decl| decl.item_meta.lang_item.clone())
317                            == Some("sized".into())
318                    {
319                        return true;
320                    }
321                }
322                false
323            }
324            _ => false,
325        }
326    }
327
328    /// Emit statements that compute the metadata of the given place. Returns an operand containing the
329    /// metadata value.
330    fn compute_place_metadata(&mut self, place: &Place) -> Operand {
331        /// No metadata. We use the `unit_metadata` global to avoid having to define unit locals
332        /// everywhere.
333        fn no_metadata<T: BodyTransformCtx>(ctx: &T) -> Operand {
334            let unit_meta = ctx.get_ctx().translated.unit_metadata.clone().unwrap();
335            Operand::Copy(Place::new_global(unit_meta, Ty::mk_unit()))
336        }
337
338        /// Compute the metadata for a place. Return `None` if the place has no metadata.
339        fn compute_place_metadata_inner<T: BodyTransformCtx>(
340            ctx: &mut T,
341            place: &Place,
342            metadata_ty: &Ty,
343        ) -> Option<Operand> {
344            let (subplace, proj) = place.as_projection()?;
345            match proj {
346                // The outermost deref we encountered gives us the metadata of the place.
347                ProjectionElem::Deref => {
348                    let metadata_place = subplace
349                        .clone()
350                        .project(ProjectionElem::PtrMetadata, metadata_ty.clone());
351                    Some(Operand::Copy(metadata_place))
352                }
353                ProjectionElem::Field { .. } => {
354                    compute_place_metadata_inner(ctx, subplace, metadata_ty)
355                }
356                // Indexing for array & slice will only result in sized types, hence no metadata
357                ProjectionElem::Index { .. } => None,
358                // Ptr metadata is always sized.
359                ProjectionElem::PtrMetadata { .. } => None,
360                // Subslice must have metadata length, compute the metadata here as `to` - `from`
361                ProjectionElem::Subslice { from, to, from_end } => {
362                    let to_idx = ctx.compute_subslice_end_idx(subplace, *to.clone(), *from_end);
363                    let diff_place = ctx.fresh_var(None, Ty::mk_usize());
364                    ctx.insert_assn_stmt(
365                        diff_place.clone(),
366                        // Overflow is UB and should have been prevented by a bound check beforehand.
367                        Rvalue::BinaryOp(BinOp::Sub(OverflowMode::UB), to_idx, *from.clone()),
368                    );
369                    Some(Operand::Copy(diff_place))
370                }
371            }
372        }
373        trace!(
374            "getting ptr metadata for place: {}",
375            place.with_ctx(&self.get_ctx().into_fmt())
376        );
377        let metadata_ty = place
378            .ty()
379            .get_ptr_metadata(&self.get_ctx().translated)
380            .into_type();
381        if metadata_ty.is_unit()
382            || matches!(metadata_ty.kind(), TyKind::PtrMetadata(ty) if self.is_sized_type_var(ty))
383        {
384            // If the type var is known to be `Sized`, then no metadata is needed
385            return no_metadata(self);
386        }
387        trace!(
388            "computed metadata type: {}",
389            metadata_ty.with_ctx(&self.get_ctx().into_fmt())
390        );
391        compute_place_metadata_inner(self, place, &metadata_ty).unwrap_or_else(|| no_metadata(self))
392    }
393}
394
395pub struct UllbcStatementTransformCtx<'a> {
396    pub ctx: &'a mut TransformCtx,
397    pub params: &'a GenericParams,
398    pub locals: &'a mut Locals,
399    /// Span of the statement being explored
400    pub span: Span,
401    /// Statements to prepend to the statement currently being explored.
402    pub statements: Vec<ullbc_ast::Statement>,
403}
404
405impl BodyTransformCtx for UllbcStatementTransformCtx<'_> {
406    fn get_ctx(&self) -> &TransformCtx {
407        self.ctx
408    }
409    fn get_params(&self) -> &GenericParams {
410        self.params
411    }
412    fn get_locals_mut(&mut self) -> &mut Locals {
413        self.locals
414    }
415
416    fn insert_storage_live_stmt(&mut self, local: LocalId) {
417        self.statements.push(ullbc_ast::Statement::new(
418            self.span,
419            ullbc_ast::StatementKind::StorageLive(local),
420        ));
421    }
422
423    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
424        self.statements.push(ullbc_ast::Statement::new(
425            self.span,
426            ullbc_ast::StatementKind::Assign(place, rvalue),
427        ));
428    }
429
430    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
431        self.statements.push(ullbc_ast::Statement::new(
432            self.span,
433            ullbc_ast::StatementKind::StorageDead(local),
434        ));
435    }
436}
437
438impl FunDecl {
439    pub fn transform_ullbc_terminators(
440        &mut self,
441        ctx: &mut TransformCtx,
442        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Terminator),
443    ) {
444        if let Ok(body) = &mut self.body {
445            let params = &self.signature.generics;
446            let body = body.as_unstructured_mut().unwrap();
447            body.body.iter_mut().for_each(|block| {
448                let span = block.terminator.span;
449                let mut ctx = UllbcStatementTransformCtx {
450                    ctx,
451                    params,
452                    locals: &mut body.locals,
453                    span,
454                    statements: std::mem::take(&mut block.statements),
455                };
456                f(&mut ctx, &mut block.terminator);
457                block.statements = ctx.statements;
458            });
459        }
460    }
461
462    pub fn transform_ullbc_statements(
463        &mut self,
464        ctx: &mut TransformCtx,
465        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Statement),
466    ) {
467        if let Ok(body) = &mut self.body {
468            let params = &self.signature.generics;
469            let body = body.as_unstructured_mut().unwrap();
470            body.body.iter_mut().for_each(|block| {
471                block.transform(|st: &mut ullbc_ast::Statement| {
472                    let mut ctx = UllbcStatementTransformCtx {
473                        ctx,
474                        params,
475                        locals: &mut body.locals,
476                        span: st.span,
477                        statements: Vec::new(),
478                    };
479                    f(&mut ctx, st);
480                    ctx.statements
481                });
482            });
483        }
484    }
485}