charon_lib/transform/simplify_output/index_intermediate_assigns.rs
1//! This micro-pass introduces intermediate assignments in preparation of
2//! [`crate::transform::simplify_output::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::transform::ctx::{BodyTransformCtx, UllbcPass};
38use crate::ullbc_ast::*;
39
40fn contains_index_proj<T: BodyVisitable>(x: &T) -> bool {
41 let mut contains_index = false;
42 x.dyn_visit_in_body(|proj: &ProjectionElem| {
43 use ProjectionElem::*;
44 if let Index { .. } | Subslice { .. } = proj {
45 contains_index = true;
46 }
47 });
48 contains_index
49}
50
51impl Place {
52 fn contains_index_proj(&self) -> bool {
53 contains_index_proj(self)
54 }
55}
56
57impl Rvalue {
58 fn contains_index_proj(&self) -> bool {
59 contains_index_proj(self)
60 }
61}
62
63pub struct Transform;
64
65impl UllbcPass for Transform {
66 fn transform_function(&self, ctx: &mut TransformCtx, decl: &mut FunDecl) {
67 decl.transform_ullbc_statements(ctx, |ctx, st: &mut Statement| {
68 // Introduce an intermediate statement if both the rhs and the lhs contain an
69 // "index" projection element (to avoid introducing too many intermediate
70 // assignments).
71 if let StatementKind::Assign(lhs, rhs) = &mut st.kind
72 && lhs.contains_index_proj()
73 && rhs.contains_index_proj()
74 {
75 // Fresh local variable for the temporary assignment
76 let tmp_var = ctx.fresh_var(None, lhs.ty().clone());
77 let tmp_rhs = std::mem::replace(rhs, Rvalue::Use(Operand::Move(tmp_var.clone())));
78 // Introduce the intermediate let-binding
79 ctx.insert_assn_stmt(tmp_var, tmp_rhs)
80 }
81 });
82 }
83}