charon_lib/transform/
insert_ptr_metadata.rs

1use crate::formatter::IntoFormatter;
2use crate::pretty::FmtWithCtx;
3use crate::transform::TransformCtx;
4use crate::transform::ctx::BodyTransformCtx;
5use crate::transform::index_to_function_calls::compute_subslice_end_idx;
6use crate::{transform::ctx::UllbcPass, ullbc_ast::*};
7use derive_generic_visitor::*;
8
9#[derive(Visitor)]
10struct BodyVisitor<'a, 'b> {
11    locals: &'a mut Locals,
12    /// Statements to prepend to the statement currently being explored.
13    statements: Vec<Statement>,
14    span: Span,
15    ctx: &'b TransformCtx,
16    params: &'a GenericParams,
17}
18
19fn is_sized_type_var<T: BodyTransformCtxWithParams>(ctx: &mut T, ty: &Ty) -> bool {
20    match ty.kind() {
21        TyKind::TypeVar(..) => {
22            if ctx.get_ctx().options.hide_marker_traits {
23                // If we're hiding `Sized`, let's consider everything to be sized.
24                return true;
25            }
26            let params = ctx.get_params();
27            for clause in &params.trait_clauses {
28                let tref = clause.trait_.clone().erase();
29                // Check if it is `Sized<T>`
30                if tref.generics.types[0] == *ty
31                    && ctx
32                        .get_ctx()
33                        .translated
34                        .trait_decls
35                        .get(tref.id)
36                        .and_then(|decl| decl.item_meta.lang_item.clone())
37                        == Some("sized".into())
38                {
39                    return true;
40                }
41            }
42            false
43        }
44        _ => false,
45    }
46}
47
48/// No metadata. We use the `unit_metadata` global to avoid having to define unit locals
49/// everywhere.
50fn no_metadata<T: BodyTransformCtx>(ctx: &T) -> Operand {
51    let unit_meta = ctx.get_ctx().translated.unit_metadata.clone().unwrap();
52    Operand::Copy(Place::new_global(unit_meta, Ty::mk_unit()))
53}
54
55/// Compute the metadata for a place. Return `None` if the place has no metadata.
56fn compute_place_metadata_inner<T: BodyTransformCtx>(
57    ctx: &mut T,
58    place: &Place,
59    metadata_ty: &Ty,
60) -> Option<Operand> {
61    let (subplace, proj) = place.as_projection()?;
62    match proj {
63        // The outermost deref we encountered gives us the metadata of the place.
64        ProjectionElem::Deref => {
65            let metadata_place = subplace
66                .clone()
67                .project(ProjectionElem::PtrMetadata, metadata_ty.clone());
68            Some(Operand::Copy(metadata_place))
69        }
70        ProjectionElem::Field { .. } => compute_place_metadata_inner(ctx, subplace, metadata_ty),
71        // Indexing for array & slice will only result in sized types, hence no metadata
72        ProjectionElem::Index { .. } => None,
73        // Ptr metadata is always sized.
74        ProjectionElem::PtrMetadata { .. } => None,
75        // Subslice must have metadata length, compute the metadata here as `to` - `from`
76        ProjectionElem::Subslice { from, to, from_end } => {
77            let to_idx = compute_subslice_end_idx(ctx, subplace, *to.clone(), *from_end);
78            let diff_place = ctx.fresh_var(None, Ty::mk_usize());
79            ctx.insert_assn_stmt(
80                diff_place.clone(),
81                // Overflow is UB and should have been prevented by a bound check beforehand.
82                Rvalue::BinaryOp(BinOp::Sub(OverflowMode::UB), to_idx, *from.clone()),
83            );
84            Some(Operand::Copy(diff_place))
85        }
86    }
87}
88
89/// Emit statements that compute the metadata of the given place. Returns an operand containing the
90/// metadata value.
91pub fn compute_place_metadata<T: BodyTransformCtxWithParams>(
92    ctx: &mut T,
93    place: &Place,
94) -> Operand {
95    trace!(
96        "getting ptr metadata for place: {}",
97        place.with_ctx(&ctx.get_ctx().into_fmt())
98    );
99    let metadata_ty = place
100        .ty()
101        .get_ptr_metadata(&ctx.get_ctx().translated)
102        .into_type();
103    if metadata_ty.is_unit()
104        || matches!(metadata_ty.kind(),  TyKind::PtrMetadata(ty) if is_sized_type_var(ctx, ty))
105    {
106        // If the type var is known to be `Sized`, then no metadata is needed
107        return no_metadata(ctx);
108    }
109    trace!(
110        "computed metadata type: {}",
111        metadata_ty.with_ctx(&ctx.get_ctx().into_fmt())
112    );
113    compute_place_metadata_inner(ctx, place, &metadata_ty).unwrap_or_else(|| no_metadata(ctx))
114}
115
116pub trait BodyTransformCtxWithParams: BodyTransformCtx {
117    fn get_params(&self) -> &GenericParams;
118}
119
120impl BodyTransformCtxWithParams for BodyVisitor<'_, '_> {
121    fn get_params(&self) -> &GenericParams {
122        self.params
123    }
124}
125
126impl BodyTransformCtx for BodyVisitor<'_, '_> {
127    fn get_locals_mut(&mut self) -> &mut Locals {
128        self.locals
129    }
130
131    fn insert_storage_live_stmt(&mut self, local: LocalId) {
132        self.statements
133            .push(Statement::new(self.span, StatementKind::StorageLive(local)));
134    }
135
136    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
137        self.statements.push(Statement::new(
138            self.span,
139            StatementKind::Assign(place, rvalue),
140        ));
141    }
142
143    fn get_ctx(&self) -> &TransformCtx {
144        self.ctx
145    }
146
147    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
148        self.statements
149            .push(Statement::new(self.span, StatementKind::StorageDead(local)));
150    }
151}
152
153impl VisitBodyMut for BodyVisitor<'_, '_> {
154    fn visit_rvalue(&mut self, x: &mut Rvalue) -> ::std::ops::ControlFlow<Self::Break> {
155        if let Rvalue::Ref {
156            place,
157            ptr_metadata,
158            ..
159        }
160        | Rvalue::RawPtr {
161            place,
162            ptr_metadata,
163            ..
164        } = x
165        {
166            *ptr_metadata = compute_place_metadata(self, place);
167        }
168        Continue(())
169    }
170}
171
172pub struct Transform;
173
174impl Transform {
175    fn transform_body_with_param(
176        &self,
177        ctx: &mut TransformCtx,
178        b: &mut ExprBody,
179        params: &GenericParams,
180    ) {
181        b.body.iter_mut().for_each(|data| {
182            data.transform(|st: &mut Statement| {
183                let mut visitor = BodyVisitor {
184                    locals: &mut b.locals,
185                    statements: Vec::new(),
186                    span: st.span,
187                    ctx: &ctx,
188                    params,
189                };
190                let _ = st.drive_body_mut(&mut visitor);
191                visitor.statements
192            });
193        });
194    }
195}
196
197/// This pass computes the metadata for Rvalue, which is used to create references and raw pointers.
198/// E.g., in cases like:
199/// ```ignore
200/// let x = &[mut] (*some_v).field;
201/// ```
202/// If the `(*some_v).field` is a DST, like `[i32]`, we will need to fetch the metadata, i.e., the length of the slice,
203/// and store it in a local variable, then we have:
204/// ```ignore
205/// let x = Rvalue::Ref { place:(*some_v).field, kind: [mut], ptr_metadata: PtrMetadata(some_v) };
206/// ```
207/// There should be a new local variable introduced to store `PtrMetadata(some_v)`.
208impl UllbcPass for Transform {
209    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
210        if let Ok(body) = &mut decl.body {
211            self.transform_body_with_param(
212                ctx,
213                body.as_unstructured_mut().unwrap(),
214                &decl.signature.generics,
215            )
216        }
217    }
218}