Skip to main content

charon_lib/transform/simplify_output/
builtins_to_function_calls.rs

1//! Desugar built-in operations and array/slice indexing to standard library function calls.
2
3use std::collections::{HashMap, HashSet};
4
5use crate::llbc_ast::*;
6use crate::name_matcher::NamePattern;
7use crate::transform::ctx::{BodyTransformCtx, LlbcStatementTransformCtx};
8use crate::transform::{CowBox, TransformCtx};
9use derive_generic_visitor::*;
10use itertools::Itertools;
11
12use crate::transform::ctx::LlbcPass;
13
14fn mk_fn_ptr(ctx: &TransformCtx, id: ItemId, mut generics: GenericArgs) -> FnPtr {
15    if ctx.options.add_destruct_bounds
16        && let Some(item) = ctx.translated.get_item(id)
17    {
18        // Charon adds `Destruct` as the last trait clause of every generic item.
19        let trait_decl_ref = item
20            .generic_params()
21            .trait_clauses
22            .last()
23            .unwrap()
24            .trait_
25            .clone()
26            .substitute(&generics);
27        let kind = TraitRefKind::BuiltinOrAuto {
28            builtin_data: BuiltinImplData::UntrackedDestruct,
29            parent_trait_refs: Default::default(),
30            types: Default::default(),
31            vtable: None,
32        };
33        generics
34            .trait_refs
35            .push(TraitRef::new(kind, trait_decl_ref));
36    }
37    let fun_id = *id.as_fun().unwrap();
38    FnPtr::new(FnPtrKind::Fun(fun_id), generics)
39}
40
41/// Instantiate the trait impl that provides the given method.
42fn method_impl_trait_ref(
43    ctx: &TransformCtx,
44    id: ItemId,
45    fun_generics: &GenericArgs,
46) -> Option<TraitRef> {
47    let fun_id = *id.as_fun()?;
48    let fun = ctx.translated.fun_decls.get(fun_id)?;
49    let FunSource::TraitImpl { impl_ref, .. } = &fun.src else {
50        return None;
51    };
52    let impl_ref = impl_ref.clone().substitute(fun_generics);
53    let trait_impl = ctx.translated.trait_impls.get(impl_ref.id)?;
54    let trait_decl_ref =
55        RegionBinder::empty(trait_impl.impl_trait.clone().substitute(&impl_ref.generics));
56    Some(TraitRef::new(
57        TraitRefKind::TraitImpl(impl_ref),
58        trait_decl_ref,
59    ))
60}
61
62fn index_method_types(
63    params: &GenericParams,
64    elem_ty: &Ty,
65    index_ty: &Ty,
66    output_ty: &Ty,
67) -> Option<IndexVec<TypeVarId, Ty>> {
68    let mut types: IndexVec<TypeVarId, Ty> =
69        [elem_ty.clone(), index_ty.clone()].into_iter().collect();
70    match params.types.len() {
71        // With `--lift-associated-types`, `Index::Output` is an explicit type parameter.
72        3 => {
73            types.push(output_ty.clone());
74        }
75        2 => {}
76        _ => return None,
77    }
78    Some(types)
79}
80
81fn transform_operation(std_items: &Transform, ctx: &TransformCtx, statement: &mut Statement) {
82    match &statement.kind {
83        // Transform the ArrayToSlice unop.
84        StatementKind::Assign(
85            place,
86            Rvalue::UnaryOp(
87                UnOp::Cast(CastKind::Unsize(src_ty, tgt_ty, UnsizingMetadata::Length(_))),
88                operand,
89            ),
90        ) => {
91            if let (TyKind::Ref(_, src_ty, src_kind), TyKind::Ref(_, tgt_ty, tgt_kind)) =
92                (src_ty.kind(), tgt_ty.kind())
93                && let TyKind::Array(elem_ty, len, elem_ty_is_sized) = src_ty.kind()
94                && let TyKind::Slice(..) = tgt_ty.kind()
95            {
96                // In MIR terminology, we go from &[T; l] to &[T] which means we
97                // effectively "unsize" the type, as `l` no longer appears in the
98                // destination type. At runtime, the converse happens: the length
99                // materializes into the fat pointer.
100                assert!(src_kind == tgt_kind);
101                // We could avoid the clone operations below if we take the content of
102                // the statement. In practice, this shouldn't have much impact.
103                let item = match src_kind {
104                    RefKind::Shared => StdItem::ArrayAsSlice,
105                    RefKind::Mut => StdItem::ArrayAsMutSlice,
106                };
107                let Some(&fun_id) = std_items.item_map.get(&item) else {
108                    return;
109                };
110                let generics = GenericArgs::new(
111                    [Region::Erased].into(),
112                    [elem_ty.clone()].into(),
113                    [len.clone()].into(),
114                    elem_ty_is_sized.iter().cloned().collect(),
115                );
116                statement.kind = StatementKind::Call {
117                    call: Call {
118                        func: FnOperand::Regular(mk_fn_ptr(ctx, fun_id, generics)),
119                        args: vec![operand.clone()],
120                        dest: place.clone(),
121                        safety: CallSafety::Inherit,
122                    },
123                    on_unwind: Block::new_unreachable(statement.span),
124                };
125            }
126        }
127        // Transform the array aggregates to function calls.
128        StatementKind::Assign(place, Rvalue::Repeat(operand, ty, len, ty_is_copy)) => {
129            let Some(ty_is_copy) = ty_is_copy else {
130                return;
131            };
132            // We could avoid the clone operations below if we take the content of
133            // the statement. In practice, this shouldn't have much impact.
134            let Some(&fun_id) = std_items.item_map.get(&StdItem::ArrayRepeat) else {
135                return;
136            };
137            let TyKind::Array(_, _, ty_is_sized) = place.ty().kind() else {
138                return;
139            };
140            // `Copy`'s explicit `Clone` supertrait follows its optional implicit marker clause.
141            // `array::repeat` has the corresponding `Clone` bound at the same position.
142            let clone_clause_id = TraitClauseId::new(usize::from(ty_is_sized.is_some()));
143            let Some(ty_is_clone) = ty_is_copy
144                .clone()
145                .project_parent_clause(&ctx.translated, clone_clause_id)
146            else {
147                return;
148            };
149            let generics = GenericArgs::new(
150                [].into(),
151                [ty.clone()].into(),
152                [len.clone()].into(),
153                ty_is_sized.iter().cloned().chain([ty_is_clone]).collect(),
154            );
155            statement.kind = StatementKind::Call {
156                call: Call {
157                    func: FnOperand::Regular(mk_fn_ptr(ctx, fun_id, generics)),
158                    args: vec![operand.clone()],
159                    dest: place.clone(),
160                    safety: CallSafety::Inherit,
161                },
162                on_unwind: Block::new_unreachable(statement.span),
163            };
164        }
165        _ => {}
166    }
167}
168
169/// We replace some place constructors with function calls. To do that, we explore all the places
170/// in a body and deconstruct a given place access into intermediate assignments.
171///
172/// We accumulate the new assignments as statements in the visitor, and at the end we insert these
173/// statements before the one that was just explored.
174#[derive(Visitor)]
175struct IndexVisitor<'a, 'b> {
176    ctx: &'b mut LlbcStatementTransformCtx<'a>,
177    std_items: &'b Transform,
178    // When we visit a place, we need to know if it is being accessed mutably or not. Whenever we
179    // visit something that contains a place we push the relevant mutability on this stack.
180    // Unfortunately this requires us to be very careful to catch all the cases where we see
181    // places.
182    place_mutability_stack: Vec<bool>,
183}
184
185impl<'a, 'b> IndexVisitor<'a, 'b> {
186    /// transform `place: subplace[i]` into indexing function calls for `subplace` and `i`
187    fn transform_place(&mut self, mut_access: bool, place: &mut Place) {
188        use ProjectionElem::*;
189        // This function is naturally called recusively, so `subplace` cannot be another `Index` or `Subslice`.
190        // Hence, `subplace`, if still projecting, must be either a `Deref` or a `Field`.
191        let Some((subplace, pe @ (Index { .. } | Subslice { .. }))) = place.as_projection() else {
192            return;
193        };
194
195        let (ty, len, ty_is_sized) = match subplace.ty.kind() {
196            TyKind::Array(ty, len, ty_is_sized) => (ty.clone(), Some(len.clone()), ty_is_sized),
197            TyKind::Slice(ty, ty_is_sized) => (ty.clone(), None, ty_is_sized),
198            _ => unreachable!("Indexing can only be done on arrays or slices"),
199        };
200
201        let mutability = RefKind::mutable(mut_access);
202        let item = match (pe.is_subslice(), mutability) {
203            (false, RefKind::Shared) => StdItem::SliceIndex,
204            (false, RefKind::Mut) => StdItem::SliceIndexMut,
205            (true, RefKind::Shared) => StdItem::RangeIndex,
206            (true, RefKind::Mut) => StdItem::RangeIndexMut,
207        };
208        let Some(&index_fun_id) = self.std_items.item_map.get(&item) else {
209            return;
210        };
211        let Some(index_fun) = index_fun_id
212            .as_fun()
213            .and_then(|id| self.ctx.ctx.translated.fun_decls.get(*id))
214        else {
215            return;
216        };
217        let index_generics = GenericArgs::new(
218            [Region::Erased].into(),
219            [ty.clone()].into(),
220            [].into(),
221            ty_is_sized.iter().cloned().collect(),
222        );
223        let index_fn_ptr = mk_fn_ptr(self.ctx.ctx, index_fun_id, index_generics);
224        let index_ty = index_fun.signature.inputs[0]
225            .clone()
226            .substitute(&index_fn_ptr.generics);
227
228        let output_inner_ty = if matches!(pe, Index { .. }) {
229            ty.clone()
230        } else {
231            TyKind::Slice(ty.clone(), ty_is_sized.clone()).into_ty()
232        };
233        let output_ty = {
234            TyKind::Ref(
235                Region::Erased,
236                output_inner_ty.clone(),
237                RefKind::mutable(mut_access),
238            )
239            .into_ty()
240        };
241
242        // Push the statements:
243        // `storage_live(tmp0)`
244        // `tmp0 = &{mut}p`
245        let input_var =
246            self.ctx
247                .borrow_to_new_var(subplace.clone(), BorrowKind::mutable(mut_access), None);
248
249        // Construct the arguments to pass to the indexing function.
250        let (last_arg, from_end) = match &pe {
251            Index {
252                offset: x,
253                from_end,
254                ..
255            }
256            | Subslice {
257                to: x, from_end, ..
258            } => (x.as_ref().clone(), *from_end),
259            _ => unreachable!(),
260        };
261        let to_idx = self
262            .ctx
263            .compute_subslice_end_idx(subplace, last_arg, from_end);
264        let index = match &pe {
265            Index { .. } => to_idx,
266            Subslice { from, .. } => {
267                let Some(range_ref) = index_ty.as_adt().cloned() else {
268                    return;
269                };
270                let range_ty = TyKind::Adt(range_ref.clone()).into_ty();
271                let range_var = self.ctx.fresh_var(None, range_ty);
272                self.ctx.insert_assn_stmt(
273                    range_var.clone(),
274                    Rvalue::Aggregate(
275                        AggregateKind::Adt(range_ref, None, None),
276                        vec![from.as_ref().clone(), to_idx],
277                    ),
278                );
279                Operand::Move(range_var)
280            }
281            _ => unreachable!(),
282        };
283
284        let (index_fn_ptr, args) = if let Some(len) = len {
285            let (array_item, slice_item) = match mutability {
286                RefKind::Shared => (StdItem::ArrayIndex, StdItem::SliceIndexImpl),
287                RefKind::Mut => (StdItem::ArrayIndexMut, StdItem::SliceIndexMutImpl),
288            };
289            let Some(&array_fun_id) = self.std_items.item_map.get(&array_item) else {
290                return;
291            };
292            let Some(array_fun) = array_fun_id
293                .as_fun()
294                .and_then(|id| self.ctx.ctx.translated.fun_decls.get(*id))
295            else {
296                return;
297            };
298            let Some(&slice_fun_id) = self.std_items.item_map.get(&slice_item) else {
299                return;
300            };
301            let Some(slice_fun) = slice_fun_id
302                .as_fun()
303                .and_then(|id| self.ctx.ctx.translated.fun_decls.get(*id))
304            else {
305                return;
306            };
307
308            let Some(slice_types) =
309                index_method_types(&slice_fun.generics, &ty, &index_ty, &output_inner_ty)
310            else {
311                return;
312            };
313            let mut slice_generics =
314                GenericArgs::new([Region::Erased].into(), slice_types, [].into(), [].into());
315            let Some(slice_index_trait_ref) =
316                method_impl_trait_ref(self.ctx.ctx, index_fun_id, &index_fn_ptr.generics)
317            else {
318                return;
319            };
320            let index_ty_is_sized = if ty_is_sized.is_some() {
321                let Some(clause) = slice_fun.generics.trait_clauses.get(TraitClauseId::new(1))
322                else {
323                    return;
324                };
325                let trait_decl_ref = clause.trait_.clone().substitute_explicits(&slice_generics);
326                let Some(meta_sized) = slice_index_trait_ref
327                    .clone()
328                    .project_parent_clause(&self.ctx.ctx.translated, TraitClauseId::ZERO)
329                else {
330                    return;
331                };
332                Some(TraitRef::new(
333                    TraitRefKind::BuiltinOrAuto {
334                        builtin_data: BuiltinImplData::Sized,
335                        parent_trait_refs: [meta_sized].into_iter().collect(),
336                        types: Default::default(),
337                        vtable: None,
338                    },
339                    trait_decl_ref,
340                ))
341            } else {
342                None
343            };
344            slice_generics.trait_refs = ty_is_sized
345                .iter()
346                .cloned()
347                .chain(index_ty_is_sized.iter().cloned())
348                .chain([slice_index_trait_ref])
349                .collect();
350            let slice_fn_ptr = mk_fn_ptr(self.ctx.ctx, slice_fun_id, slice_generics);
351            let Some(slice_trait_ref) =
352                method_impl_trait_ref(self.ctx.ctx, slice_fun_id, &slice_fn_ptr.generics)
353            else {
354                return;
355            };
356
357            let Some(array_types) =
358                index_method_types(&array_fun.generics, &ty, &index_ty, &output_inner_ty)
359            else {
360                return;
361            };
362            let array_generics = GenericArgs::new(
363                [Region::Erased].into(),
364                array_types,
365                [len].into(),
366                ty_is_sized
367                    .iter()
368                    .cloned()
369                    .chain(index_ty_is_sized)
370                    .chain([slice_trait_ref])
371                    .collect(),
372            );
373            (
374                mk_fn_ptr(self.ctx.ctx, array_fun_id, array_generics),
375                vec![Operand::Move(input_var), index],
376            )
377        } else {
378            (index_fn_ptr, vec![index, Operand::Move(input_var)])
379        };
380
381        // Call the indexing function:
382        // `storage_live(tmp1)`
383        // `tmp1 = {Array,Slice}{Mut,Shared}{Index,SubSlice}(move tmp0, <other args>)`
384        let output_var = {
385            let output_var = self.ctx.fresh_var(None, output_ty);
386            let index_call = Call {
387                func: FnOperand::Regular(index_fn_ptr),
388                args,
389                dest: output_var.clone(),
390                safety: CallSafety::Inherit,
391            };
392            let kind = StatementKind::Call {
393                call: index_call,
394                on_unwind: Block::new_unreachable(self.ctx.span),
395            };
396            self.ctx
397                .statements
398                .push(Statement::new(self.ctx.span, kind));
399            output_var
400        };
401
402        // Update the place.
403        *place = output_var.project(ProjectionElem::Deref, output_inner_ty);
404    }
405
406    /// Calls `self.visit_inner()` with `mutability` pushed on the stack.
407    fn visit_inner_with_mutability<T>(
408        &mut self,
409        x: &mut T,
410        mutability: bool,
411    ) -> ControlFlow<Infallible>
412    where
413        T: for<'s> DriveMut<'s, BodyVisitableWrapper<Self>> + BodyVisitable,
414    {
415        self.place_mutability_stack.push(mutability);
416        self.visit_inner(x)?;
417        self.place_mutability_stack.pop();
418        Continue(())
419    }
420}
421
422/// The visitor methods.
423impl VisitBodyMut for IndexVisitor<'_, '_> {
424    /// We explore places from the inside-out --- recursion naturally happens here.
425    fn exit_place(&mut self, place: &mut Place) {
426        // We have intercepted every traversal that would reach a place and pushed the correct
427        // mutability on the stack.
428        let mut_access = *self.place_mutability_stack.last().unwrap();
429        self.transform_place(mut_access, place);
430    }
431
432    fn visit_operand(&mut self, x: &mut Operand) -> ControlFlow<Infallible> {
433        match x {
434            Operand::Move(_) => self.visit_inner_with_mutability(x, true),
435            Operand::Copy(_) => self.visit_inner_with_mutability(x, false),
436            Operand::Const(..) => self.visit_inner(x),
437        }
438    }
439
440    fn visit_call(&mut self, x: &mut Call) -> ControlFlow<Infallible> {
441        self.visit_inner_with_mutability(x, true)
442    }
443
444    fn visit_asm_operand(&mut self, x: &mut AsmOperand) -> ControlFlow<Infallible> {
445        match x {
446            AsmOperand::Out { .. } | AsmOperand::InOut { .. } => {
447                self.visit_inner_with_mutability(x, true)
448            }
449            _ => self.visit_inner(x),
450        }
451    }
452
453    fn visit_fn_operand(&mut self, x: &mut FnOperand) -> ControlFlow<Infallible> {
454        match x {
455            FnOperand::Regular(_) => self.visit_inner(x),
456            FnOperand::Dynamic(_) => self.visit_inner_with_mutability(x, true),
457        }
458    }
459
460    fn visit_rvalue(&mut self, x: &mut Rvalue) -> ControlFlow<Infallible> {
461        use Rvalue::*;
462        match x {
463            // `UniqueImmutable` de facto gives mutable access and only shows up if there is nested
464            // mutable access.
465            RawPtr {
466                kind: RefKind::Mut, ..
467            }
468            | Ref {
469                kind: BorrowKind::Mut | BorrowKind::TwoPhaseMut | BorrowKind::UniqueImmutable,
470                ..
471            } => self.visit_inner_with_mutability(x, true),
472            RawPtr {
473                kind: RefKind::Shared,
474                ..
475            }
476            | Ref {
477                kind: BorrowKind::Shared | BorrowKind::Shallow,
478                ..
479            }
480            | Discriminant(..)
481            | Len(..) => self.visit_inner_with_mutability(x, false),
482
483            Use(..) | NullaryOp(..) | UnaryOp(..) | BinaryOp(..) | Aggregate(..) | Repeat(..) => {
484                self.visit_inner(x)
485            }
486        }
487    }
488
489    fn visit_llbc_block(&mut self, _: &mut llbc_ast::Block) -> ControlFlow<Infallible> {
490        ControlFlow::Continue(())
491    }
492}
493
494/// We do the following.
495///
496/// If `p` is a projection (for instance: `var`, `*var`, `var.f`, etc.), we
497/// detect:
498/// - whether it operates on a slice or an array (we keep track of the types)
499/// - whether the access might mutate the value or not (it is
500///   the case if it is in a `move`, `&mut` or at the lhs of an assignment),
501///   and do the following transformations
502///
503/// ```text
504///   // If array and mutable access:
505///   ... p[i] ...
506///      ~~>
507///   tmp0 = &mut p
508///   tmp1 = <[_; _] as IndexMut<_>>::index_mut(move tmp0, i)
509///   ... *tmp1 ...
510///
511///   // If array and non-mutable access:
512///   ... p[i] ...
513///      ~~>
514///   tmp0 := & p
515///   tmp1 := <[_; _] as Index<_>>::index(move tmp0, i)
516///   ... *tmp1 ...
517///
518///   // Omitting the slice cases, which are similar
519/// ```
520///
521/// For instance, it leads to the following transformations:
522/// ```text
523///   // x : [u32; N]
524///   y : u32 = copy x[i]
525///      ~~>
526///   tmp0 : & [u32; N] := &x
527///   tmp1 : &u32 = <[_; _] as Index<_>>::index(move tmp0, i)
528///   y : u32 = copy (*tmp1)
529///
530///   // x : &[T; N]
531///   y : &T = & (*x)[i]
532///      ~~>
533///   tmp0 : & [T; N] := & (*x)
534///   tmp1 : &T = <[_; _] as Index<_>>::index(move tmp0, i)
535///   y : &T = & (*tmp1)
536///
537///   // x : [u32; N]
538///   y = &mut x[i]
539///      ~~>
540///   tmp0 : &mut [u32; N] := &mut x
541///   tmp1 : &mut u32 := <[_; _] as IndexMut<_>>::index_mut(move tmp0, i)
542///   y = &mut (*tmp)
543///
544///   // When using an index on the lhs:
545///   // y : [T; N]
546///   y[i] = x
547///      ~~>
548///   tmp0 : &mut [T; N] := &mut y;
549///   tmp1 : &mut T = <[_; _] as IndexMut<_>>::index_mut(move tmp0, i)
550///   *tmp1 = x
551/// ```
552#[derive(Copy, Clone, PartialEq, Eq, Hash)]
553enum StdItem {
554    ArrayAsSlice,
555    ArrayAsMutSlice,
556    ArrayRepeat,
557    ArrayIndex,
558    ArrayIndexMut,
559    SliceIndexImpl,
560    SliceIndexMutImpl,
561    SliceIndex,
562    SliceIndexMut,
563    RangeIndex,
564    RangeIndexMut,
565}
566
567pub struct Transform {
568    item_map: HashMap<StdItem, ItemId>,
569    item_set: HashSet<ItemId>,
570}
571
572impl Transform {
573    pub fn new(ctx: &TransformCtx) -> CowBox<dyn LlbcPass> {
574        use StdItem::*;
575
576        let mut matches: [(StdItem, NamePattern, Vec<ItemId>); _] = [
577            (ArrayAsSlice, "core::array::_::as_slice"),
578            (ArrayAsMutSlice, "core::array::_::as_mut_slice"),
579            (ArrayRepeat, "core::array::repeat"),
580            (
581                SliceIndex,
582                "core::slice::index::{impl core::slice::index::SliceIndex<_> for usize}::index",
583            ),
584            (
585                SliceIndexMut,
586                "core::slice::index::{impl core::slice::index::SliceIndex<_> for usize}::index_mut",
587            ),
588            (
589                RangeIndex,
590                "core::slice::index::{impl core::slice::index::SliceIndex<_> for core::ops::range::Range<usize>}::index",
591            ),
592            (
593                RangeIndexMut,
594                "core::slice::index::{impl core::slice::index::SliceIndex<_> for core::ops::range::Range<usize>}::index_mut",
595            ),
596        ]
597        .map(|(item, path)| (item, NamePattern::parse(path).unwrap(), Vec::new()));
598
599        // Resolve the items
600        for (id, name) in &ctx.translated.item_names {
601            for (_, pattern, found) in &mut matches {
602                if pattern.matches(&ctx.translated, name) {
603                    found.push(*id);
604                }
605            }
606        }
607
608        let mut index_impl_methods: HashMap<StdItem, Vec<ItemId>> = HashMap::new();
609        for fun in &ctx.translated.fun_decls {
610            let FunSource::TraitImpl { trait_ref, .. } = &fun.src else {
611                continue;
612            };
613            let Some(trait_decl) = ctx.translated.trait_decls.get(trait_ref.id) else {
614                continue;
615            };
616            let Some(self_ty) = trait_ref.self_ty(&ctx.translated) else {
617                continue;
618            };
619            let item = match (&trait_decl.item_meta.lang_item, self_ty.kind()) {
620                (Some(crate::ast::from_rustc::LangItem::Index), TyKind::Array(..)) => ArrayIndex,
621                (Some(crate::ast::from_rustc::LangItem::IndexMut), TyKind::Array(..)) => {
622                    ArrayIndexMut
623                }
624                (Some(crate::ast::from_rustc::LangItem::Index), TyKind::Slice(..)) => {
625                    SliceIndexImpl
626                }
627                (Some(crate::ast::from_rustc::LangItem::IndexMut), TyKind::Slice(..)) => {
628                    SliceIndexMutImpl
629                }
630                _ => continue,
631            };
632            index_impl_methods
633                .entry(item)
634                .or_default()
635                .push(ItemId::Fun(fun.def_id));
636        }
637        let item_map = matches
638            .into_iter()
639            .map(|(item, _, found)| (item, found))
640            .chain(index_impl_methods)
641            .filter_map(|(item, found)| found.into_iter().exactly_one().ok().map(|id| (item, id)))
642            .collect::<HashMap<_, _>>();
643        let item_set = item_map.values().copied().collect();
644        CowBox::Owned(Box::new(Self { item_map, item_set }))
645    }
646}
647
648impl LlbcPass for Transform {
649    fn should_run(&self, options: &crate::options::TranslateOptions) -> bool {
650        options.ops_to_function_calls || options.index_to_function_calls
651    }
652
653    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
654        if self.item_set.contains(&ItemId::Fun(decl.def_id)) {
655            return;
656        }
657        let Some(body) = decl.body.as_structured_mut() else {
658            return;
659        };
660        if ctx.options.ops_to_function_calls {
661            body.body
662                .visit_statements(&mut |statement: &mut Statement| {
663                    transform_operation(self, ctx, statement)
664                });
665        }
666        if ctx.options.index_to_function_calls {
667            decl.transform_llbc_statements(ctx, |ctx, st: &mut Statement| {
668                let mut visitor = IndexVisitor {
669                    ctx,
670                    std_items: self,
671                    place_mutability_stack: Vec::new(),
672                };
673                use StatementKind::*;
674                match &mut st.kind {
675                    Assign(..) | SetDiscriminant(..) | Drop { .. } | Call { .. } => {
676                        let _ = visitor.visit_inner_with_mutability(st, true);
677                    }
678                    Switch { .. } | PlaceMention(..) | Borrowck(..) => {
679                        let _ = visitor.visit_inner_with_mutability(st, false);
680                    }
681                    Nop
682                    | UnwindResume
683                    | InlineAsm { .. }
684                    | Assert { .. }
685                    | Panic { .. }
686                    | UndefinedBehavior
687                    | UnwindTerminate
688                    | StorageDead(..)
689                    | StorageLive(..)
690                    | Return
691                    | Break(..)
692                    | Continue(..)
693                    | Loop(..) => {
694                        let _ = st.drive_body_mut(&mut visitor);
695                    }
696                }
697            })
698        }
699    }
700}