charon_lib/ast/bodies/structured.rs
1//! Bodies with structured control-flow (`if ... then ... else ...`,
2//! `loop { ... }`, etc).
3//!
4//! We reconstruct this structure from the unstructured ast in an optional translation pass.
5use derive_generic_visitor::*;
6use macros::{EnumAsGetters, EnumIsA, EnumToGetters};
7use serde_state::{DeserializeState, SerializeState};
8use std::mem;
9use std::sync::atomic::{AtomicUsize, Ordering};
10
11use crate::ast::*;
12
13// Globally-unique identifier for each statement.
14generate_index_type!(StatementId);
15// Globally-unique identifier for each block.
16generate_index_type!(BlockId);
17
18pub type ExprBody = GExprBody<Block>;
19
20/// A sequence of statements.
21#[derive(Debug, Clone, Eq)]
22#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
23#[serde_state(state_implements = DedupSerializerState)] // Avoid corecursive impls due to perfect derive
24pub struct Block {
25 pub span: Span,
26 /// Integer uniquely identifying this block. To simplify things we generate globally-fresh ids
27 /// when creating a new `Block`.
28 #[cfg_attr(feature = "charon_on_charon", charon::rename("block_id"))]
29 pub id: BlockId,
30 pub statements: Vec<Statement>,
31}
32
33/// A statement, which can contain nested statements inside loops or switchers.
34#[derive(Debug, Clone, Eq)]
35#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
36pub struct Statement {
37 pub span: Span,
38 /// Integer uniquely identifying this statement among the statmeents in the current body. To
39 /// simplify things we generate globally-fresh ids when creating a new `Statement`.
40 #[cfg_attr(feature = "charon_on_charon", charon::rename("statement_id"))]
41 pub id: StatementId,
42 pub kind: StatementKind,
43 /// Comments that precede this statement.
44 // This is filled in a late pass after all the control-flow manipulation.
45 pub comments_before: Vec<String>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[derive(EnumIsA, EnumToGetters, EnumAsGetters)]
50#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
51pub enum StatementKind {
52 /// Assigns an `Rvalue` to a `Place`. e.g. `let y = x;` could become
53 /// `y := move x` which is represented as `Assign(y, Rvalue::Use(Operand::Move(x)))`.
54 Assign(Place, Rvalue),
55 /// Not used today because we take MIR built.
56 SetDiscriminant(Place, VariantId),
57 /// Indicates that this local should be allocated; if it is already allocated, this frees
58 /// the local and re-allocates it. The arguments do not receive a `StorageLive`. We ensure in
59 /// the micro-pass `insert_storage_statements` that all other locals have a `StorageLive`
60 /// associated with them.
61 StorageLive(LocalId),
62 /// Deallocates the given local; if it is already deallocated, this is
63 /// a no-op. Not all local deallocations are explicit: if a non-return local is still live at
64 /// function end (return or unwind), it is implicitly deallocated.
65 /// If `--deallocate-all-locals` is set, all local deallocations are made explicit.
66 StorageDead(LocalId),
67 /// A place is mentioned, but not accessed. The place itself must still be valid though, so
68 /// this statement is not a no-op: it can trigger UB if the place's projections are not valid
69 /// (e.g. because they go out of bounds).
70 PlaceMention(Place),
71 /// Statements that only affect borrow-checking.
72 Borrowck(BorrowckStatement),
73 /// Drop the value at the given place.
74 ///
75 /// Depending on `DropKind`, this may be a real call to `drop_glue`, or a conditional call
76 /// that should only happen if the place has not been moved out of. See the docs of `DropKind`
77 /// for more details; to get precise drops use `--precise-drops`.
78 Drop {
79 place: Place,
80 /// Reference to the `drop_glue` code to call on drop.
81 fn_ptr: FnPtr,
82 kind: DropKind,
83 on_unwind: Block,
84 },
85 Assert {
86 assert: Assert,
87 on_failure: AbortKind,
88 on_unwind: Block,
89 },
90 /// An inline assembly block.
91 InlineAsm {
92 asm: InlineAsm,
93 /// Next block if the control-flow continues without jumping. Absent for `naked_asm!` and `noreturn`.
94 fallthrough: Option<Block>,
95 /// Targets of [`AsmOperand::Label`] operands.
96 labels: IndexVec<BranchId, Block>,
97 /// Action to be taken if the inline assembly unwinds.
98 on_unwind: Block,
99 },
100 Call {
101 call: Call,
102 on_unwind: Block,
103 },
104 Switch {
105 data: SwitchData,
106 branches: IndexVec<BranchId, Block>,
107 },
108
109 Loop(Block),
110 /// Break to outer loops.
111 /// The `usize` gives the index of the outer loop to break to:
112 /// * 0: break to first outer loop (the current loop)
113 /// * 1: break to second outer loop
114 /// * ...
115 Break(usize),
116 /// Continue to outer loops.
117 /// The `usize` gives the index of the outer loop to continue to:
118 /// * 0: continue to first outer loop (the current loop)
119 /// * 1: continue to second outer loop
120 /// * ...
121 Continue(usize),
122
123 /// Call to a built-in panicking function.
124 Panic {
125 /// The name of the function that was called.
126 name: Name,
127 on_unwind: Block,
128 },
129 /// Unwinding must stop for ABI reasons or because cleanup code panicked again.
130 UnwindTerminate,
131 /// Unwind out of the current function into its caller.
132 UnwindResume,
133
134 Return,
135 /// Reaching this point is undefined behavior in the Rust abstract machine.
136 UndefinedBehavior,
137 /// No-op.
138 Nop,
139}
140
141/// Ignores statement ids.
142impl PartialEq for Statement {
143 fn eq(&self, other: &Self) -> bool {
144 self.span == other.span
145 && self.kind == other.kind
146 && self.comments_before == other.comments_before
147 }
148}
149
150/// Ignores block ids.
151impl PartialEq for Block {
152 fn eq(&self, other: &Self) -> bool {
153 self.span == other.span && self.statements == other.statements
154 }
155}
156
157impl Block {
158 pub fn new(span: Span, statements: Vec<Statement>) -> Self {
159 Block {
160 span,
161 id: BlockId::fresh(),
162 statements,
163 }
164 }
165
166 pub fn new_unreachable(span: Span) -> Self {
167 Statement::new(span, StatementKind::UndefinedBehavior).into_block()
168 }
169
170 pub fn from_seq(seq: Vec<Statement>) -> Option<Self> {
171 if seq.is_empty() {
172 None
173 } else {
174 let span = seq
175 .iter()
176 .map(|st| st.span)
177 .reduce(|a, b| meta::combine_span(&a, &b))
178 .unwrap();
179 Some(Block::new(span, seq))
180 }
181 }
182
183 pub fn merge(mut self, mut other: Self) -> Self {
184 self.span = meta::combine_span(&self.span, &other.span);
185 self.statements.append(&mut other.statements);
186 self
187 }
188
189 pub fn then(mut self, r: Statement) -> Self {
190 self.span = meta::combine_span(&self.span, &r.span);
191 self.statements.push(r);
192 self
193 }
194
195 pub fn then_opt(self, other: Option<Statement>) -> Self {
196 if let Some(other) = other {
197 self.then(other)
198 } else {
199 self
200 }
201 }
202
203 /// Apply a function to all the statements, in a top-down manner.
204 pub fn visit_statements<F: FnMut(&mut Statement)>(&mut self, f: F) {
205 self.visit_helper(|_| {}, f);
206 }
207
208 /// Apply a transformer to all the statements, in a bottom-up manner. Compared to `transform`,
209 /// this also gives access to the following statements if any. Statements that are not part of
210 /// a sequence will be traversed as `[st]`. Statements that are will be traversed twice: once
211 /// as `[st]`, and then as `[st, ..]` with the following statements if any.
212 ///
213 /// The transformer should:
214 /// - mutate the current statements in place
215 /// - return the sequence of statements to introduce before the current statements
216 pub fn transform_sequences<F: FnMut(&mut [Statement]) -> Vec<Statement>>(&mut self, mut f: F) {
217 self.visit_blocks_bwd(|blk: &mut Block| {
218 let mut final_len = blk.statements.len();
219 let mut to_insert = vec![];
220 for i in (0..blk.statements.len()).rev() {
221 let new_to_insert = f(&mut blk.statements[i..]);
222 final_len += new_to_insert.len();
223 to_insert.push((i, new_to_insert));
224 }
225 if !to_insert.is_empty() {
226 to_insert.sort_by_key(|(i, _)| *i);
227 // Make it so the first element is always at the end so we can pop it.
228 to_insert.reverse();
229 // Construct the merged list of statements.
230 let old_statements =
231 mem::replace(&mut blk.statements, Vec::with_capacity(final_len));
232 for (i, stmt) in old_statements.into_iter().enumerate() {
233 while let Some((j, _)) = to_insert.last()
234 && *j == i
235 {
236 let (_, mut stmts) = to_insert.pop().unwrap();
237 blk.statements.append(&mut stmts);
238 }
239 blk.statements.push(stmt);
240 }
241 }
242 })
243 }
244
245 /// Visit `self` and its sub-blocks in a bottom-up (post-order) traversal.
246 pub fn visit_blocks_bwd<F: FnMut(&mut Block)>(&mut self, f: F) {
247 self.visit_helper(f, |_| {});
248 }
249
250 /// Small visitor helper to visit statements and blocks.
251 fn visit_helper<F: FnMut(&mut Block), G: FnMut(&mut Statement)>(
252 &mut self,
253 exit_blk: F,
254 enter_stmt: G,
255 ) {
256 #[derive(Visitor)]
257 pub struct BlockVisitor<F: FnMut(&mut Block), G: FnMut(&mut Statement)> {
258 exit_blk: F,
259 enter_stmt: G,
260 }
261
262 impl<F: FnMut(&mut Block), G: FnMut(&mut Statement)> VisitBodyMut for BlockVisitor<F, G> {
263 fn exit_llbc_block(&mut self, x: &mut Block) {
264 (self.exit_blk)(x)
265 }
266 fn enter_llbc_statement(&mut self, x: &mut Statement) {
267 (self.enter_stmt)(x)
268 }
269 }
270 BlockVisitor {
271 exit_blk,
272 enter_stmt,
273 }
274 .visit_by_val_infallible(self);
275 }
276}
277
278impl BlockId {
279 pub fn fresh() -> BlockId {
280 static COUNTER: AtomicUsize = AtomicUsize::new(0);
281 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
282 BlockId::new(id)
283 }
284}
285
286impl Statement {
287 pub fn new(span: Span, kind: StatementKind) -> Self {
288 Statement {
289 span,
290 id: StatementId::fresh(),
291 kind,
292 comments_before: vec![],
293 }
294 }
295
296 pub fn into_box(self) -> Box<Self> {
297 Box::new(self)
298 }
299
300 pub fn into_block(self) -> Block {
301 Block::new(self.span, vec![self])
302 }
303}
304
305impl StatementId {
306 pub fn fresh() -> StatementId {
307 static COUNTER: AtomicUsize = AtomicUsize::new(0);
308 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
309 StatementId::new(id)
310 }
311}