charon_lib/ast/ullbc_ast.rs
1//! "Unstructured LLBC" ast (ULLBC). This is LLBC before the control-flow
2//! reconstruction. In effect, this is a cleaned up version of MIR.
3pub use crate::ast::*;
4use crate::ids::IndexVec;
5use derive_generic_visitor::{Drive, DriveMut};
6use macros::{EnumAsGetters, EnumIsA, VariantIndexArity, VariantName};
7use serde_state::{DeserializeState, SerializeState};
8
9// Block identifier. Similar to rust's `BasicBlock`.
10generate_index_type!(BlockId, "Block");
11
12// The entry block of a function is always the block with id 0
13pub static START_BLOCK_ID: BlockId = BlockId::ZERO;
14
15#[cfg_attr(feature = "charon_on_charon", charon::rename("Blocks"))]
16pub type BodyContents = IndexVec<BlockId, BlockData>;
17pub type ExprBody = GExprBody<BodyContents>;
18
19/// A raw statement: a statement without meta data.
20#[derive(
21 Debug,
22 PartialEq,
23 Eq,
24 Clone,
25 EnumIsA,
26 EnumAsGetters,
27 VariantName,
28 SerializeState,
29 DeserializeState,
30 Drive,
31 DriveMut,
32)]
33pub enum StatementKind {
34 Assign(Place, Rvalue),
35 /// A call. For now, we don't support dynamic calls (i.e. to a function pointer in memory).
36 SetDiscriminant(Place, VariantId),
37 /// Equivalent to std::intrinsics::copy_nonoverlapping; this is not modelled as a function
38 /// call as it cannot diverge
39 CopyNonOverlapping(Box<CopyNonOverlapping>),
40 /// Indicates that this local should be allocated; if it is already allocated, this frees
41 /// the local and re-allocates it. The arguments do not receive a `StorageLive`. We ensure in
42 /// the micro-pass `insert_storage_statements` that all other locals have a `StorageLive`
43 /// associated with them.
44 StorageLive(LocalId),
45 /// Indicates that this local should be deallocated; if it is already deallocated, this is
46 /// a no-op. A local may not have a `StorageDead` in the function's body, in which case it
47 /// is implicitly deallocated at the end of the function. The return local does not receive a
48 /// `StorageDead`. We ensure in the micro-pass `insert_storage_statements` that all other locals
49 /// have a `StorageDead` before function exits.
50 StorageDead(LocalId),
51 /// A place is mentioned, but not accessed. The place itself must still be valid though, so
52 /// this statement is not a no-op: it can trigger UB if the place's projections are not valid
53 /// (e.g. because they go out of bounds).
54 PlaceMention(Place),
55 /// A non-diverging runtime check for a condition. This can be either:
56 /// - Emitted for inlined "assumes" (which cause UB on failure)
57 /// - Reconstructed from `if b { panic() }` if `--reconstruct-asserts` is set.
58 ///
59 /// This statement comes with the effect that happens when the check fails
60 /// (rather than representing it as an unwinding edge).
61 Assert {
62 assert: Assert,
63 on_failure: AbortKind,
64 },
65 /// Does nothing. Useful for passes.
66 Nop,
67}
68
69#[derive(Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut)]
70pub struct Statement {
71 pub span: Span,
72 pub kind: StatementKind,
73 /// Comments that precede this statement.
74 // This is filled in a late pass after all the control-flow manipulation.
75 #[drive(skip)]
76 pub comments_before: Vec<String>,
77}
78
79#[derive(
80 Debug,
81 PartialEq,
82 Eq,
83 Clone,
84 EnumIsA,
85 EnumAsGetters,
86 VariantName,
87 VariantIndexArity,
88 SerializeState,
89 DeserializeState,
90 Drive,
91 DriveMut,
92)]
93#[cfg_attr(feature = "charon_on_charon", charon::rename("Switch"))]
94pub enum SwitchTargets {
95 /// Gives the `if` block and the `else` block
96 If(BlockId, BlockId),
97 /// Gives the integer type, a map linking values to switch branches, and the
98 /// otherwise block. Note that matches over enumerations are performed by
99 /// switching over the discriminant, which is an integer.
100 SwitchInt(LiteralTy, Vec<(Literal, BlockId)>, BlockId),
101}
102
103/// A raw terminator: a terminator without meta data.
104#[derive(
105 Debug,
106 PartialEq,
107 Eq,
108 Clone,
109 EnumIsA,
110 EnumAsGetters,
111 SerializeState,
112 DeserializeState,
113 Drive,
114 DriveMut,
115)]
116pub enum TerminatorKind {
117 Goto {
118 target: BlockId,
119 },
120 Switch {
121 discr: Operand,
122 targets: SwitchTargets,
123 },
124 Call {
125 call: Call,
126 target: BlockId,
127 on_unwind: BlockId,
128 },
129 /// Drop the value at the given place.
130 ///
131 /// Depending on `DropKind`, this may be a real call to `drop_glue`, or a conditional call
132 /// that should only happen if the place has not been moved out of. See the docs of `DropKind`
133 /// for more details; to get precise drops use `--precise-drops`.
134 Drop {
135 #[drive(skip)]
136 kind: DropKind,
137 place: Place,
138 /// Reference to the `drop_glue` code to call on drop.
139 fn_ptr: FnPtr,
140 target: BlockId,
141 on_unwind: BlockId,
142 },
143 /// Assert that the given condition holds, and if not, unwind to the given block. This is used for
144 /// bounds checks, overflow checks, etc.
145 #[cfg_attr(feature = "charon_on_charon", charon::rename("TAssert"))]
146 Assert {
147 assert: Assert,
148 target: BlockId,
149 on_unwind: BlockId,
150 },
151 /// An inline assembly block. For now we only preserve the template string.
152 InlineAsm {
153 asm: String,
154 targets: Vec<BlockId>,
155 on_unwind: BlockId,
156 },
157 /// Handles panics and impossible cases.
158 Abort(AbortKind),
159 Return,
160 /// Unwind out of the current function into its caller.
161 UnwindResume,
162}
163
164#[derive(Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut)]
165pub struct Terminator {
166 pub span: Span,
167 pub kind: TerminatorKind,
168 /// Comments that precede this terminator.
169 // This is filled in a late pass after all the control-flow manipulation.
170 #[drive(skip)]
171 pub comments_before: Vec<String>,
172}
173
174#[derive(Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut)]
175#[cfg_attr(feature = "charon_on_charon", charon::rename("Block"))]
176pub struct BlockData {
177 pub statements: Vec<Statement>,
178 pub terminator: Terminator,
179}