charon_lib/transform/
index_to_function_calls.rs

1//! Desugar array/slice index operations to function calls.
2use crate::llbc_ast::*;
3use crate::transform::TransformCtx;
4use derive_generic_visitor::*;
5
6use super::ctx::LlbcPass;
7
8/// We replace some place constructors with function calls. To do that, we explore all the places
9/// in a body and deconstruct a given place access into intermediate assignments.
10///
11/// We accumulate the new assignments as statements in the visitor, and at the end we insert these
12/// statements before the one that was just explored.
13#[derive(Visitor)]
14struct IndexVisitor<'a> {
15    locals: &'a mut Locals,
16    /// Statements to prepend to the statement currently being explored.
17    statements: Vec<Statement>,
18    // When we visit a place, we need to know if it is being accessed mutably or not. Whenever we
19    // visit something that contains a place we push the relevant mutability on this stack.
20    // Unfortunately this requires us to be very careful to catch all the cases where we see
21    // places.
22    place_mutability_stack: Vec<bool>,
23    // Span of the statement.
24    span: Span,
25}
26
27impl<'a> IndexVisitor<'a> {
28    fn fresh_var(&mut self, name: Option<String>, ty: Ty) -> Place {
29        let var = self.locals.new_var(name, ty);
30        let live_kind = RawStatement::StorageLive(var.local_id());
31        self.statements.push(Statement::new(self.span, live_kind));
32        var
33    }
34
35    fn transform_place(&mut self, mut_access: bool, place: &mut Place) {
36        use ProjectionElem::*;
37        let Some((subplace, pe @ (Index { .. } | Subslice { .. }))) = place.as_projection() else {
38            return;
39        };
40        let tref = subplace.ty.as_adt().unwrap();
41        let builtin_ty = tref.id.as_builtin().unwrap();
42
43        // The built-in function to call.
44        let indexing_function = {
45            let builtin_fun = BuiltinFunId::Index(BuiltinIndexOp {
46                is_array: matches!(builtin_ty, BuiltinTy::Array),
47                mutability: RefKind::mutable(mut_access),
48                is_range: matches!(pe, Subslice { .. }),
49            });
50            // Same generics as the array/slice type, except for the extra lifetime.
51            let mut generics = tref.generics.clone();
52            generics.regions = [Region::Erased].into();
53            FnOperand::Regular(FnPtr {
54                func: Box::new(FunIdOrTraitMethodRef::mk_builtin(builtin_fun)),
55                generics,
56            })
57        };
58
59        let input_ty = TyKind::Ref(
60            Region::Erased,
61            subplace.ty().clone(),
62            RefKind::mutable(mut_access),
63        )
64        .into_ty();
65
66        let elem_ty = tref.generics.types[0].clone();
67        let output_inner_ty = if matches!(pe, Index { .. }) {
68            elem_ty
69        } else {
70            TyKind::Adt(TypeDeclRef {
71                id: TypeId::Builtin(BuiltinTy::Slice),
72                generics: Box::new(GenericArgs::new_for_builtin(vec![elem_ty].into())),
73            })
74            .into_ty()
75        };
76        let output_ty = {
77            TyKind::Ref(
78                Region::Erased,
79                output_inner_ty.clone(),
80                RefKind::mutable(mut_access),
81            )
82            .into_ty()
83        };
84
85        // Push the statements:
86        // `storage_live(tmp0)`
87        // `tmp0 = &{mut}p`
88        let input_var = {
89            let input_var = self.fresh_var(None, input_ty);
90            let kind = RawStatement::Assign(
91                input_var.clone(),
92                Rvalue::Ref(subplace.clone(), BorrowKind::mutable(mut_access)),
93            );
94            self.statements.push(Statement::new(self.span, kind));
95            input_var
96        };
97
98        // Construct the arguments to pass to the indexing function.
99        let mut args = vec![Operand::Move(input_var)];
100        if let Subslice { from, .. } = &pe {
101            args.push(from.as_ref().clone());
102        }
103        let (last_arg, from_end) = match &pe {
104            Index {
105                offset: x,
106                from_end,
107                ..
108            }
109            | Subslice {
110                to: x, from_end, ..
111            } => (x.as_ref().clone(), *from_end),
112            _ => unreachable!(),
113        };
114        if from_end {
115            // `storage_live(len_var)`
116            // `len_var = len(p)`
117            let usize_ty = TyKind::Literal(LiteralTy::Integer(IntegerTy::Usize)).into_ty();
118            let len_var = self.fresh_var(None, usize_ty.clone());
119            let kind = RawStatement::Assign(
120                len_var.clone(),
121                Rvalue::Len(
122                    subplace.clone(),
123                    subplace.ty().clone(),
124                    tref.generics.const_generics.get(0.into()).cloned(),
125                ),
126            );
127            self.statements.push(Statement::new(self.span, kind));
128
129            // `storage_live(index_var)`
130            // `index_var = len_var - last_arg`
131            // `storage_dead(len_var)`
132            let index_var = self.fresh_var(None, usize_ty);
133            let kind = RawStatement::Assign(
134                index_var.clone(),
135                Rvalue::BinaryOp(BinOp::Sub, Operand::Copy(len_var.clone()), last_arg),
136            );
137            self.statements.push(Statement::new(self.span, kind));
138            let dead_kind = RawStatement::StorageDead(len_var.local_id());
139            self.statements.push(Statement::new(self.span, dead_kind));
140            args.push(Operand::Copy(index_var));
141        } else {
142            args.push(last_arg);
143        }
144
145        // Call the indexing function:
146        // `storage_live(tmp1)`
147        // `tmp1 = {Array,Slice}{Mut,Shared}{Index,SubSlice}(move tmp0, <other args>)`
148        let output_var = {
149            let output_var = self.fresh_var(None, output_ty);
150            let index_call = Call {
151                func: indexing_function,
152                args,
153                dest: output_var.clone(),
154            };
155            let kind = RawStatement::Call(index_call);
156            self.statements.push(Statement::new(self.span, kind));
157            output_var
158        };
159
160        // Update the place.
161        *place = output_var.project(ProjectionElem::Deref, output_inner_ty);
162    }
163
164    /// Calls `self.visit_inner()` with `mutability` pushed on the stack.
165    fn visit_inner_with_mutability<T>(
166        &mut self,
167        x: &mut T,
168        mutability: bool,
169    ) -> ControlFlow<Infallible>
170    where
171        T: for<'s> DriveMut<'s, BodyVisitableWrapper<Self>>,
172    {
173        self.place_mutability_stack.push(mutability);
174        self.visit_inner(x)?;
175        self.place_mutability_stack.pop();
176        Continue(())
177    }
178}
179
180/// The visitor methods.
181impl VisitBodyMut for IndexVisitor<'_> {
182    /// We explore places from the inside-out.
183    fn exit_place(&mut self, place: &mut Place) {
184        // We have intercepted every traversal that would reach a place and pushed the correct
185        // mutability on the stack.
186        let mut_access = *self.place_mutability_stack.last().unwrap();
187        self.transform_place(mut_access, place);
188    }
189
190    fn visit_operand(&mut self, x: &mut Operand) -> ControlFlow<Infallible> {
191        match x {
192            Operand::Move(_) => self.visit_inner_with_mutability(x, true),
193            Operand::Copy(_) => self.visit_inner_with_mutability(x, false),
194            Operand::Const(..) => self.visit_inner(x),
195        }
196    }
197
198    fn visit_call(&mut self, x: &mut Call) -> ControlFlow<Infallible> {
199        self.visit_inner_with_mutability(x, true)
200    }
201
202    fn visit_fn_operand(&mut self, x: &mut FnOperand) -> ControlFlow<Infallible> {
203        match x {
204            FnOperand::Regular(_) => self.visit_inner(x),
205            FnOperand::Move(_) => self.visit_inner_with_mutability(x, true),
206        }
207    }
208
209    fn visit_rvalue(&mut self, x: &mut Rvalue) -> ControlFlow<Infallible> {
210        use Rvalue::*;
211        match x {
212            // `UniqueImmutable` de facto gives mutable access and only shows up if there is nested
213            // mutable access.
214            RawPtr(_, RefKind::Mut)
215            | Ref(_, BorrowKind::Mut | BorrowKind::TwoPhaseMut | BorrowKind::UniqueImmutable) => {
216                self.visit_inner_with_mutability(x, true)
217            }
218            RawPtr(_, RefKind::Shared)
219            | Ref(_, BorrowKind::Shared | BorrowKind::Shallow)
220            | Discriminant(..)
221            | Len(..) => self.visit_inner_with_mutability(x, false),
222
223            Use(_) | NullaryOp(..) | UnaryOp(..) | BinaryOp(..) | Aggregate(..) | Global(..)
224            | GlobalRef(..) | Repeat(..) | ShallowInitBox(..) => self.visit_inner(x),
225        }
226    }
227
228    fn visit_llbc_block(&mut self, _: &mut llbc_ast::Block) -> ControlFlow<Infallible> {
229        ControlFlow::Continue(())
230    }
231}
232
233pub struct Transform;
234
235/// We do the following.
236///
237/// If `p` is a projection (for instance: `var`, `*var`, `var.f`, etc.), we
238/// detect:
239/// - whether it operates on a slice or an array (we keep track of the types)
240/// - whether the access might mutate the value or not (it is
241///   the case if it is in a `move`, `&mut` or at the lhs of an assignment),
242///   and do the following transformations
243///
244/// ```text
245///   // If array and mutable access:
246///   ... p[i] ...
247///      ~~>
248///   tmp0 = &mut p
249///   tmp1 = ArrayIndexMut(move p, i)
250///   ... *tmp1 ...
251///
252///   // If array and non-mutable access:
253///   ... p[i] ...
254///      ~~>
255///   tmp0 := & p
256///   tmp1 := ArrayIndexShared(move tmp0, i)
257///   ... *tmp1 ...
258///
259///   // Omitting the slice cases, which are similar
260/// ```
261///
262/// For instance, it leads to the following transformations:
263/// ```text
264///   // x : [u32; N]
265///   y : u32 = copy x[i]
266///      ~~>
267///   tmp0 : & [u32; N] := &x
268///   tmp1 : &u32 = ArrayIndexShared(move tmp0, i)
269///   y : u32 = copy (*tmp1)
270///
271///   // x : &[T; N]
272///   y : &T = & (*x)[i]
273///      ~~>
274///   tmp0 : & [T; N] := & (*x)
275///   tmp1 : &T = ArrayIndexShared(move tmp0, i)
276///   y : &T = & (*tmp1)
277///
278///   // x : [u32; N]
279///   y = &mut x[i]
280///      ~~>
281///   tmp0 : &mut [u32; N] := &mut x
282///   tmp1 : &mut u32 := ArrayIndexMut(move tmp0, i)
283///   y = &mut (*tmp)
284///
285///   // When using an index on the lhs:
286///   // y : [T; N]
287///   y[i] = x
288///      ~~>
289///   tmp0 : &mut [T; N] := &mut y;
290///   tmp1 : &mut T = ArrayIndexMut(move y, i)
291///   *tmp1 = x
292/// ```
293impl LlbcPass for Transform {
294    fn transform_body(&self, _ctx: &mut TransformCtx, b: &mut ExprBody) {
295        b.body.transform(|st: &mut Statement| {
296            let mut visitor = IndexVisitor {
297                locals: &mut b.locals,
298                statements: Vec::new(),
299                place_mutability_stack: Vec::new(),
300                span: st.span,
301            };
302            use RawStatement::*;
303            match &mut st.content {
304                Assign(..)
305                | SetDiscriminant(..)
306                | CopyNonOverlapping(_)
307                | Drop(..)
308                | Deinit(..)
309                | Call(..) => {
310                    let _ = visitor.visit_inner_with_mutability(st, true);
311                }
312                Switch(..) => {
313                    let _ = visitor.visit_inner_with_mutability(st, false);
314                }
315                Nop | Error(..) | Assert(..) | Abort(..) | StorageDead(..) | StorageLive(..)
316                | Return | Break(..) | Continue(..) | Loop(..) => {
317                    let _ = st.drive_body_mut(&mut visitor);
318                }
319            }
320            visitor.statements
321        });
322    }
323}