Skip to main content

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 asm;
14pub mod expressions;
15pub mod places;
16pub mod safety;
17pub mod structured;
18pub mod unstructured;
19pub mod values;
20
21pub use asm::*;
22pub use expressions::*;
23pub use places::*;
24pub use safety::*;
25pub use values::*;
26
27/// The body of a function.
28///
29/// A normal function has a body that's either structured or unstructured. These are two equivalent
30/// representations of the same function body, which only differ in how control-flow is
31/// represented. By default bodies are structured; passing `--ullbc` to Charon makes bodies
32/// unstructured.
33///
34/// Besides these, some functions have virtual bodies or no body, see the doc for each variant.
35#[derive(Debug, Clone)]
36#[derive(EnumIsA, EnumAsGetters, EnumToGetters)]
37#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
38#[serde_state(state_implements = DedupSerializerState)]
39#[cfg_attr(feature = "charon_on_charon", charon::variants_suffix("Body"))]
40pub enum Body {
41    /// Body represented as a control-flow graph (CFG), i.e. with numbered blocks and jumps/GOTOs
42    /// between them. This is available when passing `--ullbc` to Charon.
43    ///
44    /// This is also the same structure as rustc's MIR, and is in fact a direct translation of it.
45    Unstructured(ullbc_ast::ExprBody),
46    /// Body represented with structured control flow, i.e. with nested `if`/`match`/`loop` blocks.
47    /// This is available when `--ullbc` is not passed to Charon.
48    ///
49    /// This structure is recovered from the unstructured body in the `ullbc_to_llbc` pass.
50    Structured(llbc_ast::ExprBody),
51    /// A façade body that dispatches to one of several per-target function bodies. This is created
52    /// during multi-target merging (using `--target`) to represent functions that have a different
53    /// implementation across targets.
54    TargetDispatch(
55        #[serde(with = "SeqHashMapToArray::<TargetTriple, FunDeclRef>")]
56        SeqHashMap<TargetTriple, FunDeclRef>,
57    ),
58    /// Function declared in an `extern { ... }` block. The string is the foreign symbol name.
59    Extern(String),
60    /// Rust intrinsic function. This has no body and describes a "built-in" operation that must be
61    /// handled by the codegen backend.
62    Intrinsic {
63        /// The intrinsic name.
64        name: String,
65        /// The argument names, None if not available.
66        arg_names: Vec<Option<String>>,
67    },
68    /// A body that the user chose not to translate, based on opacity settings like
69    /// `--include`/`--opaque`.
70    Opaque,
71    /// A body that was not available.
72    ///
73    /// These can occur when using a sysroot that doesn't have MIR for all the standard library
74    /// functions. This is the case of the sysroot that ships with every Rust toolchain, but Charon
75    /// uses a custom-built sysroot that does. So this shouldn't happen unless you're passing
76    /// `--sysroot` to charon.
77    Missing,
78    /// We encountered an error while translating this body.
79    #[serde_state(stateless)]
80    Error(Error),
81}
82
83generate_index_type!(LocalId, "");
84
85/// The local variables of a body.
86#[derive(Debug, Default, Clone)]
87#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
88pub struct Locals {
89    /// The number of local variables used for the input arguments.
90    pub arg_count: usize,
91    /// The local variables.
92    /// We always have, in the following order:
93    /// - the local used for the return value (index 0)
94    /// - the `arg_count` input arguments
95    /// - the remaining locals, used for the intermediate computations
96    pub locals: IndexVec<LocalId, Local>,
97}
98
99/// A variable
100#[derive(Debug, Clone)]
101#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
102pub struct Local {
103    /// Unique index identifying the variable
104    pub index: LocalId,
105    /// Variable name - may be `None` if the variable was introduced by Rust
106    /// through desugaring.
107    pub name: Option<String>,
108    /// Span of the variable declaration.
109    pub span: Span,
110    /// The variable type
111    #[cfg_attr(feature = "charon_on_charon", charon::rename("local_ty"))]
112    pub ty: Ty,
113    /// If this local is a drop flag, this is the place whose initialization state it tracks.
114    pub drop_flag_for: Option<Place>,
115}
116
117/// An expression body.
118/// TODO: arg_count should be stored in GFunDecl below. But then,
119///       the print is obfuscated and Aeneas may need some refactoring.
120#[derive(Debug, Clone)]
121#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
122#[cfg_attr(feature = "charon_on_charon", charon::rename("GexprBody"))]
123pub struct GExprBody<T> {
124    pub span: Span,
125    /// The number of regions existentially bound in this body. We introduce fresh such regions
126    /// during translation instead of the erased regions that rustc gives us.
127    pub bound_body_regions: usize,
128    /// The local variables.
129    pub locals: Locals,
130    /// The statements and blocks that compose this body.
131    pub body: T,
132    /// For each line inside the body, we record any whole-line `//` comments found before it. They
133    /// are added to statements in the late `recover_body_comments` pass.
134    #[cfg_attr(feature = "charon_on_charon", charon::opaque)]
135    pub comments: Vec<(u32, Vec<String>)>,
136}
137
138generate_index_type!(BranchId, "Branch");
139
140/// The value inspected by a switch. Must be of integer, bool or char type.
141#[derive(Debug, Clone, PartialEq, Eq)]
142#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
143#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("Switch"))]
144pub enum SwitchScrutinee {
145    /// Inspect the value produced by an operand.
146    Value(Operand),
147    /// Inspect the discriminant of an enum place.
148    Discriminant(Place),
149}
150
151/// A branching operation.
152#[derive(Debug, Clone, PartialEq, Eq)]
153#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
154pub struct SwitchData {
155    /// The value to branch over.
156    pub scrutinee: SwitchScrutinee,
157    /// Which branch to take for each value of the scrutinee. Several values may point to the same
158    /// branch, and not all values may be accounted for.
159    ///
160    /// If the scrutinee is an operand, the constant expressions are literal values. If the
161    /// scrutinee is a discriminant read, the expressions are of the form
162    /// `ConstantExprKind::Discriminant`.
163    pub branches: Vec<(ConstantExpr, BranchId)>,
164    /// Branch to use if the scrutinee didn't match any of the values above. `None` if the set of
165    /// branch values is known to be exhaustive.
166    pub fallback: Option<BranchId>,
167}
168
169/// A function operand is used in function calls.
170/// It either designates a top-level function, or a place in case
171/// we are using function pointers stored in local variables.
172#[derive(Debug, Clone, PartialEq, Eq)]
173#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
174#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("FnOp"))]
175pub enum FnOperand {
176    /// Regular case: call to a top-level function, trait method, etc.
177    Regular(FnPtr),
178    /// Use of a function pointer.
179    Dynamic(Operand),
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
183#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
184pub struct Call {
185    pub func: FnOperand,
186    pub args: Vec<Operand>,
187    pub dest: Place,
188    pub safety: CallSafety,
189}
190
191/// Whether we statically know something about the safety of this call, that takes
192/// precedence over the safety of the callee's signature.
193#[derive(Debug, Copy, Clone, PartialEq, Eq)]
194#[derive(EnumIsA)]
195#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
196pub enum CallSafety {
197    /// As safe as the callee's signature.
198    Inherit,
199    /// Safe despite an unsafe signature: desugared drops, and calls to safe `#[target_feature]` functions
200    /// from contexts that enable the required features.
201    Safe,
202    /// Unsafe despite a safe signature: calls to `#[target_feature]` functions from contexts
203    /// that don't enable the required features, and explicit calls to `Drop` methods.
204    Unsafe,
205}
206
207/// Statements that only affect borrow-checking. They are no-ops at runtime.
208#[derive(Debug, Clone, PartialEq, Eq)]
209#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
210pub enum BorrowckStatement {
211    /// Acts like a read of the place.
212    FakeRead(Place),
213    /// Relate the type of a place to the provided type. For example, `let x: Self = value`
214    /// produces `SetType` for `x` and `Self`.
215    SetType {
216        place: Place,
217        ty: Ty,
218        #[serde_state(stateless)]
219        variance: Variance,
220    },
221    /// Require a type to outlive a region. For example, the `'a` bound in
222    /// `let x: impl Copy + 'a = value` produces `SetOutlives(typeof(x), 'a)`.
223    SetOutlives(Ty, Region),
224    /// Require a trait predicate to hold. For example, the `Copy` bound in
225    /// `let x: impl Copy = value` produces `PredicateHolds(typeof(x): Copy)`.
226    PredicateHolds(TraitRef),
227}
228
229/// (U)LLBC is a language with side-effects: a statement may abort in a way that isn't tracked by
230/// control-flow. The three kinds of abort are:
231/// - Panic
232/// - Undefined behavior (caused by an "assume")
233/// - Unwind termination
234#[derive(Debug, Clone, PartialEq, Eq)]
235#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
236#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("Abort"))]
237pub enum AbortKind {
238    /// A built-in panicking function, or a panic due to a failed built-in check (e.g. for out-of-bounds accesses).
239    Panic(Option<Name>),
240    /// Undefined behavior in the rust abstract machine.
241    UndefinedBehavior,
242    /// Unwind had to stop for ABI reasons or because cleanup code panicked again.
243    UnwindTerminate,
244}
245
246/// A `Drop` statement/terminator can mean two things, depending on what MIR phase we retrieved
247/// from rustc: it could be a real drop, or it could be a "conditional drop", which is where drop
248/// may happen depending on whether the borrow-checker determines a drop is needed.
249#[derive(Debug, Copy, Clone, PartialEq, Eq)]
250#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
251pub enum DropKind {
252    /// A real drop. This calls `<T as Destruct>::drop_glue(&mut place)` and marks the
253    /// place as moved-out-of. Use `--desugar-drops` to transform all such drops to an actual
254    /// function call.
255    ///
256    /// The `drop_glue` method is added by Charon to the `Destruct` trait to make it possible
257    /// to track drop code in polymorphic code. It contains the same code as the
258    /// `core::ptr::drop_glue<T>` builtin would.
259    ///
260    /// Drop are precise in MIR `elaborated` and `optimized`.
261    Precise,
262    /// A conditional drop, which may or may not end up running drop code depending on the code
263    /// path that led to it. A conditional drop may also become a partial drop (dropping only the
264    /// subplaces that haven't been moved out of), may be conditional on the code path that led to
265    /// it, or become an async drop. The exact semantics are left intentionally unspecified by
266    /// rustc developers. To elaborate such drops into precise drops, pass `--precise-drops` to
267    /// Charon.
268    ///
269    /// A conditional drop may also be passed an unaligned place when dropping fields of packed
270    /// structs. Such a thing is UB for a precise drop.
271    ///
272    /// Drop are conditional in MIR `built` and `promoted`.
273    Conditional,
274}
275
276/// Check the value of an operand and abort if the value is not expected. This is introduced to
277/// avoid a lot of small branches.
278///
279/// We translate MIR asserts (introduced for out-of-bounds accesses or divisions by zero for
280/// instance) to this. We then eliminate them in [crate::transform::resugar::reconstruct_fallible_operations],
281/// because they're implicit in the semantics of our array accesses etc. Finally we introduce new asserts in
282/// [crate::transform::resugar::reconstruct_asserts].
283#[derive(Debug, Clone, PartialEq, Eq)]
284#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
285#[cfg_attr(feature = "charon_on_charon", charon::rename("Assertion"))]
286pub struct Assert {
287    pub cond: Operand,
288    /// The value that the operand should evaluate to for the assert to succeed.
289    pub expected: bool,
290    /// The kind of check performed by this assert. This is only used for error reporting, as the check
291    /// is actually performed by the instructions preceding the assert.
292    pub check_kind: Option<BuiltinAssertKind>,
293}
294
295/// The kind of a built-in assertion, which may panic and unwind. These are removed
296/// by `reconstruct_fallible_operations` because they're implicit in the semantics of (U)LLBC.
297/// This kind should only be used for error-reporting purposes, as the check itself
298/// is performed in the instructions preceding the assert.
299#[derive(Debug, Clone, PartialEq, Eq)]
300#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
301pub enum BuiltinAssertKind {
302    BoundsCheck { len: Operand, index: Operand },
303    Overflow(BinOp, Operand, Operand),
304    OverflowNeg(Operand),
305    DivisionByZero(Operand),
306    RemainderByZero(Operand),
307    MisalignedPointerDereference { required: Operand, found: Operand },
308    NullPointerDereference,
309    NullReferenceCreated,
310    InvalidEnumConstruction(Operand),
311    ResumedAfterReturn,
312    ResumedAfterPanic,
313    ResumedAfterDrop,
314}
315
316impl Body {
317    /// Whether there is an actual body with statements etc, as opposed to the body being missing
318    /// for some reason.
319    pub fn has_contents(&self) -> bool {
320        match self {
321            Body::Unstructured(..) | Body::Structured(..) => true,
322            Body::Extern(..)
323            | Body::Intrinsic { .. }
324            | Body::Opaque
325            | Body::Missing
326            | Body::Error(..)
327            | Body::TargetDispatch(..) => false,
328        }
329    }
330
331    pub fn locals(&self) -> &Locals {
332        match self {
333            Body::Structured(body) => &body.locals,
334            Body::Unstructured(body) => &body.locals,
335            _ => panic!("called `locals` on a missing body"),
336        }
337    }
338}
339
340impl Locals {
341    pub fn new(arg_count: usize) -> Self {
342        Self {
343            arg_count,
344            locals: Default::default(),
345        }
346    }
347
348    /// Creates a new variable and returns a place pointing to it.
349    /// Warning: don't forget to `StorageLive` it before using it.
350    pub fn new_var(&mut self, name: Option<String>, ty: Ty) -> Place {
351        let local_id = self.locals.push_with(|index| Local {
352            index,
353            name,
354            span: Span::dummy(),
355            ty: ty.clone(),
356            drop_flag_for: None,
357        });
358        Place::new(local_id, ty)
359    }
360
361    /// Gets a place pointing to the corresponding variable.
362    pub fn place_for_var(&self, local_id: LocalId) -> Place {
363        let ty = self.locals[local_id].ty.clone();
364        Place::new(local_id, ty)
365    }
366
367    /// All the locals, including the return local and arguments.
368    pub fn iter(&self) -> impl Iterator<Item = &Local> {
369        self.locals.iter()
370    }
371
372    /// The input argument locals.
373    pub fn arguments(&self) -> impl Iterator<Item = &Local> {
374        self.locals.iter().skip(1).take(self.arg_count)
375    }
376
377    /// The local used for the return value.
378    pub fn return_local(&self) -> &Local {
379        &self.locals[LocalId::ZERO]
380    }
381
382    /// Returns whether this local is the special return local or one of the input argument locals.
383    pub fn is_return_or_arg(&self, lid: LocalId) -> bool {
384        lid.index() <= self.arg_count
385    }
386
387    /// The place where we write the return value.
388    pub fn return_place(&self) -> Place {
389        self.place_for_var(LocalId::ZERO)
390    }
391
392    /// Locals that aren't arguments or return values.
393    pub fn non_argument_locals(&self) -> impl Iterator<Item = (LocalId, &Local)> {
394        self.locals.iter_enumerated().skip(1 + self.arg_count)
395    }
396}
397
398impl SwitchData {
399    /// If this is a switch over a boolean, return its `then` and `else` branches.
400    pub fn as_if(&self) -> Option<(BranchId, BranchId)> {
401        let SwitchScrutinee::Value(scrutinee) = &self.scrutinee else {
402            return None;
403        };
404        if !matches!(scrutinee.ty().kind(), TyKind::Scalar(ScalarTy::Bool)) {
405            return None;
406        }
407
408        let branch_for = |value| {
409            self.branches
410                .iter()
411                .find_map(|(case, branch_id)| match case.kind() {
412                    ConstantExprKind::Bool(case_value) if *case_value == value => Some(*branch_id),
413                    _ => None,
414                })
415                .or(self.fallback)
416        };
417        Some((branch_for(true)?, branch_for(false)?))
418    }
419
420    /// Group the explicit switch values by the branch they select.
421    pub fn group_by_branch(&self) -> IndexVec<BranchId, Vec<ConstantExpr>> {
422        let mut grouped = IndexVec::new();
423        if let Some(branch_id) = self.fallback {
424            grouped.get_or_extend_and_insert(branch_id, Vec::new);
425        }
426        for (value, branch_id) in &self.branches {
427            grouped
428                .get_or_extend_and_insert(*branch_id, Vec::new)
429                .push(value.clone());
430        }
431        grouped
432    }
433}
434
435impl std::ops::Index<LocalId> for Locals {
436    type Output = Local;
437    fn index(&self, local_id: LocalId) -> &Self::Output {
438        &self.locals[local_id]
439    }
440}
441impl std::ops::IndexMut<LocalId> for Locals {
442    fn index_mut(&mut self, local_id: LocalId) -> &mut Self::Output {
443        &mut self.locals[local_id]
444    }
445}