charon_lib/transform/update_closure_signatures.rs
1//! # Micro-pass: the first local variable of closures is (a borrow to) the closure itself. This is
2//! not consistent with the closure signature, which represents the captured state as a tuple. This
3//! micro-pass updates this.
4use crate::ids::Vector;
5use crate::transform::TransformCtx;
6use crate::ullbc_ast::*;
7use derive_generic_visitor::{Continue, ControlFlow, Visitor};
8
9use super::ctx::UllbcPass;
10
11#[derive(Visitor)]
12struct InsertRegions<'a> {
13 regions: &'a mut Vector<RegionId, RegionVar>,
14 // The number of region groups we dived into (we don't count the regions
15 // at the declaration level). We use this for the DeBruijn indices.
16 depth: usize,
17}
18
19impl VisitAstMut for InsertRegions<'_> {
20 fn exit_region(&mut self, r: &mut Region) {
21 if r == &Region::Erased {
22 // Insert a fresh region
23 let index = self
24 .regions
25 .push_with(|index| RegionVar { index, name: None });
26 *r = Region::Var(DeBruijnVar::bound(DeBruijnId::new(self.depth), index));
27 }
28 }
29
30 fn visit_region_binder<T: AstVisitable>(
31 &mut self,
32 x: &mut RegionBinder<T>,
33 ) -> ControlFlow<Self::Break> {
34 self.depth += 1;
35 self.visit_inner(x)?;
36 self.depth -= 1;
37 Continue(())
38 }
39}
40
41fn transform_function(_ctx: &TransformCtx, def: &mut FunDecl) -> Result<(), Error> {
42 let FunSig {
43 closure_info,
44 inputs,
45 generics,
46 ..
47 } = &mut def.signature;
48 if let Some(info) = closure_info {
49 // Explore the state and introduce fresh regions for the erased regions we find.
50 let mut visitor = InsertRegions {
51 regions: &mut generics.regions,
52 depth: 0,
53 };
54 let _ = visitor.visit(&mut inputs[0]);
55
56 // Update the body.
57 // We change the type of the local variable of index 1, which is a reference to the closure
58 // itself, so that it has the type of (a reference to) the state of the closure.
59 //
60 // For instance, in the example below:
61 // ```text
62 // pub fn test_closure_capture(x: u32, y: u32) -> u32 {
63 // let f = &|z| x + y + z;
64 // (f)(0)
65 // }
66 // ```
67 //
68 // The first local variable in the body of the closure has type:
69 // `&'_ (fn(u32) -> u32)`
70 // We change it to:
71 // ```
72 // &'_ (&u32, &u32)
73 // ```
74 // We also update the corresponding field accesses in the body of
75 // the function.
76
77 if let Ok(body) = &mut def.body {
78 let body = body.as_unstructured_mut().unwrap();
79
80 // Update the type of the local 1 (which is the closure)
81 assert!(body.locals.locals.elem_count() > 1);
82 let state_var = &mut body.locals.locals[1];
83 state_var.ty = inputs[0].clone();
84 state_var.name = Some("state".to_string());
85
86 // Update the body, and in particular the accesses to the states
87 // TODO: translate to ADT field access
88 // TODO: handle directly during translation
89 let num_fields = info.state.elem_count();
90 body.body.dyn_visit_in_body_mut(|pe: &mut ProjectionElem| {
91 if let ProjectionElem::Field(pk @ FieldProjKind::ClosureState, _) = pe {
92 *pk = FieldProjKind::Tuple(num_fields);
93 }
94 });
95 }
96
97 Ok(())
98 } else {
99 Ok(())
100 }
101}
102
103pub struct Transform;
104impl UllbcPass for Transform {
105 fn transform_function(&self, ctx: &mut TransformCtx, def: &mut FunDecl) {
106 // Ignore the errors, which should have been reported
107 let _ = transform_function(ctx, def);
108 }
109}