midenc_hir_type/
struct_type.rs

1use core::{fmt, num::NonZeroU16};
2
3use smallvec::SmallVec;
4
5use super::{Alignable, Type};
6
7/// This represents a structured aggregate type
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct StructType {
11    /// The representation to use for this type
12    pub(crate) repr: TypeRepr,
13    /// The computed size of this struct
14    pub(crate) size: u32,
15    /// The fields of this struct, in the original order specified
16    ///
17    /// The actual order of fields in the final layout is determined by the index
18    /// associated with each field, not the index in this vector, although for `repr(C)`
19    /// structs they will be the same
20    pub(crate) fields: SmallVec<[StructField; 2]>,
21}
22
23/// This represents metadata about a field of a [StructType]
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct StructField {
27    /// The index of this field in the final layout
28    pub index: u8,
29    /// The specified alignment for this field
30    pub align: u16,
31    /// The offset of this field relative to the base of the struct
32    pub offset: u32,
33    /// The type of this field
34    pub ty: Type,
35}
36
37/// This represents metadata about how a structured type will be represented in memory
38#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub enum TypeRepr {
41    /// This corresponds to the C ABI representation for a given type
42    #[default]
43    Default,
44    /// This modifies the default representation, by raising the minimum alignment.
45    ///
46    /// The alignment must be a power of two, e.g. 32, and values from 1 to 2^16 are allowed.
47    ///
48    /// The alignment must be greater than the default minimum alignment of the type
49    /// or this representation has no effect.
50    Align(NonZeroU16),
51    /// This modifies the default representation, by lowering the minimum alignment of
52    /// a type, and in the case of structs, changes the alignments of the fields to be
53    /// the smaller of the specified alignment and the default alignment. This has the
54    /// effect of changing the layout of a struct.
55    ///
56    /// Notably, `Packed(1)` will result in a struct that has no alignment requirement,
57    /// and no padding between fields.
58    ///
59    /// The alignment must be a power of two, e.g. 32, and values from 1 to 2^16 are allowed.
60    ///
61    /// The alignment must be smaller than the default alignment, or this representation
62    /// has no effect.
63    Packed(NonZeroU16),
64    /// This may only be used on structs with no more than one non-zero sized field, and
65    /// indicates that the representation of that field should be used for the struct.
66    Transparent,
67    /// This is equivalent to the default representation, except it indicates that if multiple
68    /// field elements are required to represent the value on Miden's operand stack (i.e. the
69    /// value is larger than 4 bytes), then the field elements will be ordered on the operand stack
70    /// with the highest-addressed bytes at the top.
71    ///
72    /// Normally, types are laid out in natural order (i.e. lowest-addressed bytes on top of the
73    /// stack), and when lowering word-sized loads/stores, we are required to reverse the order
74    /// of the elements into big-endian order.
75    ///
76    /// This representation essentially disables this implicit reversal, keeping elements on the
77    /// operand stack in the order produced by `mem_loadw`.
78    ///
79    /// NOTE: This is meant to be a temporary work around to permit us to represent some legacy
80    /// types in the transaction kernel API which use a different representation on the operand
81    /// stack than in memory - this _will_ be deprecated in the future.
82    BigEndian,
83}
84
85impl TypeRepr {
86    /// Construct a packed representation with the given alignment
87    #[inline]
88    pub fn packed(align: u16) -> Self {
89        Self::Packed(
90            NonZeroU16::new(align).expect("invalid alignment: expected value in range 1..=65535"),
91        )
92    }
93
94    /// Construct a representation with the given minimum alignment
95    #[inline]
96    pub fn align(align: u16) -> Self {
97        Self::Align(
98            NonZeroU16::new(align).expect("invalid alignment: expected value in range 1..=65535"),
99        )
100    }
101
102    /// Return true if this type representation is transparent
103    pub fn is_transparent(&self) -> bool {
104        matches!(self, Self::Transparent)
105    }
106
107    /// Return true if this type representation is packed
108    pub fn is_packed(&self) -> bool {
109        matches!(self, Self::Packed(_))
110    }
111
112    /// Get the custom alignment given for this type representation, if applicable
113    pub fn min_alignment(&self) -> Option<usize> {
114        match self {
115            Self::Packed(align) | Self::Align(align) => Some(align.get() as usize),
116            _ => None,
117        }
118    }
119}
120
121impl StructType {
122    /// Create a new struct with default representation, i.e. a struct with representation of
123    /// `TypeRepr::Packed(1)`.
124    #[inline]
125    pub fn new<I: IntoIterator<Item = Type>>(fields: I) -> Self {
126        Self::new_with_repr(TypeRepr::Default, fields)
127    }
128
129    /// Create a new struct with the given representation.
130    ///
131    /// This function will panic if the rules of the given representation are violated.
132    pub fn new_with_repr<I: IntoIterator<Item = Type>>(repr: TypeRepr, fields: I) -> Self {
133        let tys = fields.into_iter().collect::<SmallVec<[_; 2]>>();
134        let mut fields = SmallVec::<[_; 2]>::with_capacity(tys.len());
135        let size = match repr {
136            TypeRepr::Transparent => {
137                let mut offset = 0u32;
138                for (index, ty) in tys.into_iter().enumerate() {
139                    let index: u8 =
140                        index.try_into().expect("invalid struct: expected no more than 255 fields");
141                    let field_size: u32 = ty
142                        .size_in_bytes()
143                        .try_into()
144                        .expect("invalid type: size is larger than 2^32 bytes");
145                    if field_size == 0 {
146                        fields.push(StructField {
147                            index,
148                            align: 1,
149                            offset,
150                            ty,
151                        });
152                    } else {
153                        let align = ty.min_alignment().try_into().expect(
154                            "invalid struct field alignment: expected power of two between 1 and \
155                             2^16",
156                        );
157                        assert_eq!(
158                            offset, 0,
159                            "invalid transparent representation for struct: repr(transparent) is \
160                             only valid for structs with a single non-zero sized field"
161                        );
162                        fields.push(StructField {
163                            index,
164                            align,
165                            offset,
166                            ty,
167                        });
168                        offset += field_size;
169                    }
170                }
171                offset
172            }
173            repr => {
174                let mut offset = 0u32;
175                let default_align: u16 =
176                    tys.iter().map(|t| t.min_alignment()).max().unwrap_or(1).try_into().expect(
177                        "invalid struct field alignment: expected power of two between 1 and 2^16",
178                    );
179                let align = match repr {
180                    TypeRepr::Align(align) => core::cmp::max(align.get(), default_align),
181                    TypeRepr::Packed(align) => core::cmp::min(align.get(), default_align),
182                    TypeRepr::Transparent | TypeRepr::Default | TypeRepr::BigEndian => {
183                        default_align
184                    }
185                };
186
187                for (index, ty) in tys.into_iter().enumerate() {
188                    let index: u8 =
189                        index.try_into().expect("invalid struct: expected no more than 255 fields");
190                    let field_size: u32 = ty
191                        .size_in_bytes()
192                        .try_into()
193                        .expect("invalid type: size is larger than 2^32 bytes");
194                    let default_align: u16 = ty.min_alignment().try_into().expect(
195                        "invalid struct field alignment: expected power of two between 1 and 2^16",
196                    );
197                    let align: u16 = match repr {
198                        TypeRepr::Packed(align) => core::cmp::min(align.get(), default_align),
199                        _ => default_align,
200                    };
201                    offset += offset.align_offset(align as u32);
202                    fields.push(StructField {
203                        index,
204                        align,
205                        offset,
206                        ty,
207                    });
208                    offset += field_size;
209                }
210                offset.align_up(align as u32)
211            }
212        };
213
214        Self { repr, size, fields }
215    }
216
217    /// Get the [TypeRepr] for this struct
218    #[inline]
219    pub const fn repr(&self) -> TypeRepr {
220        self.repr
221    }
222
223    /// Get the minimum alignment for this struct
224    pub fn min_alignment(&self) -> usize {
225        self.repr
226            .min_alignment()
227            .unwrap_or_else(|| self.fields.iter().map(|f| f.align as usize).max().unwrap_or(1))
228    }
229
230    /// Get the total size in bytes required to hold this struct, including alignment padding
231    #[inline]
232    pub fn size(&self) -> usize {
233        self.size as usize
234    }
235
236    /// Get the struct field at `index`, relative to declaration order.
237    pub fn get(&self, index: usize) -> &StructField {
238        &self.fields[index]
239    }
240
241    /// Get the struct fields as a slice
242    pub fn fields(&self) -> &[StructField] {
243        self.fields.as_slice()
244    }
245
246    /// Returns true if this struct has no fields
247    pub fn is_empty(&self) -> bool {
248        self.fields.is_empty()
249    }
250
251    /// Get the length of this struct (i.e. number of fields)
252    pub fn len(&self) -> usize {
253        self.fields.len()
254    }
255}
256
257impl TryFrom<Type> for StructType {
258    type Error = Type;
259
260    fn try_from(ty: Type) -> Result<Self, Self::Error> {
261        match ty {
262            Type::Struct(ty) => Ok(alloc::sync::Arc::unwrap_or_clone(ty)),
263            other => Err(other),
264        }
265    }
266}
267
268impl fmt::Display for StructType {
269    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270        use miden_formatting::prettier::PrettyPrint;
271        self.pretty_print(f)
272    }
273}
274
275impl miden_formatting::prettier::PrettyPrint for StructType {
276    fn render(&self) -> miden_formatting::prettier::Document {
277        use miden_formatting::prettier::*;
278
279        let header = match self.repr.render() {
280            Document::Empty => const_text("struct "),
281            repr => const_text("struct ") + const_text("#[repr(") + repr + const_text(")] "),
282        };
283
284        let singleline = self.fields.iter().enumerate().fold(Document::Empty, |acc, (i, field)| {
285            if i > 0 {
286                acc + const_text(", ") + field.render()
287            } else {
288                field.render()
289            }
290        });
291        let multiline = indent(
292            4,
293            self.fields.iter().enumerate().fold(Document::Empty, |acc, (i, field)| {
294                if i > 0 {
295                    acc + nl() + field.render()
296                } else {
297                    nl() + field.render()
298                }
299            }),
300        );
301        let body = const_text("{") + (singleline | multiline) + const_text("}");
302
303        header + body
304    }
305}
306
307impl fmt::Display for StructField {
308    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
309        fmt::Display::fmt(&self.ty, f)
310    }
311}
312
313impl miden_formatting::prettier::PrettyPrint for StructField {
314    fn render(&self) -> miden_formatting::prettier::Document {
315        self.ty.render()
316    }
317}
318
319impl fmt::Display for TypeRepr {
320    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
321        use miden_formatting::prettier::PrettyPrint;
322        self.pretty_print(f)
323    }
324}
325
326impl miden_formatting::prettier::PrettyPrint for TypeRepr {
327    fn render(&self) -> miden_formatting::prettier::Document {
328        use alloc::format;
329
330        use miden_formatting::prettier::*;
331        match self {
332            Self::Default => Document::Empty,
333            Self::Transparent => const_text("transparent"),
334            Self::Align(align) => text(format!("align({align})")),
335            Self::Packed(align) => text(format!("packed({align})")),
336            Self::BigEndian => const_text("big-endian"),
337        }
338    }
339}