charon_lib/transform/
reconstruct_boxes.rs

1//! # Micro-pass: reconstruct piecewise box allocations using `malloc` and `ShallowInitBox`.
2
3use crate::register_error;
4use crate::transform::TransformCtx;
5use crate::ullbc_ast::*;
6
7use super::ctx::UllbcPass;
8
9pub struct Transform;
10
11/// The special `alloc::boxed::box_new(x)` intrinsic becomes the following:
12///
13/// ```text
14/// @2 := size_of<i32>
15/// @3 := align_of<i32>
16/// @4 := alloc::alloc::exchange_malloc(move (@2), move (@3))
17/// storage_live(@5)
18/// @5 := shallow_init_box::<i32>(move (@4))
19/// // possibly some intermediate statements
20/// *(@5) := x
21/// ```
22///
23/// We reconstruct this into a call to `Box::new(x)`.
24impl UllbcPass for Transform {
25    fn transform_body(&self, ctx: &mut TransformCtx, b: &mut ExprBody) {
26        // We need to find a block that has exchange_malloc as the following terminator:
27        // ```text
28        // @4 := alloc::alloc::exchange_malloc(move (@2), move (@3))
29        // ```
30        // We then chekc that that this block ends with two assignments:
31        // ```text
32        // @2 := size_of<i32>
33        // @3 := align_of<i32>
34        // ```
35        // If that is the case, we look at the target block and check that it starts with`
36        // ```text
37        // storage_live(@5)
38        // @5 := shallow_init_box::<i32>(move (@4))
39        // ```
40        // We then look for the assignment into the box and take a not of its index.
41        // ```text
42        // *(@5) := x
43        // ```
44        // Finally, we replace all these assignments with a call to `@5 = Box::new(x)`
45        // We do so by replacing the terminator (exchange_malloc) with the correct call
46        // and replacing the assignment @3 := align_of<i32> with the storage live.
47        // Everything else becomes Nop.
48
49        for candidate_block_idx in b.body.all_indices() {
50            let second_block;
51            let at_5;
52            let box_generics;
53            let value_to_write;
54            let old_assign_idx;
55            let assign_span;
56            let unwind_target;
57
58            if let Some(candidate_block) = b.body.get(candidate_block_idx)
59                // If the terminator is a call
60                && let RawTerminator::Call {
61                    target: target_block_idx,
62                    call:
63                        Call {
64                            args: malloc_args,
65                            func: _, // TODO: once we have a system to recognize intrinsics, check the call is to exchange_malloc.
66                            dest: malloc_dest,
67                        },
68                        on_unwind,
69                } = &candidate_block.terminator.content
70                // The call has two move arguments
71                && let [Operand::Move(arg0), Operand::Move(arg1)] = malloc_args.as_slice()
72                && let [ .., Statement {
73                            content: RawStatement::Assign(size, Rvalue::NullaryOp(NullOp::SizeOf, _)),
74                            ..
75                        }, Statement {
76                            content: RawStatement::Assign(align, Rvalue::NullaryOp(NullOp::AlignOf, _)),
77                            ..
78                        }] = candidate_block.statements.as_slice()
79                && arg0 == size && arg1 == align
80                && let Some(target_block) = b.body.get(*target_block_idx)
81                && let [Statement {
82                            content: RawStatement::StorageLive(target_var),
83                            ..
84                        }, Statement {
85                            content:
86                                RawStatement::Assign(box_make, Rvalue::ShallowInitBox(Operand::Move(alloc_use), _)),
87                            ..
88                        }, rest @ ..] = target_block.statements.as_slice()
89                && alloc_use == malloc_dest
90                && box_make.is_local()
91                && box_make.local_id() == *target_var
92                && let TyKind::Adt(TypeId::Builtin(BuiltinTy::Box), generics) =
93                    b.locals[*target_var].ty.kind()
94                && let Some((assign_idx_in_rest, val, span)) = rest.iter().enumerate().find_map(|(idx, st)| {
95                    if let Statement {
96                            content: RawStatement::Assign(box_deref, val),
97                            span,
98                            ..
99                        } = st
100                        && let Some((sub, ProjectionElem::Deref)) = box_deref.as_projection()
101                        && sub == box_make
102                    {
103                        Some((idx, val, span))
104                    } else {
105                        None
106                    }
107                })
108            {
109                at_5 = box_make.clone();
110                old_assign_idx = assign_idx_in_rest + 2; // +2 because rest skips the first two statements
111                value_to_write = val.clone();
112                box_generics = generics.clone();
113                second_block = *target_block_idx;
114                assign_span = *span;
115                unwind_target = *on_unwind;
116            } else {
117                continue;
118            }
119
120            let first_block = b.body.get_mut(candidate_block_idx).unwrap();
121            let number_statements = first_block.statements.len();
122            let value_to_write = match value_to_write {
123                Rvalue::Use(op) => {
124                    first_block
125                        .statements
126                        .get_mut(number_statements - 2)
127                        .unwrap()
128                        .content = RawStatement::Nop;
129                    op
130                }
131                _ => {
132                    // We need to create a new variable to store the value.
133                    let name = b.locals[at_5.local_id()].name.clone();
134                    let ty = box_generics.types[0].clone();
135                    let var = b.locals.new_var(name, ty);
136                    let st = Statement::new(
137                        assign_span,
138                        RawStatement::Assign(var.clone(), value_to_write),
139                    );
140                    // We overide the @2 := size_of<i32> statement with the rvalue assignment
141                    *first_block
142                        .statements
143                        .get_mut(number_statements - 2)
144                        .unwrap() = st;
145                    Operand::Move(var)
146                }
147            };
148            first_block
149                .statements
150                .get_mut(number_statements - 1)
151                .unwrap()
152                .content = RawStatement::StorageLive(at_5.local_id());
153            first_block.terminator.content = RawTerminator::Call {
154                call: Call {
155                    func: FnOperand::Regular(FnPtr {
156                        func: Box::new(FunIdOrTraitMethodRef::Fun(FunId::Builtin(
157                            BuiltinFunId::BoxNew,
158                        ))),
159                        generics: Box::new(box_generics),
160                    }),
161                    args: vec![value_to_write],
162                    dest: at_5,
163                },
164                target: second_block,
165                on_unwind: unwind_target,
166            };
167
168            // We now update the statements in the second block.
169            let second_block = b.body.get_mut(second_block).unwrap();
170            second_block.statements.get_mut(0).unwrap().content = RawStatement::Nop;
171            second_block.statements.get_mut(1).unwrap().content = RawStatement::Nop;
172            second_block
173                .statements
174                .get_mut(old_assign_idx)
175                .unwrap()
176                .content = RawStatement::Nop;
177        }
178
179        // Make sure we got all the `ShallowInitBox`es.
180        b.body.dyn_visit_in_body(|rvalue: &Rvalue| {
181            if rvalue.is_shallow_init_box() {
182                register_error!(
183                    ctx,
184                    b.span,
185                    "Could not reconstruct `Box` initialization; \
186                    branching during `Box` initialization is not supported."
187                );
188            }
189        });
190    }
191}