charon_lib/transform/
index_intermediate_assigns.rs

1//! This micro-pass introduces intermediate assignments in preparation of
2//! [`index_to_function_calls`], so as to avoid borrow-checking errors.
3//!
4//! The problem comes from "and assignments" like in the snippet of code below:
5//! ```text
6//! x[0] += 1; // Desugars to: x[0] = copy x[0] + 1
7//! ```
8//!
9//! If we don't introduce an intermediate assignment, then the micro-pass which
10//! transforms the index operations into function calls generates the following
11//! LLBC:
12//! ```text
13//! dest = &mut x[0];
14//! src = & x[0]; // Invalidates dest
15//! *dest = copy (*src) + 1; // Can't dereference dest!
16//! ```
17//! (also note that the problem remains if we introduce `src` *then* `dest`).
18//!
19//! Our solution is to introduce a temporary variable for the assignment.
20//! We first transform the code to:
21//! ```text
22//! tmp = copy x[0] + 1;
23//! x[0] = move tmp;
24//! ```
25//!
26//! Which then allows us to transform it to:
27//! ```text
28//! // RHS:
29//! src = & x[0];
30//! tmp = copy (*src) + 1;
31//! // LHS:
32//! dest = &mut x[0];
33//! *dest = move tmp;
34//! ```
35
36use crate::transform::TransformCtx;
37use crate::ullbc_ast::*;
38
39use super::ctx::UllbcPass;
40
41fn contains_index_proj<T: BodyVisitable>(x: &T) -> bool {
42    let mut contains_index = false;
43    x.dyn_visit_in_body(|proj: &ProjectionElem| {
44        use ProjectionElem::*;
45        if let Index { .. } | Subslice { .. } = proj {
46            contains_index = true;
47        }
48    });
49    contains_index
50}
51
52impl Place {
53    fn contains_index_proj(&self) -> bool {
54        contains_index_proj(self)
55    }
56}
57
58impl Rvalue {
59    fn contains_index_proj(&self) -> bool {
60        contains_index_proj(self)
61    }
62}
63
64pub struct Transform;
65
66impl UllbcPass for Transform {
67    fn transform_body(&self, _ctx: &mut TransformCtx, b: &mut ExprBody) {
68        for block in &mut b.body {
69            block.transform(|st: &mut Statement| {
70                match &mut st.content {
71                    // Introduce an intermediate statement if both the rhs and the lhs contain an
72                    // "index" projection element (to avoid introducing too many intermediate
73                    // assignments).
74                    RawStatement::Assign(lhs, rhs)
75                        if lhs.contains_index_proj() && rhs.contains_index_proj() =>
76                    {
77                        // Fresh local variable for the temporary assignment
78                        let tmp_var = b.locals.new_var(None, lhs.ty().clone());
79                        let tmp_rhs =
80                            std::mem::replace(rhs, Rvalue::Use(Operand::Move(tmp_var.clone())));
81                        // Introduce the intermediate let-binding
82                        vec![Statement::new(
83                            st.span,
84                            RawStatement::Assign(tmp_var, tmp_rhs),
85                        )]
86                    }
87                    _ => vec![],
88                }
89            });
90        }
91    }
92}