charon_lib/ast/bodies/expressions.rs
1//! Implements expressions: paths, operands, rvalues, lvalues
2use crate::ast::*;
3use derive_generic_visitor::{Drive, DriveMut, DriveTwo};
4use macros::{EnumAsGetters, EnumIsA, EnumToGetters, VariantIndexArity, VariantName};
5use serde::{Deserialize, Serialize};
6use serde_state::{DeserializeState, SerializeState};
7use std::vec::Vec;
8
9/// An expression that evaluates to a value. This is the RHS of an assignment.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[derive(EnumToGetters, EnumAsGetters, EnumIsA)]
12#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
13pub enum Rvalue {
14 /// Lifts an operand as an rvalue.
15 Use(Operand, WithRetag),
16 /// Takes a reference to the given place.
17 /// The `Operand` refers to the init value of the metadata, it is `()` if no metadata
18 #[cfg_attr(feature = "charon_on_charon", charon::rename("RvRef"))]
19 Ref {
20 place: Place,
21 #[serde_state(stateless)]
22 kind: BorrowKind,
23 ptr_metadata: Operand,
24 },
25 /// Takes a raw pointer with the given mutability to the given place. This is generated by
26 /// pointer casts like `&v as *const _` or raw borrow expressions like `&raw const v.`
27 /// Like `Ref`, the `Operand` refers to the init value of the metadata, it is `()` if no metadata.
28 RawPtr {
29 place: Place,
30 kind: RefKind,
31 ptr_metadata: Operand,
32 },
33 /// Binary operations.
34 BinaryOp(BinOp, Operand, Operand),
35 /// Unary operation (e.g. not, neg)
36 UnaryOp(UnOp, Operand),
37 /// An operation with no inputs.
38 NullaryOp(NullOp),
39 /// Discriminant read. Reads the discriminant value of an enum. The place must have the type of
40 /// an enum. The discriminant in question is the one in the `discriminant` field of the
41 /// corresponding `Variant`. This can be different than the value stored in memory (called
42 /// `tag`); that one is described by [`Discriminator`] and [`VariantLayout::tagger`].
43 Discriminant(Place),
44 /// Creates an aggregate value, like a tuple, a struct or an enum:
45 /// ```text
46 /// l = List::Cons { value:x, tail:tl };
47 /// ```
48 /// Note that in some MIR passes (like optimized MIR), aggregate values are
49 /// decomposed, like below:
50 /// ```text
51 /// (l as List::Cons).value = x;
52 /// (l as List::Cons).tail = tl;
53 /// ```
54 /// Because we may want to plug our translation mechanism at various
55 /// places, we need to take both into accounts in the translation and in
56 /// our semantics. Aggregate value initialization is easy, you might want
57 /// to have a look at expansion of `Bottom` values for explanations about the
58 /// other case.
59 ///
60 /// Remark: in case of closures, the aggregated value groups the closure id
61 /// together with its state.
62 Aggregate(AggregateKind, Vec<Operand>),
63 /// Length of a place of type `[T]` or `[T; N]`. This applies to the place itself, not to a
64 /// pointer value. This is inserted by rustc in a single case: slice patterns.
65 /// ```text
66 /// fn slice_pattern_4(x: &[()]) {
67 /// match x {
68 /// [_named] => (),
69 /// _ => (),
70 /// }
71 /// }
72 /// ```
73 Len(Place, Ty, Option<ConstantExpr>),
74 /// `Repeat(x, n)` creates an array containing `n` copies of `x`.
75 ///
76 /// We translate this to a function call for LLBC.
77 /// The last field is the proof that the repeated value is `Copy`. This proof can be absent
78 /// when the operand is a constant, which Rust permits to be repeated without `Copy`.
79 Repeat(Operand, Ty, ConstantExpr, Option<TraitRef>),
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83#[derive(EnumIsA, EnumToGetters, EnumAsGetters, VariantName)]
84#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
85#[serde_state(state_implements = DedupSerializerState)] // Avoid corecursive impls due to perfect derive
86pub enum Operand {
87 Copy(Place),
88 Move(Place),
89 /// Constant value (including constant and static variables)
90 #[cfg_attr(feature = "charon_on_charon", charon::rename("Constant"))]
91 Const(ConstantExpr),
92}
93
94/// Used for [`Rvalue::Use`] to indicate whether the operand should be retagged (this is used
95/// for Rust's aliasing model).
96#[derive(Debug, Clone, PartialEq, Eq, Hash)]
97#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
98#[cfg_attr(feature = "charon_on_charon", charon::variants_suffix("Retag"))]
99pub enum WithRetag {
100 No,
101 Yes,
102}
103
104#[derive(Debug, Copy, Clone, PartialEq, Eq)]
105#[derive(Serialize, Deserialize)]
106#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("O"))]
107pub enum OverflowMode {
108 /// If this operation overflows, it panics. Only exists in debug mode, for instance in
109 /// `a + b`, and only if `--reconstruct-fallible-operations` is passed to Charon. Otherwise the
110 /// bound check will be explicit.
111 Panic,
112 /// If this operation overflows, it is UB; for instance in `core::num::unchecked_add`. This can
113 /// exists in safe code, but will always be preceded by a bounds check.
114 UB,
115 /// If this operation overflows, it wraps around for instance in `core::num::wrapping_add`,
116 /// or `a + b` in release mode.
117 Wrap,
118}
119
120/// Binary operations.
121#[derive(Debug, Copy, Clone, PartialEq, Eq)]
122#[derive(EnumIsA, VariantName)]
123#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
124#[cfg_attr(feature = "charon_on_charon", charon::rename("Binop"))]
125#[serde_state(stateless)]
126pub enum BinOp {
127 BitXor,
128 BitAnd,
129 BitOr,
130 Eq,
131 Lt,
132 Le,
133 Ne,
134 Ge,
135 Gt,
136 Add(OverflowMode),
137 Sub(OverflowMode),
138 Mul(OverflowMode),
139 Div(OverflowMode),
140 Rem(OverflowMode),
141 /// Returns `(result, did_overflow)`, where `result` is the result of the operation with
142 /// wrapping semantics, and `did_overflow` is a boolean that indicates whether the operation
143 /// overflowed. This operation does not fail.
144 AddChecked,
145 /// Like `AddChecked`.
146 SubChecked,
147 /// Like `AddChecked`.
148 MulChecked,
149 /// Fails if the shift is bigger than the bit-size of the type.
150 Shl(OverflowMode),
151 /// Fails if the shift is bigger than the bit-size of the type.
152 Shr(OverflowMode),
153 /// `BinOp(Offset, ptr, n)` for `ptr` a pointer to type `T` offsets `ptr` by `n * size_of::<T>()`.
154 Offset,
155 /// `BinOp(Cmp, a, b)` returns `-1u8` if `a < b`, `0u8` if `a == b`, and `1u8` if `a > b`.
156 Cmp,
157}
158
159/// Unary operation
160#[derive(Debug, Clone, PartialEq, Eq)]
161#[derive(EnumIsA, VariantName)]
162#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
163#[cfg_attr(feature = "charon_on_charon", charon::rename("Unop"))]
164pub enum UnOp {
165 Not,
166 /// This can overflow, for `-i::MIN`.
167 #[serde_state(stateless)]
168 Neg(OverflowMode),
169 /// Casts are rvalues in MIR, but we treat them as unops.
170 Cast(CastKind),
171}
172
173/// For all the variants: the first type gives the source type, the second one gives
174/// the destination type.
175#[derive(Debug, Clone, PartialEq, Eq)]
176#[derive(EnumIsA, VariantName)]
177#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
178#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("Cast"))]
179pub enum CastKind {
180 /// Conversion between scalar types.
181 /// See <https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric>
182 Scalar(ScalarTy, ScalarTy),
183 /// A conversion between pointer and function pointer types.
184 RawPtr(Ty, Ty),
185 /// Converts a pointer or function pointer to an address, exposing its provenance.
186 /// See <https://doc.rust-lang.org/std/primitive.pointer.html#method.expose_provenance>.
187 PtrExposeProvenance(Ty, ScalarTy),
188 /// Converts an address to a pointer, which picks up exposed provenance.
189 /// See <https://doc.rust-lang.org/std/ptr/fn.with_exposed_provenance.html>.
190 PtrWithExposedProvenance(ScalarTy, Ty),
191 /// Cast into a function pointer. The source may be a function item or an unsafe function pointer
192 /// that is made safe.
193 FnPtr(Ty, Ty),
194 /// [Unsize coercion](https://doc.rust-lang.org/std/ops/trait.CoerceUnsized.html). This is
195 /// either `[T; N]` -> `[T]` or `T: Trait` -> `dyn Trait` coercions, behind a pointer
196 /// (reference, `Box`, or other type that implements `CoerceUnsized`).
197 ///
198 /// The special case of `&[T; N]` -> `&[T]` coercion is caught by `UnOp::ArrayToSlice`.
199 Unsize(Ty, Ty, UnsizingMetadata),
200 /// Reinterprets the bits of a value of one type as another type, i.e. exactly what
201 /// [`std::mem::transmute`] does.
202 Transmute(Ty, Ty),
203 /// Converts a receiver type with `dyn Trait<...>` to a concrete type `T`, used in vtable method shims.
204 /// Valid conversions are references, raw pointers, and (optionally) boxes:
205 /// - `&[mut] dyn Trait<...>` -> `&[mut] T`
206 /// - `*[mut] dyn Trait<...>` -> `*[mut] T`
207 /// - `Box<dyn Trait<...>>` -> `Box<T>` when no `--raw-boxes`
208 ///
209 /// For possible receivers, see: <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>.
210 /// Other receivers, e.g., `Rc` should be unpacked before the cast and re-boxed after.
211 /// FIXME(ssyram): but this is not implemented yet, namely, there may still be
212 /// something like `Rc<dyn Trait<...>> -> Rc<T>` in the types.
213 Concretize(Ty, Ty),
214}
215
216/// Nullary operation
217#[derive(Debug, Clone, PartialEq, Eq)]
218#[derive(EnumIsA, VariantName)]
219#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
220#[cfg_attr(feature = "charon_on_charon", charon::rename("Nullop"))]
221pub enum NullOp {
222 UbChecks,
223 OverflowChecks,
224 ContractChecks,
225}
226
227#[derive(Debug, Copy, Clone, PartialEq, Eq)]
228#[derive(EnumAsGetters)]
229#[derive(Serialize, Deserialize, Drive, DriveMut, DriveTwo)]
230#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("B"))]
231pub enum BorrowKind {
232 Shared,
233 Mut,
234 /// See <https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.MutBorrowKind.html#variant.TwoPhaseBorrow>
235 /// and <https://rustc-dev-guide.rust-lang.org/borrow_check/two_phase_borrows.html>
236 TwoPhaseMut,
237 /// Those are typically introduced when using guards in matches, to make sure guards don't
238 /// change the variant of an enum value while me match over it.
239 ///
240 /// See <https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.FakeBorrowKind.html#variant.Shallow>.
241 Shallow,
242 /// Data must be immutable but not aliasable. In other words you can't mutate the data but you
243 /// can mutate *through it*, e.g. if it points to a `&mut T`. This is only used in closure
244 /// captures, e.g.
245 /// ```rust,ignore
246 /// let mut z = 3;
247 /// let x: &mut isize = &mut z;
248 /// let y = || *x += 5;
249 /// ```
250 /// Here the captured variable can't be `&mut &mut x` since the `x` binding is not mutable, yet
251 /// we must be able to mutate what it points to.
252 ///
253 /// See <https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.MutBorrowKind.html#variant.ClosureCapture>.
254 UniqueImmutable,
255}
256
257impl BorrowKind {
258 pub fn is_unique(&self) -> bool {
259 matches!(
260 self,
261 BorrowKind::Mut | BorrowKind::TwoPhaseMut | BorrowKind::UniqueImmutable,
262 )
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
267#[derive(EnumIsA, VariantName)]
268#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
269#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("Meta"))]
270pub enum UnsizingMetadata {
271 /// Cast from `[T; N]` to `[T]`.
272 Length(ConstantExpr),
273 /// Cast from a sized value to a `dyn Trait` value. The `TraitRef` is the proof of the `dyn
274 /// Trait` predicate; the constant expression is a reference to the vtable `static` value.
275 VTable(TraitRef, ConstantExpr),
276 /// Cast from `dyn Trait` to `dyn OtherTrait`. The fields indicate how to retreive the vtable:
277 /// it's always either the same we already had, or the vtable for a (possibly nested) supertrait.
278 ///
279 /// Note that we cheat in one case: when upcasting to a marker trait (e.g. `dyn Trait -> dyn
280 /// Sized`), we keep the current vtable.
281 VTableUpcast(Vec<FieldId>),
282 Unknown,
283}
284
285/// An aggregated ADT.
286///
287/// Note that ADTs are desaggregated at some point in MIR. For instance, if
288/// we have in Rust:
289/// ```ignore
290/// let ls = Cons(hd, tl);
291/// ```
292///
293/// In MIR we have (yes, the discriminant update happens *at the end* for some
294/// reason):
295/// ```text
296/// (ls as Cons).0 = move hd;
297/// (ls as Cons).1 = move tl;
298/// discriminant(ls) = 0; // assuming `Cons` is the variant of index 0
299/// ```
300///
301/// Rem.: in the Aeneas semantics, both cases are handled (in case of desaggregated
302/// initialization, `ls` is initialized to `⊥`, then this `⊥` is expanded to
303/// `Cons (⊥, ⊥)` upon the first assignment, at which point we can initialize
304/// the field 0, etc.).
305#[derive(Debug, Clone, PartialEq, Eq)]
306#[derive(VariantIndexArity)]
307#[derive(SerializeState, DeserializeState, Drive, DriveMut, DriveTwo)]
308#[cfg_attr(feature = "charon_on_charon", charon::variants_prefix("Aggregated"))]
309pub enum AggregateKind {
310 /// A struct, enum or union aggregate. The `VariantId`, if present, indicates this is an enum
311 /// and the aggregate uses that variant. The `FieldId`, if present, indicates this is a union
312 /// and the aggregate writes into that field. Otherwise this is a struct.
313 Adt(TypeDeclRef, Option<VariantId>, Option<FieldId>),
314 /// We don't put this with the ADT cas because this is the only built-in type
315 /// with aggregates, and it is a primitive type. In particular, it makes
316 /// sense to treat it differently because it has a variable number of fields.
317 /// The third field is the proof that the element type is `Sized`; it is absent with
318 /// `--hide-marker-traits`.
319 Array(Ty, ConstantExpr, Option<TraitRef>),
320 /// Construct a raw pointer from a pointer value, and its metadata (can be unit, if building
321 /// a thin pointer). The type is the type of the pointee.
322 RawPtr(Ty, RefKind),
323}
324
325impl Rvalue {
326 pub fn unit_value() -> Self {
327 Rvalue::Aggregate(
328 AggregateKind::Adt(
329 TypeDeclRef {
330 id: TypeDeclId::UNIT,
331 generics: Box::new(GenericArgs::empty()),
332 builtin: Some(BuiltinAdt::Tuple),
333 },
334 None,
335 None,
336 ),
337 Vec::new(),
338 )
339 }
340}
341
342impl Operand {
343 pub fn mk_const_unit() -> Self {
344 Operand::Const(ConstantExpr::mk_unit())
345 }
346
347 pub fn ty(&self) -> &Ty {
348 match self {
349 Operand::Copy(place) | Operand::Move(place) => place.ty(),
350 Operand::Const(constant_expr) => constant_expr.ty(),
351 }
352 }
353}
354
355impl BorrowKind {
356 pub fn mutable(x: bool) -> Self {
357 if x { Self::Mut } else { Self::Shared }
358 }
359
360 pub fn is_mut(self) -> bool {
361 matches!(self, Self::Mut | Self::TwoPhaseMut | Self::UniqueImmutable)
362 }
363}
364
365impl BinOp {
366 pub fn with_overflow(&self, overflow: OverflowMode) -> Self {
367 match self {
368 BinOp::Add(_) | BinOp::AddChecked => BinOp::Add(overflow),
369 BinOp::Sub(_) | BinOp::SubChecked => BinOp::Sub(overflow),
370 BinOp::Mul(_) | BinOp::MulChecked => BinOp::Mul(overflow),
371 BinOp::Div(_) => BinOp::Div(overflow),
372 BinOp::Rem(_) => BinOp::Rem(overflow),
373 BinOp::Shl(_) => BinOp::Shl(overflow),
374 BinOp::Shr(_) => BinOp::Shr(overflow),
375 _ => {
376 panic!(
377 "Cannot set overflow mode for this binary operator: {:?}",
378 self
379 );
380 }
381 }
382 }
383}
384
385impl UnOp {
386 pub fn with_overflow(&self, overflow: OverflowMode) -> Self {
387 match self {
388 UnOp::Neg(_) => UnOp::Neg(overflow),
389 _ => {
390 panic!(
391 "Cannot set overflow mode for this unary operator: {:?}",
392 self
393 );
394 }
395 }
396 }
397}
398
399impl From<BorrowKind> for RefKind {
400 fn from(value: BorrowKind) -> Self {
401 RefKind::mutable(value.is_mut())
402 }
403}
404
405impl From<RefKind> for BorrowKind {
406 fn from(value: RefKind) -> Self {
407 match value {
408 RefKind::Shared => BorrowKind::Shared,
409 RefKind::Mut => BorrowKind::Mut,
410 }
411 }
412}