charon_lib/ast/bodies.rs
1//! The bodies of functions.
2use crate::ast::*;
3use crate::ids::IndexVec;
4use crate::llbc_ast;
5use crate::ullbc_ast;
6use crate::utils::serialize_map_to_array::SeqHashMapToArray;
7use derive_generic_visitor::{Drive, DriveMut, DriveTwo};
8use macros::EnumAsGetters;
9use macros::{EnumIsA, EnumToGetters};
10use serde_state::DeserializeState;
11use serde_state::SerializeState;
12
13pub mod expressions;
14pub mod places;
15pub mod structured;
16pub mod unstructured;
17pub mod values;
18
19pub use expressions::*;
20pub use places::*;
21pub use values::*;
22
23/// The body of a function.
24#[derive(
25 Debug,
26 PartialEq,
27 Eq,
28 Clone,
29 SerializeState,
30 DeserializeState,
31 Drive,
32 DriveMut,
33 DriveTwo,
34 EnumIsA,
35 EnumAsGetters,
36 EnumToGetters,
37)]
38#[serde_state(state_implements = HashConsSerializerState)]
39#[cfg_attr(feature = "charon_on_charon", charon::variants_suffix("Body"))]
40pub enum Body {
41 /// Body represented as a CFG. This is what ullbc is made of, and what we get after translating MIR.
42 Unstructured(ullbc_ast::ExprBody),
43 /// Body represented with structured control flow. This is what llbc is made of. We restructure
44 /// the control flow in the `ullbc_to_llbc` pass.
45 Structured(llbc_ast::ExprBody),
46 /// A façade body that dispatches to one of several per-target function bodies. Created during
47 /// multi-target merging for functions with the same signature but different bodies across
48 /// targets.
49 TargetDispatch(
50 #[serde(with = "SeqHashMapToArray::<TargetTriple, FunDeclRef>")]
51 SeqHashMap<TargetTriple, FunDeclRef>,
52 ),
53 /// Function declared in an `extern { ... }` block. The string is the foreign symbol name.
54 Extern(#[drive(skip)] String),
55 /// Rust intrinsic function.
56 Intrinsic {
57 /// The intrinsic name.
58 #[drive(skip)]
59 name: String,
60 /// The argument names, None if not available.
61 #[drive(skip)]
62 arg_names: Vec<Option<String>>,
63 },
64 /// A body that the user chose not to translate, based on opacity settings like
65 /// `--include`/`--opaque`.
66 Opaque,
67 /// A body that was not available. Typically that's function bodies for non-generic and
68 /// non-inlineable std functions, as these are not present in the compiled standard library
69 /// `.rmeta` file shipped with a rust toolchain.
70 Missing,
71 /// We encountered an error while translating this body.
72 #[drive(skip)]
73 #[serde_state(stateless)]
74 Error(Error),
75}
76
77generate_index_type!(LocalId, "");
78
79/// The local variables of a body.
80#[derive(
81 Debug,
82 PartialEq,
83 Eq,
84 Default,
85 Clone,
86 SerializeState,
87 DeserializeState,
88 Drive,
89 DriveMut,
90 DriveTwo,
91)]
92pub struct Locals {
93 /// The number of local variables used for the input arguments.
94 #[drive(skip)]
95 pub arg_count: usize,
96 /// The local variables.
97 /// We always have, in the following order:
98 /// - the local used for the return value (index 0)
99 /// - the `arg_count` input arguments
100 /// - the remaining locals, used for the intermediate computations
101 pub locals: IndexVec<LocalId, Local>,
102}
103
104/// A variable
105#[derive(
106 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
107)]
108pub struct Local {
109 /// Unique index identifying the variable
110 pub index: LocalId,
111 /// Variable name - may be `None` if the variable was introduced by Rust
112 /// through desugaring.
113 #[drive(skip)]
114 pub name: Option<String>,
115 /// Span of the variable declaration.
116 pub span: Span,
117 /// The variable type
118 #[cfg_attr(feature = "charon_on_charon", charon::rename("local_ty"))]
119 pub ty: Ty,
120}
121
122/// An expression body.
123/// TODO: arg_count should be stored in GFunDecl below. But then,
124/// the print is obfuscated and Aeneas may need some refactoring.
125#[derive(
126 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
127)]
128#[cfg_attr(feature = "charon_on_charon", charon::rename("GexprBody"))]
129pub struct GExprBody<T> {
130 pub span: Span,
131 /// The number of regions existentially bound in this body. We introduce fresh such regions
132 /// during translation instead of the erased regions that rustc gives us.
133 #[drive(skip)]
134 pub bound_body_regions: usize,
135 /// The local variables.
136 pub locals: Locals,
137 /// The statements and blocks that compose this body.
138 pub body: T,
139 /// For each line inside the body, we record any whole-line `//` comments found before it. They
140 /// are added to statements in the late `recover_body_comments` pass.
141 #[cfg_attr(feature = "charon_on_charon", charon::opaque)]
142 #[drive(skip)]
143 pub comments: Vec<(usize, Vec<String>)>,
144}
145
146/// A function operand is used in function calls.
147/// It either designates a top-level function, or a place in case
148/// we are using function pointers stored in local variables.
149#[derive(
150 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
151)]
152#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("FnOp"))]
153pub enum FnOperand {
154 /// Regular case: call to a top-level function, trait method, etc.
155 Regular(FnPtr),
156 /// Use of a function pointer.
157 Dynamic(Operand),
158}
159
160#[derive(
161 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
162)]
163pub struct Call {
164 pub func: FnOperand,
165 pub args: Vec<Operand>,
166 pub dest: Place,
167}
168
169/// Statements that only affect borrow-checking. They are no-ops at runtime.
170#[derive(
171 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
172)]
173pub enum BorrowckStatement {
174 /// Acts like a read of the place.
175 FakeRead(Place),
176 /// Relate the type of a place to the provided type. For example, `let x: Self = value`
177 /// produces `SetType` for `x` and `Self`.
178 SetType {
179 place: Place,
180 ty: Ty,
181 #[drive(skip)]
182 #[serde_state(stateless)]
183 variance: Variance,
184 },
185 /// Require a type to outlive a region. For example, the `'a` bound in
186 /// `let x: impl Copy + 'a = value` produces `SetOutlives(typeof(x), 'a)`.
187 SetOutlives(Ty, Region),
188 /// Require a trait predicate to hold. For example, the `Copy` bound in
189 /// `let x: impl Copy = value` produces `PredicateHolds(typeof(x): Copy)`.
190 PredicateHolds(TraitRef),
191}
192
193/// (U)LLBC is a language with side-effects: a statement may abort in a way that isn't tracked by
194/// control-flow. The three kinds of abort are:
195/// - Panic
196/// - Undefined behavior (caused by an "assume")
197/// - Unwind termination
198#[derive(
199 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
200)]
201pub enum AbortKind {
202 /// A built-in panicking function, or a panic due to a failed built-in check (e.g. for out-of-bounds accesses).
203 Panic(Option<Name>),
204 /// Undefined behavior in the rust abstract machine.
205 UndefinedBehavior,
206 /// Unwind had to stop for ABI reasons or because cleanup code panicked again.
207 UnwindTerminate,
208}
209
210/// A `Drop` statement/terminator can mean two things, depending on what MIR phase we retrieved
211/// from rustc: it could be a real drop, or it could be a "conditional drop", which is where drop
212/// may happen depending on whether the borrow-checker determines a drop is needed.
213#[derive(
214 Debug, PartialEq, Eq, Clone, Copy, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
215)]
216pub enum DropKind {
217 /// A real drop. This calls `<T as Destruct>::drop_glue(&mut place)` and marks the
218 /// place as moved-out-of. Use `--desugar-drops` to transform all such drops to an actual
219 /// function call.
220 ///
221 /// The `drop_glue` method is added by Charon to the `Destruct` trait to make it possible
222 /// to track drop code in polymorphic code. It contains the same code as the
223 /// `core::ptr::drop_glue<T>` builtin would.
224 ///
225 /// Drop are precise in MIR `elaborated` and `optimized`.
226 Precise,
227 /// A conditional drop, which may or may not end up running drop code depending on the code
228 /// path that led to it. A conditional drop may also become a partial drop (dropping only the
229 /// subplaces that haven't been moved out of), may be conditional on the code path that led to
230 /// it, or become an async drop. The exact semantics are left intentionally unspecified by
231 /// rustc developers. To elaborate such drops into precise drops, pass `--precise-drops` to
232 /// Charon.
233 ///
234 /// A conditional drop may also be passed an unaligned place when dropping fields of packed
235 /// structs. Such a thing is UB for a precise drop.
236 ///
237 /// Drop are conditional in MIR `built` and `promoted`.
238 Conditional,
239}
240
241/// Check the value of an operand and abort if the value is not expected. This is introduced to
242/// avoid a lot of small branches.
243///
244/// We translate MIR asserts (introduced for out-of-bounds accesses or divisions by zero for
245/// instance) to this. We then eliminate them in [crate::transform::resugar::reconstruct_fallible_operations],
246/// because they're implicit in the semantics of our array accesses etc. Finally we introduce new asserts in
247/// [crate::transform::resugar::reconstruct_asserts].
248#[derive(
249 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
250)]
251#[cfg_attr(feature = "charon_on_charon", charon::rename("Assertion"))]
252pub struct Assert {
253 pub cond: Operand,
254 /// The value that the operand should evaluate to for the assert to succeed.
255 #[drive(skip)]
256 pub expected: bool,
257 /// The kind of check performed by this assert. This is only used for error reporting, as the check
258 /// is actually performed by the instructions preceding the assert.
259 pub check_kind: Option<BuiltinAssertKind>,
260}
261
262/// The kind of a built-in assertion, which may panic and unwind. These are removed
263/// by `reconstruct_fallible_operations` because they're implicit in the semantics of (U)LLBC.
264/// This kind should only be used for error-reporting purposes, as the check itself
265/// is performed in the instructions preceding the assert.
266#[derive(
267 Debug, PartialEq, Eq, Clone, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
268)]
269pub enum BuiltinAssertKind {
270 BoundsCheck { len: Operand, index: Operand },
271 Overflow(BinOp, Operand, Operand),
272 OverflowNeg(Operand),
273 DivisionByZero(Operand),
274 RemainderByZero(Operand),
275 MisalignedPointerDereference { required: Operand, found: Operand },
276 NullPointerDereference,
277 NullReferenceCreated,
278 InvalidEnumConstruction(Operand),
279 ResumedAfterReturn,
280 ResumedAfterPanic,
281 ResumedAfterDrop,
282}
283
284impl Body {
285 /// Whether there is an actual body with statements etc, as opposed to the body being missing
286 /// for some reason.
287 pub fn has_contents(&self) -> bool {
288 match self {
289 Body::Unstructured(..) | Body::Structured(..) => true,
290 Body::Extern(..)
291 | Body::Intrinsic { .. }
292 | Body::Opaque
293 | Body::Missing
294 | Body::Error(..)
295 | Body::TargetDispatch(..) => false,
296 }
297 }
298
299 pub fn locals(&self) -> &Locals {
300 match self {
301 Body::Structured(body) => &body.locals,
302 Body::Unstructured(body) => &body.locals,
303 _ => panic!("called `locals` on a missing body"),
304 }
305 }
306}
307
308impl Locals {
309 pub fn new(arg_count: usize) -> Self {
310 Self {
311 arg_count,
312 locals: Default::default(),
313 }
314 }
315
316 /// Creates a new variable and returns a place pointing to it.
317 /// Warning: don't forget to `StorageLive` it before using it.
318 pub fn new_var(&mut self, name: Option<String>, ty: Ty) -> Place {
319 let local_id = self.locals.push_with(|index| Local {
320 index,
321 name,
322 span: Span::dummy(),
323 ty: ty.clone(),
324 });
325 Place::new(local_id, ty)
326 }
327
328 /// Gets a place pointing to the corresponding variable.
329 pub fn place_for_var(&self, local_id: LocalId) -> Place {
330 let ty = self.locals[local_id].ty.clone();
331 Place::new(local_id, ty)
332 }
333
334 /// Returns whether this local is the special return local or one of the input argument locals.
335 pub fn is_return_or_arg(&self, lid: LocalId) -> bool {
336 lid.index() <= self.arg_count
337 }
338
339 /// The place where we write the return value.
340 pub fn return_place(&self) -> Place {
341 self.place_for_var(LocalId::new(0))
342 }
343
344 /// Locals that aren't arguments or return values.
345 pub fn non_argument_locals(&self) -> impl Iterator<Item = (LocalId, &Local)> {
346 self.locals.iter_enumerated().skip(1 + self.arg_count)
347 }
348}
349
350impl std::ops::Index<LocalId> for Locals {
351 type Output = Local;
352 fn index(&self, local_id: LocalId) -> &Self::Output {
353 &self.locals[local_id]
354 }
355}
356impl std::ops::IndexMut<LocalId> for Locals {
357 fn index_mut(&mut self, local_id: LocalId) -> &mut Self::Output {
358 &mut self.locals[local_id]
359 }
360}