Skip to main content

rucc_types/
granule.rs

1//! Which type each byte of a record has, and how often one granule of them holds only one type.
2//!
3//! Design: `spec/safe-memory/05-representation.md` sections 5.2.3 and 5.2.5, and this is the
4//! measurement `spec/safe-memory/17-open-questions.md` question 6 asks for.
5//!
6//! The question it answers, stated exactly. A type plane that records an effective type per
7//! byte costs four bytes for every byte of the program, which is the 4:1 that makes TySan
8//! unaffordable. The compression in document 05 stores one entry per granule of `g` bytes and
9//! falls back to a per-byte side table over the whole granule for the ones whose bytes do not
10//! all agree, which costs `4/g + 4h` bytes per byte where `h` is the fraction of granules that
11//! disagree. Tier D's memory budget allows 1.25. Everything the type plane costs depends on
12//! where `h` really lands, which had never been measured until this ran.
13//!
14//! What it found is why [`GRANULE`] is eight and not the sixteen document 05 first assumed.
15//! On a 64-bit target the unit of a distinct type is eight bytes, so a sixteen byte granule
16//! holds two of them and `struct { char *p; int a; int b; }` is enough to make it disagree.
17//! SQLite is 64.8% heterogeneous at sixteen, which costs 2.84 against a budget of 1.25, and
18//! 12.6% at eight, which costs 1.00.
19//!
20//! Working from the compiler's own layouts rather than from DWARF, which is what question 6
21//! proposes, because they are the same layouts: DWARF is where these numbers go, not where
22//! they come from. Reading them here also keeps the type identity, which in DWARF is a
23//! reference to resolve rather than a fact to hand.
24//!
25//! What counts as one type is a decision and not a discovery, so it is [`Keying`] and both
26//! answers get reported.
27
28use rucc_target::TargetInfo;
29
30use crate::kind::{ArrayLen, RecordId, RecordKind, TypeKind};
31use crate::layout::layout;
32use crate::record::Field;
33use crate::types::{TypeId, Types};
34
35/// How many bytes one granule covers.
36///
37/// Eight, because that is where the curve in document 05.2.5 bottoms out on both of the inputs
38/// measured, and because it is the size of a pointer, which is the thing whose type the plane
39/// most wants to be right about. It is a default and not a law, which is why [`measure`] takes
40/// the size rather than reading it: the granule size is the one dial the design has, and what
41/// it buys is the thing worth reporting.
42pub const GRANULE: u64 = 8;
43
44/// The granule sizes the report walks.
45///
46/// One is byte granularity, which is the uncompressed plane and is here as the number the
47/// compression has to beat.
48const SIZES: &[u64] = &[1, 4, 8, 16, 32, 64];
49
50/// The largest record this measures, in bytes.
51///
52/// Painting a byte at a time means a record with a large array member costs its own size in
53/// memory, and a translation unit is allowed to declare a structure holding a megabyte of
54/// buffer. Skipped records are counted and reported rather than silently dropped, because a
55/// measurement that quietly ignores its largest inputs is the one that reads best.
56const LIMIT: u64 = 1 << 20;
57
58/// How many layouts of one record this is willing to walk.
59///
60/// A union is a choice rather than a coexistence: at any moment its bytes hold whichever
61/// member was last stored, so a union of a `long` and a `double` fills its granule with one
62/// type either way and a plane keyed by granule has no trouble with it. That means a record
63/// containing unions has one layout per combination of choices, and a record with several
64/// unions has the product of them. Past this many the record is measured with every member
65/// painted at once instead, which can only say a granule disagrees when it might not, so the
66/// answer stays on the pessimistic side of the truth.
67const LAYOUTS: usize = 32;
68
69/// What is treated as one type when deciding whether a granule agrees with itself.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum Keying {
72    /// Two bytes agree when their types are the same after typedefs and qualifiers are
73    /// resolved and an enumeration is replaced by what it is represented in.
74    ///
75    /// Qualifiers go because an effective type has none: writing through a `const int *` and
76    /// through an `int *` stores the same effective type, and 6.5p6 says so. Enumerations go
77    /// because an enumeration is compatible with an implementation-defined integer type and
78    /// keeping them apart would count a real structure as mixed over a distinction no access
79    /// can observe.
80    Exact,
81    /// The same, and additionally every pointer type is one type.
82    ///
83    /// Worth reporting separately because it is the one classification choice with a large
84    /// effect on the answer and no obviously right side. A plane that distinguishes `char *`
85    /// from `struct Foo *` catches a type confusion between them; a plane that does not is
86    /// cheaper, and how much cheaper is what the two numbers say.
87    PointersTogether,
88}
89
90/// What one byte holds.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92enum Cell {
93    /// Padding, or a byte no member reaches. It has no effective type and the plane stores
94    /// nothing for it.
95    Pad,
96    /// Exactly one type reaches this byte.
97    One(TypeKind),
98    /// More than one does, which within one layout means overlapping bit-fields of different
99    /// declared types.
100    Mixed,
101}
102
103impl Cell {
104    /// The result of a member of type `kind` also reaching a byte that already holds `self`.
105    fn with(self, kind: TypeKind) -> Cell {
106        match self {
107            Cell::Pad => Cell::One(kind),
108            Cell::One(had) if had == kind => self,
109            _ => Cell::Mixed,
110        }
111    }
112}
113
114/// How the bytes of some records fall into granules.
115///
116/// Everything here counts, so two tallies add, and the whole translation unit is the sum of
117/// its records.
118#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
119pub struct Tally {
120    /// How many records were measured.
121    pub records: u64,
122    /// How many were too large to measure and were skipped.
123    pub skipped: u64,
124    /// How many bytes those records occupy in total.
125    pub bytes: u64,
126    /// How many of those bytes have no effective type, meaning padding.
127    pub padding: u64,
128    /// Granules whose typed bytes all agree, which cost one entry.
129    pub uniform: u64,
130    /// Granules whose typed bytes do not, which cost an entry and a sixteen byte side table.
131    pub mixed: u64,
132    /// Granules with no typed bytes at all, which are padding to the end of the record.
133    pub blank: u64,
134}
135
136impl Tally {
137    /// How many granules were measured.
138    #[must_use]
139    pub fn granules(&self) -> u64 {
140        self.uniform + self.mixed + self.blank
141    }
142
143    /// The fraction of granules that need a side table, which is question 6's `h`.
144    ///
145    /// A blank granule needs no side table, so it counts as agreeing here even though nothing
146    /// in it agrees about anything. Zero granules gives zero, because a translation unit that
147    /// declares no records puts no pressure on the budget.
148    #[must_use]
149    pub fn disagreeing(&self) -> f64 {
150        let all = self.granules();
151        if all == 0 { 0.0 } else { self.mixed as f64 / all as f64 }
152    }
153
154    /// Bytes of type plane per byte of program, under document 05's compression.
155    ///
156    /// One four byte entry per granule, plus a four byte per-byte side table over the whole
157    /// granule for the ones that need one, so it is `4/g + 4h` and the granule size only
158    /// touches the first term. Tier D's budget is 1.25 and the uncompressed plane is 4, which
159    /// is also what this returns at a granule of one byte, since nothing disagrees with itself.
160    #[must_use]
161    pub fn ratio(&self, granule: u64) -> f64 {
162        4.0 / granule as f64 + 4.0 * self.disagreeing()
163    }
164
165    /// Adds another tally into this one.
166    pub fn absorb(&mut self, other: Tally) {
167        self.records += other.records;
168        self.skipped += other.skipped;
169        self.bytes += other.bytes;
170        self.padding += other.padding;
171        self.uniform += other.uniform;
172        self.mixed += other.mixed;
173        self.blank += other.blank;
174    }
175}
176
177/// Measures one record.
178///
179/// Returns nothing for a record that is not complete, since an incomplete one has no layout
180/// and no members to walk. A record too large to paint comes back as a tally that counts
181/// only its skip.
182#[must_use]
183pub fn measure(
184    types: &Types,
185    id: RecordId,
186    target: &TargetInfo,
187    keying: Keying,
188    granule: u64,
189) -> Option<Tally> {
190    let info = types.record_info(id);
191    let size = info.layout?.size;
192    if size > LIMIT {
193        return Some(Tally { skipped: 1, ..Tally::default() });
194    }
195    let width = usize::try_from(granule).ok().filter(|width| *width > 0)?;
196    let mut layouts = vec![vec![Cell::Pad; usize::try_from(size).ok()?]];
197    paint_record(types, id, 0, target, keying, &mut layouts);
198
199    let mut tally = Tally { records: 1, bytes: size, ..Tally::default() };
200    let count = layouts[0].len().div_ceil(width);
201    for index in 0..count {
202        let from = index * width;
203        let to = (from + width).min(layouts[0].len());
204        // A granule disagrees if it disagrees under any one layout, and is padding only if it
205        // is padding under all of them, which is why the whole granule is looked at once per
206        // layout rather than a byte at a time across them.
207        let mut disagrees = false;
208        let mut typed = false;
209        for layout in &layouts {
210            let (mixed, seen) = verdict(&layout[from..to]);
211            disagrees |= mixed;
212            typed |= seen;
213        }
214        match (disagrees, typed) {
215            (true, _) => tally.mixed += 1,
216            (false, true) => tally.uniform += 1,
217            (false, false) => tally.blank += 1,
218        }
219        tally.padding += (from..to)
220            .filter(|byte| layouts.iter().all(|layout| layout[*byte] == Cell::Pad))
221            .count() as u64;
222    }
223    Some(tally)
224}
225
226/// Whether the bytes of one granule under one layout disagree, and whether any is typed.
227fn verdict(granule: &[Cell]) -> (bool, bool) {
228    let mut seen: Option<TypeKind> = None;
229    for cell in granule {
230        match *cell {
231            Cell::Pad => {}
232            Cell::Mixed => return (true, true),
233            Cell::One(kind) => match seen {
234                None => seen = Some(kind),
235                Some(had) if had == kind => {}
236                Some(_) => return (true, true),
237            },
238        }
239    }
240    (false, seen.is_some())
241}
242
243/// Measures every complete record in the translation unit.
244#[must_use]
245pub fn measure_all(types: &Types, target: &TargetInfo, keying: Keying, granule: u64) -> Tally {
246    let mut tally = Tally::default();
247    for (id, _) in types.records() {
248        if let Some(one) = measure(types, id, target, keying, granule) {
249            tally.absorb(one);
250        }
251    }
252    tally
253}
254
255/// The measurement for one translation unit, as text.
256///
257/// Text and not JSON, unlike the safety summary, because the safety summary is a number a
258/// build watches and this is a table somebody reads once and writes a paragraph about. One
259/// line per record so the worst offenders can be found with `sort`, then the curve of what the
260/// plane costs against the granule size, under both keyings.
261///
262/// # Panics
263///
264/// Panics if writing to a `String` fails, which it does not.
265#[must_use]
266pub fn report(types: &Types, names: &rucc_base::Interner, target: &TargetInfo) -> String {
267    use std::fmt::Write as _;
268
269    let mut out = String::new();
270    writeln!(out, "per record, at a granule of {GRANULE} bytes\n").expect("a string takes writes");
271    writeln!(out, "{:>8} {:>8} {:>8} {:>8}  record", "bytes", "uniform", "mixed", "blank")
272        .expect("a string takes writes");
273    for (id, info) in types.records() {
274        let Some(tally) = measure(types, id, target, Keying::Exact, GRANULE) else {
275            continue;
276        };
277        let kind = match info.kind {
278            RecordKind::Struct => "struct",
279            RecordKind::Union => "union",
280        };
281        let tag = match info.tag {
282            Some(tag) => names.resolve(tag).to_string(),
283            None => format!("<anonymous {}>", id.0),
284        };
285        writeln!(
286            out,
287            "{:>8} {:>8} {:>8} {:>8}  {kind} {tag}",
288            tally.bytes, tally.uniform, tally.mixed, tally.blank
289        )
290        .expect("a string takes writes");
291    }
292    for keying in [Keying::Exact, Keying::PointersTogether] {
293        let label = match keying {
294            Keying::Exact => "every type distinct",
295            Keying::PointersTogether => "every pointer one type",
296        };
297        writeln!(out, "\n{label}").expect("a string takes writes");
298        writeln!(
299            out,
300            "{:>8} {:>8} {:>9} {:>9} {:>9} {:>9}",
301            "granule", "records", "bytes", "granules", "disagree", "plane"
302        )
303        .expect("a string takes writes");
304        for &size in SIZES {
305            let tally = measure_all(types, target, keying, size);
306            writeln!(
307                out,
308                "{:>8} {:>8} {:>9} {:>9} {:>9.4} {:>9.4}",
309                size,
310                tally.records,
311                tally.bytes,
312                tally.granules(),
313                tally.disagreeing(),
314                tally.ratio(size)
315            )
316            .expect("a string takes writes");
317        }
318    }
319    let whole = measure_all(types, target, Keying::Exact, GRANULE);
320    writeln!(out, "\npadding   {} of {} bytes", whole.padding, whole.bytes)
321        .expect("a string takes writes");
322    writeln!(out, "skipped   {} records too large to measure", whole.skipped)
323        .expect("a string takes writes");
324    writeln!(out, "budget    1.25 bytes of plane per byte of program, at Tier D")
325        .expect("a string takes writes");
326    out
327}
328
329/// Writes the type of every byte `ty` occupies at `base` into every layout.
330///
331/// Recursion is what makes the answer right: the effective type a store leaves behind is the
332/// type of the lvalue it stored through, which for a nested structure is the scalar member and
333/// not the structure. Padding is never written, so it stays [`Cell::Pad`] and the plane owes
334/// it nothing.
335fn paint(
336    types: &Types,
337    ty: TypeId,
338    base: u64,
339    target: &TargetInfo,
340    keying: Keying,
341    layouts: &mut Vec<Vec<Cell>>,
342) {
343    let canonical = types.canonical(ty);
344    match types.kind(canonical) {
345        TypeKind::Record(id) => paint_record(types, id, base, target, keying, layouts),
346        TypeKind::Array { elem, len } => {
347            let ArrayLen::Fixed(count) = len else {
348                // A flexible array member, a variable length array or `[*]`. None of them has
349                // a size the declaration knows, and a flexible array member deliberately
350                // contributes nothing to `sizeof`, so there are no bytes here to paint.
351                return;
352            };
353            let Ok(each) = layout(types, elem, target) else {
354                return;
355            };
356            for index in 0..count {
357                let Some(at) = each.size.checked_mul(index).and_then(|off| base.checked_add(off))
358                else {
359                    return;
360                };
361                paint(types, elem, at, target, keying, layouts);
362            }
363        }
364        // An atomic type is its inner type with a rule about how it is accessed, and the plane
365        // records what was stored rather than how.
366        TypeKind::Atomic(inner) => paint(types, inner, base, target, keying, layouts),
367        _ => {
368            let Ok(whole) = layout(types, canonical, target) else {
369                return;
370            };
371            fill(types, canonical, base, whole.size, keying, layouts);
372        }
373    }
374}
375
376/// Writes the type of every byte one record occupies at `base` into every layout.
377///
378/// A `struct` places its members side by side, so every one of them goes into every layout.
379/// A `union` places them on top of each other and the program picks one, so it multiplies the
380/// layouts instead: the answer for a union of a `long` and a `double` is that its granule holds
381/// one type, whichever member was stored, and a plane keyed by granule handles it. That is the
382/// difference between a choice and a coexistence, and getting it wrong is what would make every
383/// tagged value in a real program look like it needs a side table.
384///
385/// Separate from [`paint`] because a record is reached both through a type and through a
386/// [`RecordId`] on its own, and asking the type table for the type of a record it already has
387/// would need to intern one.
388fn paint_record(
389    types: &Types,
390    id: RecordId,
391    base: u64,
392    target: &TargetInfo,
393    keying: Keying,
394    layouts: &mut Vec<Vec<Cell>>,
395) {
396    let info = types.record_info(id);
397    let grown = match info.kind {
398        RecordKind::Union if info.fields.len() > 1 => {
399            layouts.len().checked_mul(info.fields.len()).filter(|grown| *grown <= LAYOUTS)
400        }
401        _ => None,
402    };
403    if let Some(grown) = grown {
404        let start = layouts.clone();
405        let mut out = Vec::with_capacity(grown);
406        for field in &info.fields {
407            let mut copy = start.clone();
408            place(types, field, base, target, keying, &mut copy);
409            out.append(&mut copy);
410        }
411        *layouts = out;
412        return;
413    }
414    for field in &info.fields {
415        let at = match info.kind {
416            RecordKind::Struct => base + field.offset,
417            RecordKind::Union => base,
418        };
419        place(types, field, at, target, keying, layouts);
420    }
421}
422
423/// Writes one member, placed at `at`, into every layout.
424fn place(
425    types: &Types,
426    field: &Field,
427    at: u64,
428    target: &TargetInfo,
429    keying: Keying,
430    layouts: &mut Vec<Vec<Cell>>,
431) {
432    match field.bits {
433        // A zero width bit-field places nothing and is only there to move the next member
434        // along.
435        Some(0) => {}
436        // A bit-field has no address, so the granule question is about the bytes a load of it
437        // would touch. Two bit-fields of different declared types sharing a byte make that byte
438        // disagree with itself, which is the honest answer: a plane keyed by byte cannot tell
439        // them apart, and unlike a union they are both there at once.
440        Some(width) => {
441            let bytes = u64::from(field.bit + width).div_ceil(8);
442            fill(types, field.ty, at, bytes, keying, layouts);
443        }
444        None => paint(types, field.ty, at, target, keying, layouts),
445    }
446}
447
448/// Records that `count` bytes from `base` hold the type `ty`, in every layout.
449///
450/// Clipped to the record rather than checked, because a union member painted at the start of a
451/// record it does not fill and an array whose arithmetic ran past the end both want the same
452/// answer, which is to write what is inside and drop what is not.
453fn fill(
454    types: &Types,
455    ty: TypeId,
456    base: u64,
457    count: u64,
458    keying: Keying,
459    layouts: &mut [Vec<Cell>],
460) {
461    let kind = key(types, ty, keying);
462    let Ok(from) = usize::try_from(base) else {
463        return;
464    };
465    for cells in layouts.iter_mut() {
466        let to = usize::try_from(base.saturating_add(count)).unwrap_or(usize::MAX).min(cells.len());
467        if from >= to {
468            continue;
469        }
470        for cell in &mut cells[from..to] {
471            *cell = cell.with(kind);
472        }
473    }
474}
475
476/// What `ty` counts as, under `keying`.
477fn key(types: &Types, ty: TypeId, keying: Keying) -> TypeKind {
478    let kind = types.kind(types.canonical(ty));
479    match kind {
480        // An enumeration is compatible with the integer type it is represented in, so a byte
481        // written through one and a byte written through the other hold the same effective
482        // type and a plane that separated them would be counting a distinction no access can
483        // make. Before the underlying type is decided there is nothing to fold to.
484        TypeKind::Enum(id) => match types.enum_info(id).underlying {
485            Some(underlying) => types.kind(types.canonical(underlying)),
486            None => kind,
487        },
488        TypeKind::Pointer(_) if keying == Keying::PointersTogether => {
489            TypeKind::Pointer(types.void())
490        }
491        _ => kind,
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use rucc_base::Interner;
498    use rucc_target::Triple;
499
500    use super::*;
501    use crate::kind::IntKind;
502    use crate::layout_record;
503    use crate::record::{FieldDecl, RecordOptions};
504
505    fn target() -> TargetInfo {
506        TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().expect("a triple"))
507    }
508
509    /// Builds a struct from its members and measures it under both keyings.
510    fn built(types: &mut Types, names: &mut Interner, members: &[(&str, TypeId)]) -> RecordId {
511        let fields: Vec<FieldDecl> = members
512            .iter()
513            .map(|(name, ty)| FieldDecl::new(Some(names.intern(name)), *ty))
514            .collect();
515        let id = types.declare_record(RecordKind::Struct, None);
516        let laid_out =
517            layout_record(types, RecordKind::Struct, &fields, &RecordOptions::default(), &target())
518                .expect("a record with a layout");
519        types.complete_record(id, laid_out);
520        id
521    }
522
523    #[test]
524    fn a_granule_of_one_type_agrees_with_itself() {
525        let mut types = Types::new();
526        let mut names = Interner::new();
527        let long = types.int(IntKind::Long);
528        let id = built(&mut types, &mut names, &[("a", long), ("b", long)]);
529
530        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
531        assert_eq!(tally.bytes, 16);
532        assert_eq!(tally.uniform, 1);
533        assert_eq!(tally.mixed, 0);
534        assert_eq!(tally.padding, 0);
535    }
536
537    #[test]
538    fn two_types_in_one_granule_do_not() {
539        let mut types = Types::new();
540        let mut names = Interner::new();
541        let long = types.int(IntKind::Long);
542        let double = types.float(crate::kind::FloatKind::Double);
543        let id = built(&mut types, &mut names, &[("a", long), ("b", double)]);
544
545        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
546        assert_eq!(tally.bytes, 16);
547        assert_eq!(tally.mixed, 1);
548        assert_eq!(tally.uniform, 0);
549    }
550
551    #[test]
552    fn padding_has_no_type_and_costs_nothing() {
553        let mut types = Types::new();
554        let mut names = Interner::new();
555        let ch = types.int(IntKind::Char);
556        let id = built(&mut types, &mut names, &[("a", ch)]);
557
558        // One byte of member and fifteen bytes of nothing, since the record is one byte long
559        // and the granule is the rest of the way to sixteen.
560        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
561        assert_eq!(tally.bytes, 1);
562        assert_eq!(tally.padding, 0);
563        assert_eq!(tally.uniform, 1);
564    }
565
566    #[test]
567    fn padding_between_members_is_counted_and_does_not_make_a_granule_disagree() {
568        let mut types = Types::new();
569        let mut names = Interner::new();
570        let ch = types.int(IntKind::Char);
571        let long = types.int(IntKind::Long);
572        let inner = built(&mut types, &mut names, &[("c", ch)]);
573        let inner = types.record(inner);
574        let id = built(&mut types, &mut names, &[("a", ch), ("b", long), ("c", inner)]);
575
576        // `char` at 0, seven bytes of padding, `long` at 8, `char` at 16. The first granule
577        // holds a `char` and a `long` so it disagrees; the second holds one `char` and fifteen
578        // bytes of padding, so it does not.
579        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
580        assert_eq!(tally.bytes, 24);
581        assert_eq!(tally.padding, 7 + 7);
582        assert_eq!(tally.mixed, 1);
583        assert_eq!(tally.uniform, 1);
584    }
585
586    #[test]
587    fn an_array_paints_every_element_and_stays_one_type() {
588        let mut types = Types::new();
589        let mut names = Interner::new();
590        let int = types.int(IntKind::Int);
591        let array = types.array(int, ArrayLen::Fixed(16));
592        let id = built(&mut types, &mut names, &[("a", array)]);
593
594        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
595        assert_eq!(tally.bytes, 64);
596        assert_eq!(tally.uniform, 4);
597        assert_eq!(tally.mixed, 0);
598    }
599
600    #[test]
601    fn a_union_is_a_choice_and_not_a_coexistence() {
602        let mut types = Types::new();
603        let mut names = Interner::new();
604        let long = types.int(IntKind::Long);
605        let double = types.float(crate::kind::FloatKind::Double);
606        let fields = [FieldDecl::new(Some(names.intern("i")), long), FieldDecl::new(None, double)];
607        let id = types.declare_record(RecordKind::Union, None);
608        let laid_out =
609            layout_record(&types, RecordKind::Union, &fields, &RecordOptions::default(), &target())
610                .expect("a union with a layout");
611        types.complete_record(id, laid_out);
612
613        // Eight bytes holding a `long` or eight bytes holding a `double`, never both, so the
614        // granule holds one type either way and the plane needs no side table for it.
615        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
616        assert_eq!(tally.bytes, 8);
617        assert_eq!(tally.mixed, 0);
618        assert_eq!(tally.uniform, 1);
619    }
620
621    #[test]
622    fn a_union_sharing_a_granule_with_a_member_of_another_type_does_disagree() {
623        let mut types = Types::new();
624        let mut names = Interner::new();
625        let long = types.int(IntKind::Long);
626        let double = types.float(crate::kind::FloatKind::Double);
627        let members = [FieldDecl::new(Some(names.intern("i")), long), FieldDecl::new(None, double)];
628        let inner = types.declare_record(RecordKind::Union, None);
629        let laid_out = layout_record(
630            &types,
631            RecordKind::Union,
632            &members,
633            &RecordOptions::default(),
634            &target(),
635        )
636        .expect("a union with a layout");
637        types.complete_record(inner, laid_out);
638        let inner = types.record(inner);
639        let int = types.int(IntKind::Int);
640        let id = built(&mut types, &mut names, &[("u", inner), ("n", int)]);
641
642        // Whichever member the union holds, the `int` after it is a second type in the same
643        // sixteen bytes, so both layouts disagree and so does the granule.
644        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
645        assert_eq!(tally.bytes, 16);
646        assert_eq!(tally.mixed, 1);
647    }
648
649    #[test]
650    fn two_pointers_to_different_things_agree_only_under_the_looser_keying() {
651        let mut types = Types::new();
652        let mut names = Interner::new();
653        let ch = types.int(IntKind::Char);
654        let int = types.int(IntKind::Int);
655        let to_char = types.pointer(ch);
656        let to_int = types.pointer(int);
657        let id = built(&mut types, &mut names, &[("a", to_char), ("b", to_int)]);
658
659        let exact = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
660        assert_eq!(exact.mixed, 1);
661        let loose = measure(&types, id, &target(), Keying::PointersTogether, 16)
662            .expect("a complete record");
663        assert_eq!(loose.mixed, 0);
664        assert_eq!(loose.uniform, 1);
665    }
666
667    #[test]
668    fn an_enumeration_agrees_with_the_integer_it_is_represented_in() {
669        let mut types = Types::new();
670        let mut names = Interner::new();
671        let int = types.int(IntKind::Int);
672        let enumeration = types.declare_enum(None);
673        types.complete_enum(enumeration, int, false);
674        let enumeration = types.enumeration(enumeration);
675        let id = built(&mut types, &mut names, &[("a", int), ("b", enumeration)]);
676
677        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
678        assert_eq!(tally.mixed, 0);
679        assert_eq!(tally.uniform, 1);
680    }
681
682    #[test]
683    fn a_flexible_array_member_paints_nothing_because_it_occupies_nothing() {
684        let mut types = Types::new();
685        let mut names = Interner::new();
686        let long = types.int(IntKind::Long);
687        let ch = types.int(IntKind::Char);
688        let flexible = types.array(ch, ArrayLen::Unknown);
689        let id = built(&mut types, &mut names, &[("a", long), ("rest", flexible)]);
690
691        let tally = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
692        assert_eq!(tally.bytes, 8);
693        assert_eq!(tally.uniform, 1);
694        assert_eq!(tally.mixed, 0);
695    }
696
697    #[test]
698    fn the_ratio_is_a_quarter_when_nothing_disagrees_and_four_when_everything_does() {
699        let none = Tally { uniform: 4, ..Tally::default() };
700        assert!((none.ratio(16) - 0.25).abs() < 1e-9);
701        let all = Tally { mixed: 4, ..Tally::default() };
702        assert!((all.ratio(16) - 4.25).abs() < 1e-9);
703        // At sixteen the budget of 1.25 bytes per byte is spent exactly by a quarter of the
704        // granules disagreeing. At eight the entry costs twice as much per byte, so the same
705        // budget only pays for three sixteenths of them.
706        let budget = Tally { uniform: 3, mixed: 1, ..Tally::default() };
707        assert!((budget.ratio(16) - 1.25).abs() < 1e-9);
708        let budget = Tally { uniform: 13, mixed: 3, ..Tally::default() };
709        assert!((budget.ratio(8) - 1.25).abs() < 1e-9);
710    }
711
712    #[test]
713    fn the_default_granule_is_eight_because_a_pointer_and_two_ints_fit_in_sixteen() {
714        // The whole finding in one record. `struct { char *p; int a; int b; }` is an ordinary
715        // shape and it disagrees at sixteen bytes and agrees at eight, which is why the
716        // measurement moved the granule and why the default is what it is.
717        let mut types = Types::new();
718        let mut names = Interner::new();
719        let ch = types.int(IntKind::Char);
720        let int = types.int(IntKind::Int);
721        let to_char = types.pointer(ch);
722        let id = built(&mut types, &mut names, &[("p", to_char), ("a", int), ("b", int)]);
723
724        let wide = measure(&types, id, &target(), Keying::Exact, 16).expect("a complete record");
725        assert_eq!(wide.mixed, 1);
726        assert_eq!(wide.uniform, 0);
727
728        assert_eq!(GRANULE, 8);
729        let tally =
730            measure(&types, id, &target(), Keying::Exact, GRANULE).expect("a complete record");
731        assert_eq!(tally.mixed, 0);
732        assert_eq!(tally.uniform, 2);
733    }
734}