charon_lib/ast/items/layout.rs
1//! The layout of types.
2use crate::ast::*;
3use crate::ids::IndexVec;
4use crate::utils::serialize_map_to_array::SeqHashMapToArray;
5use derive_generic_visitor::*;
6use serde::{Deserialize, Serialize};
7use serde_state::{DeserializeState, SerializeState};
8
9pub type ByteCount = u64;
10
11/// Type layout information.
12///
13/// Does not include information about niches.
14/// If the type does not have a fully known layout (e.g. it is ?Sized)
15/// some of the layout parts are not available.
16#[derive(
17 Debug, Clone, PartialEq, Eq, SerializeState, DeserializeState, Drive, DriveMut, DriveTwo,
18)]
19pub struct Layout {
20 /// The size of the type in bytes.
21 #[drive(skip)]
22 pub size: Option<ByteCount>,
23 /// The alignment, in bytes.
24 #[drive(skip)]
25 pub align: Option<ByteCount>,
26 /// Decision tree that determines the active variant by reading memory. Only `Some` for enums.
27 #[drive(skip)]
28 #[serde_state(stateless)]
29 pub discriminator: Option<Discriminator>,
30 /// Whether the type is uninhabited, i.e. has any valid value at all.
31 /// Note that uninhabited types can have arbitrary layouts: `(u32, !)` has space for the `u32`
32 /// and `enum E2 { A, B(!), C(i32, !) }` may have space for a discriminant.
33 #[drive(skip)]
34 pub uninhabited: bool,
35 /// Map from `VariantId` to the corresponding field layouts. Some variants don't have a
36 /// meaningful layout due to being uninhabited (though an uninhabited variant may have a
37 /// layout). Structs and unions are modeled as having exactly one variant.
38 #[serde_state(stateless)]
39 pub variant_layouts: IndexVec<VariantId, Option<VariantLayout>>,
40 /// The representation options of this type declaration as annotated by the user.
41 #[drive(skip)]
42 #[serde_state(stateless)]
43 pub repr: ReprOptions,
44}
45
46/// Simplified layout of a single variant.
47///
48/// Maps fields to their offset within the layout.
49#[derive(
50 Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Drive, DriveMut, DriveTwo,
51)]
52pub struct VariantLayout {
53 /// The offset of each field.
54 #[drive(skip)]
55 pub field_offsets: IndexVec<FieldId, ByteCount>,
56 /// Whether the variant is uninhabited, i.e. has any valid possible value.
57 /// Note that uninhabited types can have arbitrary layouts.
58 #[drive(skip)]
59 pub uninhabited: bool,
60 /// How to write the tag when constructing this variant. Each entry means: write `value` at
61 /// byte `offset`. Mirrors MiniRust's `Variant::tagger`.
62 #[drive(skip)]
63 pub tagger: Vec<(ByteCount, ScalarValue)>,
64}
65
66/// Decision tree used to determine the active variant by reading memory. Mirrors MiniRust's
67/// `Discriminator`.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub enum Discriminator {
70 /// The variant is known.
71 Known(VariantId),
72 /// No valid variant (e.g., invalid tag value).
73 Invalid,
74 /// Branch on an integer value read from memory at `offset`.
75 Branch {
76 /// Byte offset to read from.
77 offset: ByteCount,
78 /// Integer type to read.
79 int_ty: IntegerTy,
80 /// If the integer is in one of these ranges, continue with the given `Discriminator`. The
81 /// ranges are sorted.
82 children: Vec<(std::ops::RangeInclusive<ScalarValue>, Discriminator)>,
83 /// Fallback if no range in `children` matches.
84 fallback: Box<Discriminator>,
85 },
86}
87
88/// The representation options as annotated by the user.
89///
90/// NOTE: This does not include less common/unstable representations such as `#[repr(simd)]`
91/// or the compiler internal `#[repr(linear)]`. Similarly, enum discriminant representations
92/// are encoded in [`Variant::discriminant`] and [`Discriminator`] instead.
93/// This only stores whether the discriminant type was derived from an explicit annotation.
94#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
95pub struct ReprOptions {
96 pub repr_algo: ReprAlgorithm,
97 pub align_modif: Option<AlignmentModifier>,
98 pub transparent: bool,
99 pub explicit_discr_type: bool,
100}
101
102/// Describes which layout algorithm is used for representing the corresponding type.
103/// Depends on the `#[repr(...)]` used.
104#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
105pub enum ReprAlgorithm {
106 /// The default layout algorithm. Used without an explicit `Ĺ—epr` or for `repr(Rust)`.
107 #[default]
108 Rust,
109 /// The C layout algorithm as enforced by `repr(C)`.
110 C,
111}
112
113/// Describes modifiers to the alignment and packing of the corresponding type.
114/// Represents `repr(align(n))` and `repr(packed(n))`.
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub enum AlignmentModifier {
117 Align(ByteCount),
118 Pack(ByteCount),
119}
120
121#[derive(Clone, Drive, DriveMut, DriveTwo, SerializeState, DeserializeState)]
122#[serde_state(stateless)]
123pub struct TargetInfo {
124 /// The pointer size of the target in bytes.
125 pub target_pointer_size: ByteCount,
126 /// Whether the target platform uses little endian byte order.
127 pub is_little_endian: bool,
128 /// The minimum size of a [`repr(C)`] enum.
129 pub c_enum_min_size: ByteCount,
130 /// Alignments for primitive types.
131 #[serde(with = "SeqHashMapToArray::<LiteralTy, ByteCount>")]
132 pub primitive_alignments: SeqHashMap<LiteralTy, ByteCount>,
133}
134
135impl Layout {
136 pub fn is_variant_uninhabited(&self, variant_id: VariantId) -> bool {
137 self.variant_layouts[variant_id]
138 .as_ref()
139 .is_none_or(|v| v.uninhabited)
140 }
141
142 pub fn is_c_repr(&self) -> bool {
143 self.repr.repr_algo == ReprAlgorithm::C
144 }
145}
146
147#[derive(Debug, PartialEq, Eq)]
148pub enum DiscriminantReadError {
149 /// We read an uninitialized byte.
150 UninitByte,
151 /// We reached an invalid discriminant state.
152 InvalidDiscriminant,
153}
154
155impl Discriminator {
156 /// Make a trivial discriminator that always returns the given variant id.
157 pub fn trivial(variant_id: VariantId) -> Self {
158 Self::Known(variant_id)
159 }
160
161 /// Read a discriminant from memory. The `read` function simulates reading an integer of the
162 /// given type at the given byte offset from memory and can return `UninitByte` if the byte
163 /// could not be read.
164 pub fn read_discriminant(
165 &self,
166 read: impl Fn(ByteCount, IntegerTy) -> Result<ScalarValue, DiscriminantReadError> + Copy,
167 ) -> Result<VariantId, DiscriminantReadError> {
168 match self {
169 Discriminator::Known(id) => Ok(*id),
170 Discriminator::Invalid => Err(DiscriminantReadError::InvalidDiscriminant),
171 Discriminator::Branch {
172 offset,
173 int_ty,
174 fallback,
175 children,
176 } => {
177 let val = read(*offset, *int_ty)?;
178 for (range, child) in children {
179 if range.contains(&val) {
180 return child.read_discriminant(read);
181 }
182 }
183 fallback.read_discriminant(read)
184 }
185 }
186 }
187}
188
189impl ReprOptions {
190 /// Whether this representation options guarantee a fixed
191 /// field ordering for the type.
192 ///
193 /// Since we don't support `repr(simd)` or `repr(linear)` yet, this is
194 /// the case if it's either `repr(C)` or an explicit discriminant type for
195 /// an enum with fields (if it doesn't have fields, this obviously doesn't matter anyway).
196 ///
197 /// Cf. <https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.c.struct>
198 /// and <https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.primitive.adt>.
199 pub fn guarantees_fixed_field_order(&self) -> bool {
200 self.repr_algo == ReprAlgorithm::C || self.explicit_discr_type
201 }
202}
203
204impl IntTy {
205 /// Important: this returns the target byte count for the types.
206 /// Must not be used for host types from rustc.
207 pub fn target_size(&self, ptr_size: ByteCount) -> usize {
208 match self {
209 IntTy::Isize => ptr_size as usize,
210 IntTy::I8 => size_of::<i8>(),
211 IntTy::I16 => size_of::<i16>(),
212 IntTy::I32 => size_of::<i32>(),
213 IntTy::I64 => size_of::<i64>(),
214 IntTy::I128 => size_of::<i128>(),
215 }
216 }
217}
218impl UIntTy {
219 /// Important: this returns the target byte count for the types.
220 /// Must not be used for host types from rustc.
221 pub fn target_size(&self, ptr_size: ByteCount) -> usize {
222 match self {
223 UIntTy::Usize => ptr_size as usize,
224 UIntTy::U8 => size_of::<u8>(),
225 UIntTy::U16 => size_of::<u16>(),
226 UIntTy::U32 => size_of::<u32>(),
227 UIntTy::U64 => size_of::<u64>(),
228 UIntTy::U128 => size_of::<u128>(),
229 }
230 }
231}
232impl FloatTy {
233 /// Important: this returns the target byte count for the types.
234 /// Must not be used for host types from rustc.
235 pub fn target_size(&self) -> usize {
236 match self {
237 FloatTy::F16 => size_of::<u16>(),
238 FloatTy::F32 => size_of::<u32>(),
239 FloatTy::F64 => size_of::<u64>(),
240 FloatTy::F128 => size_of::<u128>(),
241 }
242 }
243}