Skip to main content

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::register_error;
8use crate::transform::CowBox;
9use crate::ullbc_ast;
10use std::cell::RefCell;
11use std::{fmt, mem};
12
13/// Simpler context used for rustc-independent code transformation. This only depends on rustc for
14/// its error reporting machinery.
15pub struct TransformCtx {
16    /// The options that control transformation.
17    pub options: TranslateOptions,
18    /// The translated data.
19    pub translated: TranslatedCrate,
20    /// Context for tracking and reporting errors.
21    pub errors: RefCell<ErrorCtx>,
22}
23
24/// A pass that modifies ullbc bodies and can be fused with previous passes so that we run all of
25/// them on a given body.
26pub trait UllbcPass: Sync {
27    /// Whether the pass should run.
28    fn should_run(&self, _options: &TranslateOptions) -> bool {
29        true
30    }
31
32    /// Transform a body.
33    fn transform_body(&self, _ctx: &mut TransformCtx, _body: &mut ullbc_ast::ExprBody) {}
34
35    /// Transform a function declaration. This forwards to `transform_body` by default.
36    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
37        if let Some(body) = decl.body.as_unstructured_mut() {
38            self.transform_body(ctx, body)
39        }
40    }
41
42    /// Transform an item. This forwards to `transform_function` by default.
43    fn transform_item(&self, ctx: &mut TransformCtx, item: ItemRefMut<'_>) {
44        if let ItemRefMut::Fun(decl) = item {
45            self.transform_function(ctx, decl);
46        }
47    }
48
49    /// Some passes carry function bodies, which must also be transformed. This is called before
50    /// the batch of passes starts, with all the passes that come before this one. Rougly only
51    /// useful for passes that inline some functions into others.
52    fn apply_preceding_passes(
53        &mut self,
54        _ctx: &mut TransformCtx,
55        _passes: &[CowBox<dyn UllbcPass>],
56    ) {
57    }
58
59    /// Run after all the fused passes in the current block are done.
60    fn finalize(&self, _ctx: &mut TransformCtx) {}
61
62    /// The name of the pass, used for debug logging. The default implementation uses the type
63    /// name.
64    fn name(&self) -> &str {
65        std::any::type_name::<Self>()
66    }
67}
68
69/// A pass that modifies llbc bodies.
70pub trait LlbcPass: Sync {
71    /// Whether the pass should run.
72    fn should_run(&self, _options: &TranslateOptions) -> bool {
73        true
74    }
75
76    /// Transform a body.
77    fn transform_body(&self, _ctx: &mut TransformCtx, _body: &mut llbc_ast::ExprBody) {}
78
79    /// Transform a function declaration. This forwards to `transform_body` by default.
80    fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
81        if let Some(body) = decl.body.as_structured_mut() {
82            self.transform_body(ctx, body)
83        }
84    }
85
86    /// The name of the pass, used for debug logging. The default implementation uses the type
87    /// name.
88    fn name(&self) -> &str {
89        std::any::type_name::<Self>()
90    }
91}
92
93/// A pass that transforms the crate data.
94pub trait TransformPass: Sync {
95    /// Whether the pass should run.
96    fn should_run(&self, _options: &TranslateOptions) -> bool {
97        true
98    }
99
100    fn transform_ctx(&self, ctx: &mut TransformCtx);
101
102    /// The name of the pass, used for debug logging. The default implementation uses the type
103    /// name.
104    fn name(&self) -> &str {
105        std::any::type_name::<Self>()
106    }
107}
108
109impl TransformCtx {
110    pub(crate) fn has_errors(&self) -> bool {
111        self.errors.borrow().has_errors()
112    }
113
114    /// Span an error and register the error.
115    pub(crate) fn span_err(&self, span: Span, msg: &str, level: Level) -> Error {
116        self.errors
117            .borrow_mut()
118            .span_err(&self.translated, span, msg, level)
119    }
120
121    pub(crate) fn opacity_for_name(&self, name: &Name) -> ItemOpacity {
122        self.options.opacity_for_name(&self.translated, name)
123    }
124
125    /// Run an item transformation with its error context, reporting any panic on that item.
126    pub(crate) fn with_def_id(
127        &mut self,
128        def_id: impl Into<ItemId>,
129        def_id_is_local: bool,
130        span: Span,
131        f: impl FnOnce(&mut Self),
132    ) {
133        let def_id = def_id.into();
134        let mut errors = self.errors.borrow_mut();
135        let current_def_id = errors.def_id.replace(def_id);
136        let current_def_id_is_local = mem::replace(&mut errors.def_id_is_local, def_id_is_local);
137        drop(errors); // important: release the refcell "lock"
138        if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(self))).is_err() {
139            register_error!(
140                self,
141                span,
142                "Thread panicked while transforming item `{def_id:?}`."
143            );
144        }
145        let mut errors = self.errors.borrow_mut();
146        errors.def_id = current_def_id;
147        errors.def_id_is_local = current_def_id_is_local;
148    }
149
150    /// Mutably iterate over the bodies.
151    /// Warning: we replace each body with `Err(Opaque)` while inspecting it so we can keep access
152    /// to the rest of the crate.
153    pub(crate) fn for_each_body(&mut self, mut f: impl FnMut(&mut Self, &mut Body)) {
154        let fn_ids = self.translated.fun_decls.all_indices();
155        for id in fn_ids {
156            if let Some(decl) = self.translated.fun_decls.get_mut(id)
157                && decl.body.has_contents()
158            {
159                let mut body = mem::replace(&mut decl.body, Body::Opaque);
160                let fun_decl_id = decl.def_id;
161                let is_local = decl.item_meta.is_local;
162                let span = decl.item_meta.span;
163                self.with_def_id(fun_decl_id, is_local, span, |ctx| f(ctx, &mut body));
164                let decl = &mut self.translated.fun_decls[id];
165                decl.body = body;
166                decl.item_meta.has_errors |= self.errors.borrow().item_has_errors(fun_decl_id);
167            }
168        }
169    }
170
171    /// Mutably iterate over the function declarations.
172    /// Warning: each inspected function declaration becomes inaccessible from `ctx` during the
173    /// course of this function.
174    pub(crate) fn for_each_fun_decl(&mut self, mut f: impl FnMut(&mut Self, &mut FunDecl)) {
175        let fn_ids = self.translated.fun_decls.all_indices();
176        for id in fn_ids {
177            if let Some(mut decl) = self.translated.fun_decls.remove(id) {
178                let fun_decl_id = decl.def_id;
179                let is_local = decl.item_meta.is_local;
180                let span = decl.item_meta.span;
181                self.with_def_id(fun_decl_id, is_local, span, |ctx| f(ctx, &mut decl));
182                decl.item_meta.has_errors |= self.errors.borrow().item_has_errors(fun_decl_id);
183                self.translated.fun_decls.set_slot(id, decl);
184            }
185        }
186    }
187
188    /// Mutably iterate over the type declarations.
189    /// Warning: each inspected type declaration becomes inaccessible from `ctx` during the course
190    /// of this function.
191    pub(crate) fn for_each_type_decl(&mut self, mut f: impl FnMut(&mut Self, &mut TypeDecl)) {
192        let type_ids = self.translated.type_decls.all_indices();
193        for id in type_ids {
194            if let Some(mut decl) = self.translated.type_decls.remove(id) {
195                let type_decl_id = decl.def_id;
196                let is_local = decl.item_meta.is_local;
197                let span = decl.item_meta.span;
198                self.with_def_id(type_decl_id, is_local, span, |ctx| f(ctx, &mut decl));
199                decl.item_meta.has_errors |= self.errors.borrow().item_has_errors(type_decl_id);
200                self.translated.type_decls.set_slot(id, decl);
201            }
202        }
203    }
204
205    /// Iterate mutably over all items, keeping access to `self`. To make this work, we move out
206    /// each item before iterating over it. Items added during traversal will not be iterated over.
207    pub fn for_each_item_mut(&mut self, mut f: impl for<'a> FnMut(&'a mut Self, ItemRefMut<'a>)) {
208        for id in self.translated.all_ids() {
209            if let Some(mut decl) = self.translated.remove_item_temporarily(id) {
210                let is_local = decl.as_ref().item_meta().is_local;
211                let span = decl.as_ref().item_meta().span;
212                self.with_def_id(id, is_local, span, |ctx| f(ctx, decl.as_mut()));
213                decl.as_mut().item_meta().has_errors |= self.errors.borrow().item_has_errors(id);
214                self.translated.put_item_back(id, decl);
215            }
216        }
217    }
218}
219
220impl<'a> IntoFormatter for &'a TransformCtx {
221    type C = FmtCtx<'a>;
222
223    fn into_fmt(self) -> Self::C {
224        self.translated.into_fmt()
225    }
226}
227
228impl fmt::Display for TransformCtx {
229    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
230        self.translated.fmt(f)
231    }
232}
233
234/// A helper trait that captures common operations in body transformation.
235pub trait BodyTransformCtx: Sized {
236    fn get_crate(&self) -> &TranslatedCrate;
237    fn get_options(&self) -> &TranslateOptions;
238    fn get_params(&self) -> &GenericParams;
239    fn get_locals_mut(&mut self) -> &mut Locals;
240
241    fn insert_storage_live_stmt(&mut self, local: LocalId);
242    fn insert_storage_dead_stmt(&mut self, local: LocalId);
243    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue);
244
245    fn to_fmt(&self) -> FmtCtx<'_> {
246        self.get_crate().into_fmt()
247    }
248
249    /// Create a local & return the place pointing to it
250    fn fresh_var(&mut self, name: Option<String>, ty: Ty) -> Place {
251        let var = self.get_locals_mut().new_var(name, ty);
252        self.insert_storage_live_stmt(var.local_id().unwrap());
253        var
254    }
255
256    /// Assign an rvalue to a place, unless the rvalue is a move in which case we just use the
257    /// moved place.
258    fn rval_to_place(&mut self, rvalue: Rvalue, ty: Ty) -> Place {
259        if let Rvalue::Use(Operand::Move(place), WithRetag::No) = rvalue {
260            place
261        } else {
262            let var = self.fresh_var(None, ty);
263            self.insert_assn_stmt(var.clone(), rvalue);
264            var
265        }
266    }
267
268    /// When `from_end` is true, we need to compute `len(p) - last_arg` instead of just using `last_arg`.
269    /// Otherwise, we simply return `last_arg`.
270    /// New local variables are created as needed.
271    ///
272    /// The `last_arg` is either the `offset` for `Index` or the `to` for `Subslice` for the projections.
273    fn compute_subslice_end_idx(
274        &mut self,
275        len_place: &Place,
276        last_arg: Operand,
277        from_end: bool,
278    ) -> Operand {
279        if from_end {
280            // `storage_live(len_var)`
281            // `len_var = len(p)`
282            let len_var = self.fresh_var(None, Ty::mk_usize());
283            let len = match len_place.ty().kind() {
284                TyKind::Array(_, len, _) => Some(len.clone()),
285                TyKind::Slice(..) => None,
286                _ => panic!(
287                    "called `compute_subslice_end_idx` on something that isn't an array or slice: {:?}",
288                    len_place.ty()
289                ),
290            };
291            self.insert_assn_stmt(
292                len_var.clone(),
293                Rvalue::Len(len_place.clone(), len_place.ty().clone(), len),
294            );
295
296            // `storage_live(index_var)`
297            // `index_var = len_var - last_arg`
298            // `storage_dead(len_var)`
299            let index_var = self.fresh_var(None, Ty::mk_usize());
300            self.insert_assn_stmt(
301                index_var.clone(),
302                Rvalue::BinaryOp(
303                    BinOp::Sub(OverflowMode::UB),
304                    Operand::Copy(len_var.clone()),
305                    last_arg,
306                ),
307            );
308            self.insert_storage_dead_stmt(len_var.local_id().unwrap());
309            Operand::Copy(index_var)
310        } else {
311            last_arg
312        }
313    }
314
315    fn is_sized_type_var(&mut self, ty: &Ty) -> bool {
316        match ty.kind() {
317            TyKind::TypeVar(..) => {
318                if self.get_options().hide_marker_traits {
319                    // If we're hiding `Sized`, let's consider everything to be sized.
320                    return true;
321                }
322                let params = self.get_params();
323                for clause in &params.trait_clauses {
324                    let tref = clause.trait_.clone().erase();
325                    // Check if it is `Sized<T>`
326                    if tref.generics.types[0] == *ty
327                        && self
328                            .get_crate()
329                            .trait_decls
330                            .get(tref.id)
331                            .and_then(|decl| decl.item_meta.lang_item.as_ref())
332                            == Some(&from_rustc::LangItem::Sized)
333                    {
334                        return true;
335                    }
336                }
337                false
338            }
339            _ => false,
340        }
341    }
342
343    /// Emit statements that compute the metadata of the given place. Returns an operand containing the
344    /// metadata value.
345    ///
346    /// E.g., for:
347    /// ```ignore
348    /// let x = &(*ptr).field;
349    /// ```
350    /// if `(*ptr).field` is a DST like `[i32]`, this will get the metadata from the appropriate
351    /// pointer:
352    /// ```ignore
353    /// let len = ptr.metadata;
354    /// ```
355    /// and return `Operand::Move(len)`.
356    ///
357    fn compute_place_metadata(&mut self, place: &Place) -> Operand {
358        /// Compute the metadata for a place. Return `None` if the place has no metadata.
359        fn compute_place_metadata_inner<T: BodyTransformCtx>(
360            ctx: &mut T,
361            place: &Place,
362            metadata_ty: &Ty,
363        ) -> Option<Operand> {
364            let (subplace, proj) = match &place.kind {
365                PlaceKind::Local(_) => return None,
366                PlaceKind::Global(gref) => {
367                    let global = ctx.get_crate().global_decls.get(gref.id)?;
368                    return Some(Operand::Const(global.ptr_metadata.clone()));
369                }
370                PlaceKind::Projection(subplace, proj) => (subplace, proj),
371            };
372            match proj {
373                // The outermost deref we encountered gives us the metadata of the place.
374                ProjectionElem::Deref => {
375                    let metadata_place = subplace
376                        .clone()
377                        .project(ProjectionElem::PtrMetadata, metadata_ty.clone());
378                    Some(Operand::Copy(metadata_place))
379                }
380                ProjectionElem::Field { .. } => {
381                    compute_place_metadata_inner(ctx, subplace, metadata_ty)
382                }
383                // The pointee of an offset is sized, hence no metadata.
384                ProjectionElem::Offset { .. } => None,
385                // Indexing for array & slice will only result in sized types, hence no metadata
386                ProjectionElem::Index { .. } => None,
387                // Ptr metadata is always sized.
388                ProjectionElem::PtrMetadata => None,
389                // A subslice of an array whose bounds are known at compile-time.
390                // FIXME: track that distinction in `ProjectionElem::Subslice` instead.
391                ProjectionElem::Subslice { .. } if place.ty.kind().is_array() => None,
392                // Otherwise the subslice is a slice, whose metadata is its length `to` - `from`.
393                ProjectionElem::Subslice { from, to, from_end } => {
394                    let to_idx = ctx.compute_subslice_end_idx(subplace, *to.clone(), *from_end);
395                    let diff_place = ctx.fresh_var(None, Ty::mk_usize());
396                    ctx.insert_assn_stmt(
397                        diff_place.clone(),
398                        // Overflow is UB and should have been prevented by a bound check beforehand.
399                        Rvalue::BinaryOp(BinOp::Sub(OverflowMode::UB), to_idx, *from.clone()),
400                    );
401                    Some(Operand::Copy(diff_place))
402                }
403            }
404        }
405        trace!(
406            "getting ptr metadata for place: {}",
407            place.with_ctx(&self.to_fmt())
408        );
409        let metadata_ty = place.ty().get_ptr_metadata(self.get_crate()).into_type();
410        if metadata_ty.is_unit()
411            || matches!(metadata_ty.kind(), TyKind::PtrMetadata(ty) if self.is_sized_type_var(ty))
412        {
413            // If the type var is known to be `Sized`, then no metadata is needed
414            return Operand::mk_const_unit();
415        }
416        trace!(
417            "computed metadata type: {}",
418            metadata_ty.with_ctx(&self.to_fmt())
419        );
420        compute_place_metadata_inner(self, place, &metadata_ty)
421            .unwrap_or_else(Operand::mk_const_unit)
422    }
423
424    /// The projection to the place of type `output_ty` located `count * size_of::<ty>()` bytes
425    /// after the current place, or `count` bytes if `ty` is `None` (see `ProjectionElem::Offset`).
426    /// `output_is_sized` is the proof that `output_ty` is sized.
427    fn offset_projection(
428        &mut self,
429        count: Operand,
430        ty: Option<&Ty>,
431        in_bounds: bool,
432        output_ty: Ty,
433        ty_is_sized: Option<TraitRef>,
434    ) -> ProjectionElem {
435        let bytes = match ty {
436            None => count,
437            Some(ty) => {
438                let count_ty = count.ty().clone();
439                let size =
440                    ConstantExpr::new(ConstantExprKind::SizeOf(ty.clone()), count_ty.clone());
441                let mode = if in_bounds {
442                    OverflowMode::UB
443                } else {
444                    OverflowMode::Wrap
445                };
446                let rval = Rvalue::BinaryOp(BinOp::Mul(mode), count, Operand::Const(size));
447                Operand::Copy(self.rval_to_place(rval, count_ty))
448            }
449        };
450        ProjectionElem::Offset {
451            bytes: Box::new(bytes),
452            in_bounds,
453            ty: output_ty,
454            ty_is_sized,
455        }
456    }
457
458    /// Create a `&` borrow of the place.
459    fn borrow(&mut self, place: Place, kind: BorrowKind) -> Rvalue {
460        let ptr_metadata = self.compute_place_metadata(&place);
461        Rvalue::Ref {
462            place,
463            kind,
464            ptr_metadata,
465        }
466    }
467    /// Create a `&raw` borrow of the place.
468    fn raw_borrow(&mut self, place: Place, kind: RefKind) -> Rvalue {
469        let ptr_metadata = self.compute_place_metadata(&place);
470        Rvalue::RawPtr {
471            place,
472            kind,
473            ptr_metadata,
474        }
475    }
476
477    /// Store a `&` borrow of the place into a new place.
478    fn borrow_to_new_var(&mut self, place: Place, kind: BorrowKind, name: Option<String>) -> Place {
479        let ref_ty = TyKind::Ref(Region::Erased, place.ty().clone(), kind.into()).into_ty();
480        let target_place = self.fresh_var(name, ref_ty);
481        let rvalue = self.borrow(place, kind);
482        self.insert_assn_stmt(target_place.clone(), rvalue);
483        target_place
484    }
485    /// Store a `&raw` borrow of the place into a new place.
486    fn raw_borrow_to_new_var(
487        &mut self,
488        place: Place,
489        kind: RefKind,
490        name: Option<String>,
491    ) -> Place {
492        let ref_ty = TyKind::RawPtr(place.ty().clone(), kind).into_ty();
493        let target_place = self.fresh_var(name, ref_ty);
494        let rvalue = self.raw_borrow(place, kind);
495        self.insert_assn_stmt(target_place.clone(), rvalue);
496        target_place
497    }
498}
499
500pub struct UllbcStatementTransformCtx<'a> {
501    pub ctx: &'a mut TransformCtx,
502    pub params: &'a GenericParams,
503    pub def_id: &'a FunDeclId,
504    pub locals: &'a mut Locals,
505    /// Span of the statement being explored
506    pub span: Span,
507    /// Statements to prepend to the statement currently being explored.
508    pub statements: Vec<ullbc_ast::Statement>,
509}
510
511impl BodyTransformCtx for UllbcStatementTransformCtx<'_> {
512    fn get_crate(&self) -> &TranslatedCrate {
513        &self.ctx.translated
514    }
515    fn get_options(&self) -> &TranslateOptions {
516        &self.ctx.options
517    }
518    fn get_params(&self) -> &GenericParams {
519        self.params
520    }
521    fn get_locals_mut(&mut self) -> &mut Locals {
522        self.locals
523    }
524
525    fn insert_storage_live_stmt(&mut self, local: LocalId) {
526        self.statements.push(ullbc_ast::Statement::new(
527            self.span,
528            ullbc_ast::StatementKind::StorageLive(local),
529        ));
530    }
531
532    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
533        self.statements.push(ullbc_ast::Statement::new(
534            self.span,
535            ullbc_ast::StatementKind::Assign(place, rvalue),
536        ));
537    }
538
539    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
540        self.statements.push(ullbc_ast::Statement::new(
541            self.span,
542            ullbc_ast::StatementKind::StorageDead(local),
543        ));
544    }
545}
546
547pub struct LlbcStatementTransformCtx<'a> {
548    pub ctx: &'a mut TransformCtx,
549    pub params: &'a GenericParams,
550    pub locals: &'a mut Locals,
551    /// Span of the statement being explored
552    pub span: Span,
553    /// Statements to prepend to the statement currently being explored.
554    pub statements: Vec<llbc_ast::Statement>,
555}
556
557impl BodyTransformCtx for LlbcStatementTransformCtx<'_> {
558    fn get_crate(&self) -> &TranslatedCrate {
559        &self.ctx.translated
560    }
561    fn get_options(&self) -> &TranslateOptions {
562        &self.ctx.options
563    }
564    fn get_params(&self) -> &GenericParams {
565        self.params
566    }
567    fn get_locals_mut(&mut self) -> &mut Locals {
568        self.locals
569    }
570
571    fn insert_storage_live_stmt(&mut self, local: LocalId) {
572        self.statements.push(llbc_ast::Statement::new(
573            self.span,
574            llbc_ast::StatementKind::StorageLive(local),
575        ));
576    }
577
578    fn insert_assn_stmt(&mut self, place: Place, rvalue: Rvalue) {
579        self.statements.push(llbc_ast::Statement::new(
580            self.span,
581            llbc_ast::StatementKind::Assign(place, rvalue),
582        ));
583    }
584
585    fn insert_storage_dead_stmt(&mut self, local: LocalId) {
586        self.statements.push(llbc_ast::Statement::new(
587            self.span,
588            llbc_ast::StatementKind::StorageDead(local),
589        ));
590    }
591}
592
593impl FunDecl {
594    pub fn transform_ullbc_statements(
595        &mut self,
596        ctx: &mut TransformCtx,
597        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Statement),
598    ) {
599        if let Some(body) = self.body.as_unstructured_mut() {
600            let mut ctx = UllbcStatementTransformCtx {
601                ctx,
602                params: &self.generics,
603                def_id: &self.def_id,
604                locals: &mut body.locals,
605                span: self.item_meta.span,
606                statements: Vec::new(),
607            };
608            body.body.iter_mut().for_each(|block| {
609                ctx.statements = Vec::with_capacity(block.statements.len());
610                for mut st in mem::take(&mut block.statements) {
611                    ctx.span = st.span;
612                    f(&mut ctx, &mut st);
613                    ctx.statements.push(st);
614                }
615                block.statements = mem::take(&mut ctx.statements);
616            });
617        }
618    }
619
620    pub fn transform_ullbc_terminators(
621        &mut self,
622        ctx: &mut TransformCtx,
623        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut ullbc_ast::Terminator),
624    ) {
625        if let Some(body) = self.body.as_unstructured_mut() {
626            let mut ctx = UllbcStatementTransformCtx {
627                ctx,
628                params: &self.generics,
629                def_id: &self.def_id,
630                locals: &mut body.locals,
631                span: self.item_meta.span,
632                statements: Vec::new(),
633            };
634            body.body.iter_mut().for_each(|block| {
635                ctx.span = block.terminator.span;
636                ctx.statements = mem::take(&mut block.statements);
637                f(&mut ctx, &mut block.terminator);
638                block.statements = mem::take(&mut ctx.statements);
639            });
640        }
641    }
642
643    pub fn transform_ullbc_operands(
644        &mut self,
645        ctx: &mut TransformCtx,
646        mut f: impl FnMut(&mut UllbcStatementTransformCtx, &mut Operand),
647    ) {
648        self.transform_ullbc_statements(ctx, |ctx, st| {
649            st.kind.dyn_visit_in_body_mut(|op: &mut Operand| f(ctx, op));
650        });
651        self.transform_ullbc_terminators(ctx, |ctx, st| {
652            st.kind.dyn_visit_in_body_mut(|op: &mut Operand| f(ctx, op));
653        });
654    }
655
656    pub fn transform_llbc_statements(
657        &mut self,
658        ctx: &mut TransformCtx,
659        mut f: impl FnMut(&mut LlbcStatementTransformCtx, &mut llbc_ast::Statement),
660    ) {
661        if let Some(body) = self.body.as_structured_mut() {
662            let mut ctx = LlbcStatementTransformCtx {
663                ctx,
664                locals: &mut body.locals,
665                statements: Vec::new(),
666                span: self.item_meta.span,
667                params: &self.generics,
668            };
669            body.body.visit_blocks_bwd(|block: &mut llbc_ast::Block| {
670                ctx.statements = Vec::with_capacity(block.statements.len());
671                for mut st in mem::take(&mut block.statements) {
672                    ctx.span = st.span;
673                    f(&mut ctx, &mut st);
674                    ctx.statements.push(st);
675                }
676                block.statements = mem::take(&mut ctx.statements)
677            })
678        }
679    }
680}