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#[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 Clone,
23 EnumIsA,
24 EnumAsGetters,
25 VariantName,
26 SerializeState,
27 DeserializeState,
28 Drive,
29 DriveMut,
30)]
31pub enum StatementKind {
32 Assign(Place, Rvalue),
33 /// A call. For now, we don't support dynamic calls (i.e. to a function pointer in memory).
34 SetDiscriminant(Place, VariantId),
35 /// Equivalent to std::intrinsics::copy_nonoverlapping; this is not modelled as a function
36 /// call as it cannot diverge
37 CopyNonOverlapping(Box<CopyNonOverlapping>),
38 /// Indicates that this local should be allocated; if it is already allocated, this frees
39 /// the local and re-allocates it. The return value and arguments do not receive a
40 /// `StorageLive`. We ensure in the micro-pass `insert_storage_lives` that all other locals
41 /// have a `StorageLive` associated with them.
42 StorageLive(LocalId),
43 /// Indicates that this local should be deallocated; if it is already deallocated, this is
44 /// a no-op. A local may not have a `StorageDead` in the function's body, in which case it
45 /// is implicitly deallocated at the end of the function.
46 StorageDead(LocalId),
47 /// A place is mentioned, but not accessed. The place itself must still be valid though, so
48 /// this statement is not a no-op: it can trigger UB if the place's projections are not valid
49 /// (e.g. because they go out of bounds).
50 PlaceMention(Place),
51 /// A non-diverging runtime check for a condition. This can be either:
52 /// - Emitted for inlined "assumes" (which cause UB on failure)
53 /// - Reconstructed from `if b { panic() }` if `--reconstruct-asserts` is set.
54 /// This statement comes with the effect that happens when the check fails
55 /// (rather than representing it as an unwinding edge).
56 Assert {
57 assert: Assert,
58 on_failure: AbortKind,
59 },
60 /// Does nothing. Useful for passes.
61 Nop,
62}
63
64#[derive(Debug, Clone, SerializeState, DeserializeState, Drive, DriveMut)]
65pub struct Statement {
66 pub span: Span,
67 pub kind: StatementKind,
68 /// Comments that precede this statement.
69 // This is filled in a late pass after all the control-flow manipulation.
70 #[drive(skip)]
71 pub comments_before: Vec<String>,
72}
73
74#[derive(
75 Debug,
76 Clone,
77 EnumIsA,
78 EnumAsGetters,
79 VariantName,
80 VariantIndexArity,
81 SerializeState,
82 DeserializeState,
83 Drive,
84 DriveMut,
85)]
86#[charon::rename("Switch")]
87pub enum SwitchTargets {
88 /// Gives the `if` block and the `else` block
89 If(BlockId, BlockId),
90 /// Gives the integer type, a map linking values to switch branches, and the
91 /// otherwise block. Note that matches over enumerations are performed by
92 /// switching over the discriminant, which is an integer.
93 SwitchInt(LiteralTy, Vec<(Literal, BlockId)>, BlockId),
94}
95
96/// A raw terminator: a terminator without meta data.
97#[derive(
98 Debug, Clone, EnumIsA, EnumAsGetters, SerializeState, DeserializeState, Drive, DriveMut,
99)]
100pub enum TerminatorKind {
101 Goto {
102 target: BlockId,
103 },
104 Switch {
105 discr: Operand,
106 targets: SwitchTargets,
107 },
108 Call {
109 call: Call,
110 target: BlockId,
111 on_unwind: BlockId,
112 },
113 /// Drop the value at the given place.
114 ///
115 /// Depending on `DropKind`, this may be a real call to `drop_in_place`, or a conditional call
116 /// that should only happen if the place has not been moved out of. See the docs of `DropKind`
117 /// for more details; to get precise drops use `--precise-drops`.
118 Drop {
119 #[drive(skip)]
120 kind: DropKind,
121 place: Place,
122 tref: TraitRef,
123 target: BlockId,
124 on_unwind: BlockId,
125 },
126 /// Assert that the given condition holds, and if not, unwind to the given block. This is used for
127 /// bounds checks, overflow checks, etc.
128 #[charon::rename("TAssert")]
129 Assert {
130 assert: Assert,
131 target: BlockId,
132 on_unwind: BlockId,
133 },
134 /// Handles panics and impossible cases.
135 Abort(AbortKind),
136 Return,
137 UnwindResume,
138}
139
140#[derive(Debug, Clone, SerializeState, DeserializeState, Drive, DriveMut)]
141pub struct Terminator {
142 pub span: Span,
143 pub kind: TerminatorKind,
144 /// Comments that precede this terminator.
145 // This is filled in a late pass after all the control-flow manipulation.
146 #[drive(skip)]
147 pub comments_before: Vec<String>,
148}
149
150#[derive(Debug, Clone, SerializeState, DeserializeState, Drive, DriveMut)]
151#[charon::rename("Block")]
152pub struct BlockData {
153 pub statements: Vec<Statement>,
154 pub terminator: Terminator,
155}