1use 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
35pub const GRANULE: u64 = 8;
43
44const SIZES: &[u64] = &[1, 4, 8, 16, 32, 64];
49
50const LIMIT: u64 = 1 << 20;
57
58const LAYOUTS: usize = 32;
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum Keying {
72 Exact,
81 PointersTogether,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92enum Cell {
93 Pad,
96 One(TypeKind),
98 Mixed,
101}
102
103impl Cell {
104 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
119pub struct Tally {
120 pub records: u64,
122 pub skipped: u64,
124 pub bytes: u64,
126 pub padding: u64,
128 pub uniform: u64,
130 pub mixed: u64,
132 pub blank: u64,
134}
135
136impl Tally {
137 #[must_use]
139 pub fn granules(&self) -> u64 {
140 self.uniform + self.mixed + self.blank
141 }
142
143 #[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 #[must_use]
161 pub fn ratio(&self, granule: u64) -> f64 {
162 4.0 / granule as f64 + 4.0 * self.disagreeing()
163 }
164
165 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#[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 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
226fn 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#[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#[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
329fn 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 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 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
376fn 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
423fn 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 Some(0) => {}
436 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
448fn 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
476fn key(types: &Types, ty: TypeId, keying: Keying) -> TypeKind {
478 let kind = types.kind(types.canonical(ty));
479 match kind {
480 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 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 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 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 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 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 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 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}