Skip to main content

rucc_types/
layout.rs

1//! How large a type is and what it has to be aligned to, computed from the target description.
2//!
3//! Design: `spec/07-types-and-semantics.md` section 7.1 and `spec/18-package-layout.md`
4//! section 18.2, which is the rule that none of this may be a `#[cfg]`.
5//!
6//! Every number here comes out of [`TargetInfo`] rather than out of the host. That is not
7//! pedantry: `long` is four bytes on Windows and eight on Linux, `long double` is eight bytes
8//! on Apple and sixteen on SysV x86-64, and a cross compiler that asks its own platform gets
9//! both of them wrong. The widths were checked against GCC 13 on x86-64 Linux and against
10//! clang on AArch64 Darwin rather than recalled.
11//!
12//! Records are the one thing not computed here. Their layout depends on their members, on
13//! bit-field packing and on attributes, so it is computed by whoever walks the members and
14//! recorded with [`Types::complete_record`](crate::Types::complete_record); this module reads
15//! it back.
16
17use rucc_target::TargetInfo;
18
19use crate::kind::{ArrayLen, FloatKind, IntKind, TypeKind};
20use crate::types::{TypeId, Types};
21
22/// The size and alignment of a complete object type.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct Layout {
25    /// The size in bytes, which is what `sizeof` answers.
26    pub size: u64,
27    /// The alignment in bytes, which is what `_Alignof` answers. Always a power of two.
28    pub align: u64,
29}
30
31impl Layout {
32    /// A layout with the given size and alignment.
33    #[must_use]
34    pub const fn new(size: u64, align: u64) -> Layout {
35        Layout { size, align }
36    }
37
38    /// A scalar that is as aligned as it is large, which every one on a 64-bit target is.
39    #[must_use]
40    const fn scalar(size: u64) -> Layout {
41        Layout { size, align: size }
42    }
43}
44
45/// Why a type has no layout.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum LayoutError {
48    /// The type is incomplete: `void`, an array with no size, or a record or enumeration whose
49    /// definition has not been seen. GNU C gives `sizeof(void)` the value one, and that is a
50    /// dialect decision made where there is a warning to emit, not here.
51    Incomplete,
52    /// The type is a function type, which has no size at all. GNU C gives it the value one for
53    /// the same reason it does for `void`.
54    Function,
55    /// The type is complete and describes an object larger than the address space, which an
56    /// array declaration can ask for by multiplying two innocent looking numbers.
57    TooLarge,
58}
59
60impl std::fmt::Display for LayoutError {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        let text = match self {
63            LayoutError::Incomplete => "the type is incomplete",
64            LayoutError::Function => "a function type has no size",
65            LayoutError::TooLarge => "the type is larger than the address space",
66        };
67        f.write_str(text)
68    }
69}
70
71impl std::error::Error for LayoutError {}
72
73/// The size and alignment of `id` on `target`.
74///
75/// # Errors
76///
77/// [`LayoutError`] when the type has no layout, which is a normal answer rather than a bug:
78/// `sizeof` an incomplete type is a diagnostic, and the caller is the one holding the span.
79pub fn layout(types: &Types, id: TypeId, target: &TargetInfo) -> Result<Layout, LayoutError> {
80    // Sugar has whatever layout the type behind it has, and a typedef of an array of a typedef
81    // is common enough that resolving it once here beats resolving it at every arm below.
82    let id = types.canonical(id);
83    match types.kind(id) {
84        TypeKind::Void => Err(LayoutError::Incomplete),
85        TypeKind::Bool => Ok(Layout::scalar(1)),
86        TypeKind::Int(kind) => Ok(Layout::scalar(u64::from(int_width(kind, target) / 8))),
87        TypeKind::Float(kind) => Ok(Layout::scalar(u64::from(float_width(kind, target) / 8))),
88        TypeKind::Complex(kind) => {
89            // Two of the component, adjacent, with the component's own alignment rather than
90            // the pair's. `_Complex long double` on SysV x86-64 is thirty two bytes aligned to
91            // sixteen, which is what both GCC and clang report.
92            let part = Layout::scalar(u64::from(float_width(kind, target) / 8));
93            Ok(Layout::new(part.size * 2, part.align))
94        }
95        TypeKind::BitInt { width, .. } => Ok(bit_int_layout(width, target)),
96        TypeKind::Pointer(_) => Ok(Layout::scalar(u64::from(target.pointer_width / 8))),
97        TypeKind::Function(_) => Err(LayoutError::Function),
98        TypeKind::Atomic(inner) => {
99            let inner = layout(types, inner, target)?;
100            Ok(atomic_layout(inner))
101        }
102        TypeKind::Array { elem, len } => {
103            let ArrayLen::Fixed(count) = len else {
104                return Err(LayoutError::Incomplete);
105            };
106            let elem = layout(types, elem, target)?;
107            let size = elem.size.checked_mul(count).ok_or(LayoutError::TooLarge)?;
108            Ok(Layout::new(size, elem.align))
109        }
110        TypeKind::Vector { elem, len } => {
111            let elem = layout(types, elem, target)?;
112            let raw = elem.size.checked_mul(u64::from(len)).ok_or(LayoutError::TooLarge)?;
113            Ok(vector_layout(raw))
114        }
115        TypeKind::Record(record) => types.record_info(record).layout.ok_or(LayoutError::Incomplete),
116        TypeKind::Enum(id) => {
117            let underlying = types.enum_info(id).underlying.ok_or(LayoutError::Incomplete)?;
118            layout(types, underlying, target)
119        }
120        // Unreachable in practice: the id was canonicalised on the way in. Answering rather
121        // than panicking, because a wrong size is easier to find than a crash in a compiler.
122        TypeKind::Typedef { underlying, .. } => layout(types, underlying, target),
123    }
124}
125
126/// The width of a standard integer type in bits.
127#[must_use]
128pub fn int_width(kind: IntKind, target: &TargetInfo) -> u32 {
129    match kind {
130        IntKind::Char | IntKind::SChar | IntKind::UChar => 8,
131        IntKind::Short | IntKind::UShort => 16,
132        IntKind::Int | IntKind::UInt => 32,
133        IntKind::Long | IntKind::ULong => target.long_width,
134        IntKind::LongLong | IntKind::ULongLong => 64,
135        IntKind::Int128 | IntKind::UInt128 => 128,
136    }
137}
138
139/// The width of a real floating type in bits, including the padding `long double` carries.
140///
141/// The number for `long double` is storage rather than precision. Eighty bits of x87 occupy
142/// sixteen bytes on SysV x86-64, and it is the sixteen that `sizeof` answers with.
143#[must_use]
144pub fn float_width(kind: FloatKind, target: &TargetInfo) -> u32 {
145    match kind {
146        FloatKind::Float => 32,
147        FloatKind::Double => 64,
148        FloatKind::LongDouble => target.long_double_width,
149    }
150}
151
152/// The layout of `_BitInt(width)`.
153///
154/// Up to 64 bits a `_BitInt` is laid out like the smallest standard integer type that holds
155/// it, so the size is the byte count rounded up to a power of two and the alignment is the
156/// size. Above that the psABIs treat it as an array of a granule instead, and the granule is
157/// not the same everywhere: it is 64 bits on x86-64 and RISC-V and 128 on AArch64, which is
158/// why `_BitInt(65)` is sixteen bytes aligned to eight on the first and sixteen bytes aligned
159/// to sixteen on the second. Measured with clang 18 on x86-64 Linux and clang on AArch64
160/// Darwin, including the cases above 128 bits where the size keeps growing by a granule.
161fn bit_int_layout(width: u32, target: &TargetInfo) -> Layout {
162    let bytes = u64::from(width).div_ceil(8);
163    if bytes <= 8 {
164        return Layout::scalar(bytes.max(1).next_power_of_two());
165    }
166    let granule = u64::from(target.bit_int_granule / 8);
167    Layout::new(bytes.next_multiple_of(granule), granule)
168}
169
170/// The layout of `_Atomic(T)` given the layout of `T`.
171///
172/// Same size, and an alignment raised to the size when the size is one of the widths the
173/// target can do a lock free access at. That is why `_Atomic` is a type and not a qualifier:
174/// a sixteen byte structure is aligned to eight and `_Atomic` of it is aligned to sixteen, and
175/// a type system that treated the two as one type would silently disagree with itself about
176/// where the object goes. Checked against GCC 13 on x86-64 Linux and clang on AArch64 Darwin,
177/// which report exactly that.
178fn atomic_layout(inner: Layout) -> Layout {
179    if inner.size.is_power_of_two() && inner.size <= 16 {
180        return Layout::new(inner.size, inner.align.max(inner.size));
181    }
182    inner
183}
184
185/// The layout of a GNU vector whose elements occupy `raw` bytes in total.
186///
187/// Rounded up to a power of two and aligned to the whole thing, which is what GCC does with a
188/// `vector_size` that is not already one. GCC rejects an element count that is not a power of
189/// two and clang rounds instead, so this rounds and leaves the rejecting to whoever is holding
190/// the attribute and the dialect.
191fn vector_layout(raw: u64) -> Layout {
192    let size = raw.max(1).next_power_of_two();
193    Layout::scalar(size)
194}