Skip to main content

charon_lib/transform/finish_translation/
insert_storage_statements.rs

1//! Add missing storage markers -- in MIR, some locals are considered "always" initialised, and have
2//! no StorageLive and StorageDead instructions associated; this always includes the arguments
3//! and the return local, but also sometimes includes other locals. We make sure these additional
4//! locals get initialised at the start of the function and deallocated before function exits if
5//! they're used anywhere.
6use derive_generic_visitor::Visitor;
7
8use crate::ast::*;
9use crate::ids::IndexVec;
10use crate::transform::TransformCtx;
11use crate::transform::ctx::{TransformPass, UllbcPass};
12use crate::ullbc_ast::{BlockId, TerminatorKind};
13
14#[derive(Visitor)]
15struct StorageVisitor {
16    local_status: IndexVec<LocalId, LocalStatus>,
17}
18
19struct LocalStatus {
20    used: bool,
21    has_storage_live: bool,
22    has_storage_dead: bool,
23}
24
25impl StorageVisitor {
26    fn new(locals: &Locals) -> Self {
27        let local_status = locals.locals.map_ref(|local| {
28            let is_return = local.index == LocalId::ZERO;
29            let is_argument = !is_return && locals.is_return_or_arg(local.index);
30            LocalStatus {
31                used: is_return || is_argument,
32                has_storage_live: is_argument,
33                has_storage_dead: is_return,
34            }
35        });
36        Self { local_status }
37    }
38
39    fn locals_missing_storage_lives(&self) -> Vec<LocalId> {
40        self.local_status
41            .iter_enumerated()
42            .filter(|(_, status)| status.used && !status.has_storage_live)
43            .map(|(local, _)| local)
44            .collect()
45    }
46
47    fn locals_missing_storage_deads(&self) -> Vec<LocalId> {
48        self.local_status
49            .iter_enumerated()
50            .filter(|(_, status)| status.used && !status.has_storage_dead)
51            .map(|(local, _)| local)
52            .collect()
53    }
54}
55
56impl VisitBody for StorageVisitor {
57    fn visit_locals(&mut self, _: &Locals) -> ::std::ops::ControlFlow<Self::Break> {
58        // Don't look inside the local declarations otherwise we'll think they're all used.
59        ControlFlow::Continue(())
60    }
61    fn enter_local_id(&mut self, lid: &LocalId) {
62        self.local_status[*lid].used = true;
63    }
64    fn enter_llbc_statement(&mut self, st: &llbc_ast::Statement) {
65        match &st.kind {
66            llbc_ast::StatementKind::StorageLive(lid) => {
67                self.local_status[*lid].has_storage_live = true;
68            }
69            llbc_ast::StatementKind::StorageDead(lid) => {
70                self.local_status[*lid].has_storage_dead = true;
71            }
72            _ => {}
73        }
74    }
75    fn enter_ullbc_statement(&mut self, st: &ullbc_ast::Statement) {
76        match &st.kind {
77            ullbc_ast::StatementKind::StorageLive(lid) => {
78                self.local_status[*lid].has_storage_live = true;
79            }
80            ullbc_ast::StatementKind::StorageDead(lid) => {
81                self.local_status[*lid].has_storage_dead = true;
82            }
83            _ => {}
84        }
85    }
86}
87
88pub struct Transform;
89impl Transform {
90    fn transform_ullbc_body(
91        &self,
92        body: &mut ullbc_ast::ExprBody,
93        insert_missing_storage_deads: bool,
94    ) {
95        let mut storage_visitor = StorageVisitor::new(&body.locals);
96        let _ = storage_visitor.visit(body);
97
98        // Insert StorageLive instructions for the always initialised locals.
99        let locals_with_missing_storage_lives = storage_visitor.locals_missing_storage_lives();
100        let first_block = body.body.get_mut(BlockId::ZERO).unwrap();
101        let first_span = if let Some(fst) = first_block.statements.first() {
102            fst.span
103        } else {
104            first_block.terminator.span
105        };
106        let new_statements = locals_with_missing_storage_lives.iter().map(|local| {
107            ullbc_ast::Statement::new(first_span, ullbc_ast::StatementKind::StorageLive(*local))
108        });
109        first_block.statements.splice(0..0, new_statements);
110
111        // Insert StorageDead instructions before every function exit.
112        if insert_missing_storage_deads {
113            let locals_with_missing_storage_deads = storage_visitor.locals_missing_storage_deads();
114            for block in &mut body.body {
115                if matches!(
116                    block.terminator.kind,
117                    TerminatorKind::Abort(AbortKind::Panic(..) | AbortKind::UnwindTerminate)
118                        | TerminatorKind::Return
119                        | TerminatorKind::UnwindResume
120                ) {
121                    let span = block.terminator.span;
122                    block
123                        .statements
124                        .extend(locals_with_missing_storage_deads.iter().rev().map(|local| {
125                            ullbc_ast::Statement::new(
126                                span,
127                                ullbc_ast::StatementKind::StorageDead(*local),
128                            )
129                        }));
130                }
131            }
132        }
133    }
134
135    fn transform_llbc_body(
136        &self,
137        body: &mut llbc_ast::ExprBody,
138        insert_missing_storage_deads: bool,
139    ) {
140        let mut storage_visitor = StorageVisitor::new(&body.locals);
141        let _ = storage_visitor.visit(body);
142
143        let locals_with_missing_storage_lives = storage_visitor.locals_missing_storage_lives();
144        let first_span = if let Some(fst) = body.body.statements.first() {
145            fst.span
146        } else {
147            body.span
148        };
149        let new_statements = locals_with_missing_storage_lives.iter().map(|local| {
150            llbc_ast::Statement::new(first_span, llbc_ast::StatementKind::StorageLive(*local))
151        });
152        body.body.statements.splice(0..0, new_statements);
153
154        if insert_missing_storage_deads {
155            let locals_with_missing_storage_deads = storage_visitor.locals_missing_storage_deads();
156            body.body.transform_sequences(|statements| {
157                if !matches!(
158                    &statements[0].kind,
159                    llbc_ast::StatementKind::Abort(
160                        AbortKind::Panic(..) | AbortKind::UnwindTerminate
161                    ) | llbc_ast::StatementKind::Return
162                ) {
163                    return Vec::new();
164                }
165                let span = statements[0].span;
166                locals_with_missing_storage_deads
167                    .iter()
168                    .rev()
169                    .map(|local| {
170                        llbc_ast::Statement::new(span, llbc_ast::StatementKind::StorageDead(*local))
171                    })
172                    .collect()
173            });
174        }
175    }
176
177    fn transform_function(&self, fun: &mut FunDecl) {
178        // Don't insert `StorageDead`s inside global initializers because the locals there are
179        // actually statics.
180        let insert_missing_storage_deads = fun.is_global_initializer.is_none();
181        match &mut fun.body {
182            Body::Unstructured(body) => {
183                self.transform_ullbc_body(body, insert_missing_storage_deads)
184            }
185            Body::Structured(body) => self.transform_llbc_body(body, insert_missing_storage_deads),
186            _ => {}
187        }
188    }
189}
190
191impl UllbcPass for Transform {
192    fn transform_function(&self, _ctx: &mut TransformCtx, fun: &mut FunDecl) {
193        self.transform_function(fun)
194    }
195}
196
197impl TransformPass for Transform {
198    fn transform_ctx(&self, ctx: &mut TransformCtx) {
199        ctx.for_each_fun_decl(|_ctx, fun| self.transform_function(fun));
200    }
201}