1use celox_design::{
7 AbsoluteAddrBase, RegionedAbsoluteAddrBase, RuntimeEventSite, SPARSE_WORKING_REGION,
8 STABLE_REGION,
9};
10use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
11use serde::{Deserialize, Serialize};
12use std::hash::Hash;
13
14mod trace;
15pub use trace::{TRACE_GROUP_BYTES, TraceLayout};
16
17pub const RUNTIME_EVENT_CAPACITY: usize = 1024;
18pub const RUNTIME_EVENT_WRITING: u64 = u64::MAX;
19pub const STATE_HEADER_SIZE: usize = 40;
20pub const STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET: usize = 0;
21#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
23pub const STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET: usize = 8;
24#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
25pub const STATE_HEADER_NATIVE_LOOP_EVENT_SEQ_OFFSET: usize = 24;
26#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
27pub const STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET: usize = 16;
28pub const STATE_HEADER_EXTERN_FUNCTIONS_ADDR_OFFSET: usize = 32;
30pub const RUNTIME_EVENT_HEADER_SIZE: usize = 8;
32pub const RUNTIME_EVENT_SLOT_SEQ_OFFSET: usize = 0;
33pub const RUNTIME_EVENT_SLOT_SITE_OFFSET: usize = 8;
34pub const RUNTIME_EVENT_SLOT_ARG_COUNT_OFFSET: usize = 16;
35pub const RUNTIME_EVENT_SLOT_PAYLOAD_OFFSET: usize = 24;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct RuntimeEventArgLayout {
39 pub value_word_offset: usize,
40 pub mask_word_offset: usize,
41 pub word_count: usize,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct RuntimeEventSiteLayout {
46 pub args: Vec<RuntimeEventArgLayout>,
47 pub payload_words: usize,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct SparseWorkingLayout {
52 pub active_index: usize,
53 pub chunk_count: usize,
54 pub dirty_words_offset: usize,
55 pub dirty_word_count: usize,
56 pub summary_words_offset: usize,
57 pub summary_word_count: usize,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61pub enum MemoryLayoutMode {
62 Packed,
63 ElementStrided,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67pub struct UnpackedArrayLayout {
68 pub element_width: usize,
69 pub element_count: usize,
70 pub element_stride: usize,
71 pub plane_size: usize,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct StateObjectLayout<A> {
77 pub address: A,
78 pub width: usize,
79 pub is_4state: bool,
80}
81
82#[derive(Debug, Clone)]
89pub struct LayoutRequirements<A> {
90 state_aliases: HashMap<A, A>,
91}
92
93impl<A> Default for LayoutRequirements<A> {
94 fn default() -> Self {
95 Self {
96 state_aliases: HashMap::default(),
97 }
98 }
99}
100
101impl<A> LayoutRequirements<A> {
102 pub fn state_aliases(&self) -> &HashMap<A, A> {
103 &self.state_aliases
104 }
105
106 pub fn state_aliases_mut(&mut self) -> &mut HashMap<A, A> {
107 &mut self.state_aliases
108 }
109
110 pub fn is_empty(&self) -> bool {
111 self.state_aliases.is_empty()
112 }
113
114 pub fn clear(&mut self) {
115 self.state_aliases.clear();
116 }
117}
118
119pub const LANE_SEGMENT_ALIGN: usize = 256;
123pub const LANE_SEGMENT_GUARD: usize = 16;
128
129#[derive(Debug, Clone)]
132pub struct LaneWriters<A> {
133 pub stable: HashMap<A, u64>,
134 pub working: HashMap<A, u64>,
135 pub sparse: HashMap<A, u64>,
136}
137
138impl<A> Default for LaneWriters<A> {
139 fn default() -> Self {
140 Self {
141 stable: HashMap::default(),
142 working: HashMap::default(),
143 sparse: HashMap::default(),
144 }
145 }
146}
147
148impl<A> LaneWriters<A> {
149 pub fn is_empty(&self) -> bool {
150 self.stable.is_empty() && self.working.is_empty() && self.sparse.is_empty()
151 }
152}
153
154#[derive(Debug, Clone)]
160pub struct LayoutInput<A> {
161 pub state_objects: Vec<StateObjectLayout<A>>,
162 pub working_addresses: Vec<A>,
163 pub sparse_addresses: Vec<A>,
164 pub unpacked_arrays: HashMap<A, UnpackedArrayLayout>,
165 pub requirements: LayoutRequirements<A>,
166 pub ff_referenced_addresses: HashSet<A>,
167 pub num_events: usize,
168 pub runtime_event_sites: Vec<RuntimeEventSite>,
169 pub lane_writers: LaneWriters<A>,
173 pub access_rank: HashMap<A, u32>,
177}
178
179pub trait LayoutSource<A> {
181 fn layout_input(&self, mode: MemoryLayoutMode) -> LayoutInput<A>;
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(bound(
186 serialize = "A: Serialize + Eq + Hash",
187 deserialize = "A: Deserialize<'de> + Eq + Hash"
188))]
189pub struct MemoryLayout<A> {
190 pub trace: Option<TraceLayout>,
192 pub four_state: bool,
193 pub mode: MemoryLayoutMode,
194 pub offsets: HashMap<A, usize>,
196 pub widths: HashMap<A, usize>,
197 pub is_4states: HashMap<A, bool>,
199 pub unpacked_arrays: HashMap<A, UnpackedArrayLayout>,
200 pub total_size: usize,
202
203 pub working_offsets: HashMap<A, usize>,
205 pub working_base_offset: usize,
206 pub sparse_offsets: HashMap<A, usize>,
208 pub sparse_base_offset: usize,
209 pub sparse_layouts: HashMap<A, SparseWorkingLayout>,
210 pub sparse_active_bits_offset: usize,
211 pub sparse_active_capacity: usize,
212 pub merged_total_size: usize,
213
214 pub triggered_bits_offset: usize,
215 pub triggered_bits_total_size: usize,
216
217 pub scratch_base_offset: usize,
218 pub scratch_size: usize,
219
220 pub runtime_event_capacity: usize,
221 pub runtime_event_slot_size: usize,
222 pub runtime_event_buffer_size: usize,
223 pub runtime_event_site_layouts: Vec<RuntimeEventSiteLayout>,
224}
225
226type PhysicalLayoutObject<A> = (A, usize, bool, usize, usize);
227
228const LARGE_OBJECT_BYTES: usize = 256;
232
233const HOLE_REACH: usize = 128;
237
238fn sort_layout_objects<A: Copy + Ord + Hash>(
239 objects: &mut [PhysicalLayoutObject<A>],
240 access_rank: &HashMap<A, u32>,
241) {
242 objects.sort_unstable_by_key(|&(address, _, _, size, alignment)| {
249 match access_rank.get(&address) {
250 Some(&rank) => (u8::from(size > LARGE_OBJECT_BYTES), rank, 0, address),
251 None => (2, 0, usize::MAX - alignment, address),
252 }
253 });
254}
255
256fn place_region<A: Copy + Eq + Hash>(
269 objects: Vec<PhysicalLayoutObject<A>>,
270 writers: &HashMap<A, u64>,
271 start: usize,
272 mut extent: impl FnMut(&PhysicalLayoutObject<A>) -> usize,
273 mut place: impl FnMut(&PhysicalLayoutObject<A>, usize),
274) -> usize {
275 let mut common = Vec::new();
276 let mut lanes: Vec<Vec<PhysicalLayoutObject<A>>> = Vec::new();
277 let mut isolated = Vec::new();
278 for object in objects {
279 match writers.get(&object.0).copied().unwrap_or(0) {
280 0 => common.push(object),
281 mask if mask.is_power_of_two() => {
282 let lane = mask.trailing_zeros() as usize;
283 if lanes.len() <= lane {
284 lanes.resize_with(lane + 1, Vec::new);
285 }
286 lanes[lane].push(object);
287 }
288 _ => isolated.push(object),
289 }
290 }
291 let mut current = start;
292 let mut place_run = |current: &mut usize, run: Vec<PhysicalLayoutObject<A>>| {
293 let mut holes: Vec<(usize, usize)> = Vec::new();
294 for object in run {
295 let size = extent(&object);
296 let alignment = object.4;
297 let reach = current.saturating_sub(HOLE_REACH);
298 holes.retain(|&(_, end)| end > reach);
299 let hole = holes
300 .iter()
301 .position(|&(begin, end)| align_up(begin, alignment) + size <= end);
302 let offset = if let Some(index) = hole {
303 let (begin, end) = holes.swap_remove(index);
304 let offset = align_up(begin, alignment);
305 if offset > begin {
306 holes.push((begin, offset));
307 }
308 if offset + size < end {
309 holes.push((offset + size, end));
310 }
311 offset
312 } else {
313 let offset = align_up(*current, alignment);
314 if offset > *current {
315 holes.push((*current, offset));
316 }
317 *current = offset + size;
318 offset
319 };
320 place(&object, offset);
321 }
322 };
323 place_run(&mut current, common);
324 let segments = lanes
325 .into_iter()
326 .filter(|run| !run.is_empty())
327 .chain(isolated.into_iter().map(|object| vec![object]))
328 .collect::<Vec<_>>();
329 if segments.is_empty() {
330 return current;
331 }
332 for run in segments {
333 current = align_up(current + LANE_SEGMENT_GUARD, LANE_SEGMENT_ALIGN);
334 place_run(&mut current, run);
335 }
336 align_up(current + LANE_SEGMENT_GUARD, LANE_SEGMENT_ALIGN)
337}
338
339impl<A> MemoryLayout<A>
340where
341 A: Copy + Eq + Hash + Ord,
342{
343 pub fn build<S>(source: &S, four_state: bool, mode: MemoryLayoutMode) -> Self
344 where
345 S: LayoutSource<A>,
346 {
347 let input = source.layout_input(mode);
348 let LayoutInput {
349 state_objects,
350 working_addresses,
351 sparse_addresses,
352 unpacked_arrays,
353 requirements,
354 ff_referenced_addresses,
355 num_events,
356 runtime_event_sites,
357 lane_writers,
358 access_rank,
359 } = input;
360
361 let mut stable_objects = state_objects
362 .into_iter()
363 .map(|object| {
364 let size = unpacked_arrays
365 .get(&object.address)
366 .map(|layout| layout.plane_size)
367 .unwrap_or_else(|| get_byte_size(object.width));
368 let alignment = unpacked_arrays
369 .get(&object.address)
370 .map(|layout| layout.element_stride.min(8))
371 .unwrap_or_else(|| get_alignment(object.width));
372 (
373 object.address,
374 object.width,
375 object.is_4state,
376 size,
377 alignment,
378 )
379 })
380 .collect::<Vec<_>>();
381 sort_layout_objects(&mut stable_objects, &access_rank);
382
383 let mut offsets = HashMap::default();
384 let mut widths = HashMap::default();
385 let mut is_4states = HashMap::default();
386 let runtime_event_site_layouts = build_runtime_event_site_layouts(&runtime_event_sites);
387 let runtime_event_slot_size = RUNTIME_EVENT_SLOT_PAYLOAD_OFFSET
388 + runtime_event_site_layouts
389 .iter()
390 .map(|site| site.payload_words)
391 .max()
392 .unwrap_or(0)
393 * 8;
394
395 let plane_count = if four_state { 2 } else { 1 };
396 let current_offset = place_region(
397 stable_objects,
398 &lane_writers.stable,
399 STATE_HEADER_SIZE,
400 |object| object.3 * plane_count,
401 |&(address, width, is_4state, _, _), offset| {
402 offsets.insert(address, offset);
403 widths.insert(address, width);
404 is_4states.insert(address, is_4state);
405 },
406 );
407
408 let mut working_objects = working_addresses
409 .iter()
410 .map(|address| {
411 let width = widths[address];
412 let size = unpacked_arrays
413 .get(address)
414 .map(|layout| layout.plane_size)
415 .unwrap_or_else(|| get_byte_size(width));
416 let alignment = unpacked_arrays
417 .get(address)
418 .map(|layout| layout.element_stride.min(8))
419 .unwrap_or_else(|| get_alignment(width));
420 (*address, width, is_4states[address], size, alignment)
421 })
422 .collect::<Vec<_>>();
423 sort_layout_objects(&mut working_objects, &access_rank);
424
425 let mut working_offsets = HashMap::default();
426 let working_size = place_region(
429 working_objects,
430 &lane_writers.working,
431 0,
432 |object| object.3 * plane_count,
433 |&(address, ..), offset| {
434 working_offsets.insert(address, offset);
435 },
436 );
437
438 let mut sparse_objects = sparse_addresses
439 .iter()
440 .map(|address| {
441 let width = widths[address];
442 let size = unpacked_arrays
443 .get(address)
444 .map(|layout| layout.plane_size)
445 .unwrap_or_else(|| get_byte_size(width));
446 let alignment = unpacked_arrays
447 .get(address)
448 .map(|layout| layout.element_stride.min(8))
449 .unwrap_or_else(|| get_alignment(width));
450 (*address, width, is_4states[address], size, alignment)
451 })
452 .collect::<Vec<_>>();
453 sort_layout_objects(&mut sparse_objects, &access_rank);
454
455 let mut sparse_offsets = HashMap::default();
456 let sparse_size = place_region(
457 sparse_objects,
458 &lane_writers.sparse,
459 0,
460 |&(_, _, _, size, _)| {
461 let final_chunk_size = align_up(size, 8);
462 let physical_extent = (plane_count - 1) * size + final_chunk_size;
463 align_up(physical_extent, 8)
464 },
465 |&(address, ..), offset| {
466 sparse_offsets.insert(address, offset);
467 },
468 );
469
470 let region_alignment = if lane_writers.is_empty() {
472 8
473 } else {
474 LANE_SEGMENT_ALIGN
475 };
476 let working_base_offset = align_up(current_offset, region_alignment);
477 let sparse_base_offset = align_up(working_base_offset + working_size, region_alignment);
478 let mut sparse_metadata_offset = align_up(sparse_base_offset + sparse_size, 8);
479 let mut sparse_layouts = HashMap::default();
480 let mut sparse_order = sparse_addresses;
481 let sparse_lane = |address: &A| {
484 lane_writers
485 .sparse
486 .get(address)
487 .copied()
488 .filter(|mask| mask.is_power_of_two())
489 .map_or(u32::MAX, u64::trailing_zeros)
490 };
491 sparse_order.sort_unstable_by_key(|address| (sparse_lane(address), *address));
492 let mut previous_sparse_lane = None;
493 let sparse_active_capacity = sparse_order.len();
494 for (active_index, address) in sparse_order.into_iter().enumerate() {
495 let lane = sparse_lane(&address);
496 if !lane_writers.is_empty() && previous_sparse_lane != Some(lane) {
497 sparse_metadata_offset = align_up(sparse_metadata_offset, LANE_SEGMENT_ALIGN);
498 previous_sparse_lane = Some(lane);
499 }
500 let chunk_count = unpacked_arrays
501 .get(&address)
502 .map(|layout| layout.plane_size.div_ceil(8))
503 .unwrap_or_else(|| widths[&address].div_ceil(64));
504 let dirty_word_count = chunk_count.div_ceil(64);
505 let summary_word_count = dirty_word_count.div_ceil(64);
506 let dirty_words_offset = sparse_metadata_offset;
507 sparse_metadata_offset += dirty_word_count * 8;
508 let summary_words_offset = sparse_metadata_offset;
509 sparse_metadata_offset += summary_word_count * 8;
510 sparse_layouts.insert(
511 address,
512 SparseWorkingLayout {
513 active_index,
514 chunk_count,
515 dirty_words_offset,
516 dirty_word_count,
517 summary_words_offset,
518 summary_word_count,
519 },
520 );
521 }
522
523 let sparse_active_bits_offset = align_up(sparse_metadata_offset, 8);
524 sparse_metadata_offset =
525 sparse_active_bits_offset + sparse_active_capacity.div_ceil(64) * 8;
526 let triggered_bits_offset = align_up(sparse_metadata_offset, 8);
527 let triggered_bits_total_size = num_events.div_ceil(8);
528 let scratch_base_offset = align_up(triggered_bits_offset + triggered_bits_total_size, 8);
529 let runtime_event_buffer_size =
530 RUNTIME_EVENT_HEADER_SIZE + RUNTIME_EVENT_CAPACITY * runtime_event_slot_size;
531 let merged_total_size = scratch_base_offset;
532
533 let mut address_aliases = requirements.state_aliases.into_iter().collect::<Vec<_>>();
534 address_aliases.sort_unstable();
535 for (alias, canonical) in address_aliases {
536 let fourstate_ok = !four_state
537 || (is_4states.get(&alias) == Some(&false)
538 && is_4states.get(&canonical) == Some(&false));
539 let alias_fits = widths
540 .get(&alias)
541 .zip(widths.get(&canonical))
542 .is_some_and(|(&alias_width, &canonical_width)| alias_width <= canonical_width);
543 if fourstate_ok
544 && alias_fits
545 && !ff_referenced_addresses.contains(&alias)
546 && let Some(&canonical_offset) = offsets.get(&canonical)
547 {
548 offsets.insert(alias, canonical_offset);
549 }
550 }
551
552 Self {
553 trace: None,
554 four_state,
555 mode,
556 offsets,
557 widths,
558 is_4states,
559 unpacked_arrays,
560 total_size: current_offset,
561 working_offsets,
562 working_base_offset,
563 sparse_offsets,
564 sparse_base_offset,
565 sparse_layouts,
566 sparse_active_bits_offset,
567 sparse_active_capacity,
568 merged_total_size,
569 triggered_bits_offset,
570 triggered_bits_total_size,
571 scratch_base_offset,
572 scratch_size: 0,
573 runtime_event_capacity: RUNTIME_EVENT_CAPACITY,
574 runtime_event_slot_size,
575 runtime_event_buffer_size,
576 runtime_event_site_layouts,
577 }
578 }
579
580 pub fn with_backend_scratch(mut self, scratch_size: usize) -> Self {
584 self.scratch_size = scratch_size;
585 self.merged_total_size = align_up(self.scratch_base_offset + scratch_size, 8);
586 self
587 }
588
589 pub fn enable_trace(&mut self) {
592 if self.trace.is_some() {
593 return;
594 }
595 assert_eq!(
596 self.scratch_size, 0,
597 "trace must be enabled before backend planning"
598 );
599 let homes = self
600 .offsets
601 .iter()
602 .map(|(address, &offset)| {
603 let planes = if self.four_state { 2 } else { 1 };
604 (offset, offset + self.plane_size(address) * planes)
605 })
606 .collect();
607 let trace = TraceLayout::new(self.scratch_base_offset, self.total_size, homes);
608 self.scratch_base_offset = align_up(trace.end_offset(), 8);
609 self.merged_total_size = self.scratch_base_offset;
610 self.trace = Some(trace);
611 }
612
613 pub fn trace_notification_offsets<R: RegionedAddress<A>>(
614 &self,
615 address: &R,
616 ) -> Option<[usize; 2]> {
617 if address.region() != STABLE_REGION {
618 return None;
619 }
620 let trace = self.trace.as_ref()?;
621 Some(trace.notification_offsets(self.offsets[&address.absolute_address()]))
622 }
623
624 pub fn plane_size(&self, address: &A) -> usize {
625 self.unpacked_arrays
626 .get(address)
627 .map(|layout| layout.plane_size)
628 .unwrap_or_else(|| get_byte_size(self.widths[address]))
629 }
630
631 pub fn region_base_offset<R>(&self, address: &R) -> usize
632 where
633 R: RegionedAddress<A>,
634 {
635 let absolute = address.absolute_address();
636 match address.region() {
637 STABLE_REGION => self.offsets[&absolute],
638 SPARSE_WORKING_REGION => self.sparse_base_offset + self.sparse_offsets[&absolute],
639 _ => self.working_base_offset + self.working_offsets[&absolute],
640 }
641 }
642
643 pub fn map_static_bit_offset(&self, address: &A, bit_offset: usize) -> (usize, usize) {
644 let Some(array) = self.unpacked_arrays.get(address) else {
645 return (bit_offset / 8, bit_offset % 8);
646 };
647 let element = bit_offset / array.element_width;
648 let intra_element = bit_offset % array.element_width;
649 (
650 element * array.element_stride + intra_element / 8,
651 intra_element % 8,
652 )
653 }
654
655 pub fn regioned_static_byte_and_intra<R>(
656 &self,
657 address: &R,
658 bit_offset: usize,
659 ) -> Option<(i32, usize)>
660 where
661 R: RegionedAddress<A>,
662 {
663 let absolute = address.absolute_address();
664 let base = match address.region() {
665 STABLE_REGION => *self.offsets.get(&absolute).unwrap_or(&0),
666 SPARSE_WORKING_REGION => {
667 self.sparse_base_offset + *self.sparse_offsets.get(&absolute).unwrap_or(&0)
668 }
669 _ => self.working_base_offset + *self.working_offsets.get(&absolute).unwrap_or(&0),
670 };
671 let (byte, intra) = self.map_static_bit_offset(&absolute, bit_offset);
672 Some((i32::try_from(base.checked_add(byte)?).ok()?, intra))
673 }
674}
675
676pub trait RegionedAddress<A> {
677 fn region(&self) -> u32;
678 fn absolute_address(&self) -> A;
679}
680
681impl<V: Copy> RegionedAddress<AbsoluteAddrBase<V>> for RegionedAbsoluteAddrBase<V> {
682 fn region(&self) -> u32 {
683 self.region
684 }
685
686 fn absolute_address(&self) -> AbsoluteAddrBase<V> {
687 self.absolute_addr()
688 }
689}
690
691fn build_runtime_event_site_layouts(sites: &[RuntimeEventSite]) -> Vec<RuntimeEventSiteLayout> {
692 sites
693 .iter()
694 .map(|site| {
695 let mut payload_words = 0;
696 let args = site
697 .arg_widths
698 .iter()
699 .map(|width| {
700 let word_count = (*width).div_ceil(64).max(1);
701 let value_word_offset = payload_words;
702 payload_words += word_count;
703 let mask_word_offset = payload_words;
704 payload_words += word_count;
705 RuntimeEventArgLayout {
706 value_word_offset,
707 mask_word_offset,
708 word_count,
709 }
710 })
711 .collect();
712 RuntimeEventSiteLayout {
713 args,
714 payload_words,
715 }
716 })
717 .collect()
718}
719
720const fn align_up(offset: usize, alignment: usize) -> usize {
721 (offset + alignment - 1) & !(alignment - 1)
722}
723
724fn get_alignment(width: usize) -> usize {
725 let size = get_byte_size(width);
726 if size == 0 {
727 1
728 } else if size <= 8 {
729 size.next_power_of_two()
730 } else {
731 8
732 }
733}
734
735pub const fn get_byte_size(width: usize) -> usize {
736 width.div_ceil(8)
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 #[test]
744 fn byte_size_rounds_up_partial_bytes() {
745 assert_eq!(get_byte_size(0), 0);
746 assert_eq!(get_byte_size(1), 1);
747 assert_eq!(get_byte_size(8), 1);
748 assert_eq!(get_byte_size(9), 2);
749 }
750
751 #[test]
752 fn layout_order_is_independent_of_input_iteration_order() {
753 let high_address = (3u32, 64, false, 8, 8);
754 let low_address = (1u32, 64, false, 8, 8);
755 let less_aligned = (0u32, 32, false, 4, 4);
756 let mut forward = vec![high_address, less_aligned, low_address];
757 let mut reverse = forward.iter().copied().rev().collect::<Vec<_>>();
758
759 sort_layout_objects(&mut forward, &HashMap::default());
760 sort_layout_objects(&mut reverse, &HashMap::default());
761
762 assert_eq!(forward, reverse);
763 assert_eq!(forward, vec![low_address, high_address, less_aligned]);
764 }
765
766 #[test]
767 fn layout_requirements_own_semantic_aliases_until_layout() {
768 let mut requirements = LayoutRequirements::default();
769 requirements.state_aliases_mut().insert(2u32, 1u32);
770
771 assert_eq!(requirements.state_aliases().get(&2), Some(&1));
772 assert!(!requirements.is_empty());
773
774 requirements.clear();
775 assert!(requirements.is_empty());
776 }
777
778 #[test]
779 fn layout_applies_aliases_from_requirements() {
780 struct AliasLayoutSource;
781
782 impl LayoutSource<u32> for AliasLayoutSource {
783 fn layout_input(&self, _mode: MemoryLayoutMode) -> LayoutInput<u32> {
784 let mut requirements = LayoutRequirements::default();
785 requirements.state_aliases_mut().insert(2, 1);
786 LayoutInput {
787 state_objects: vec![
788 StateObjectLayout {
789 address: 1,
790 width: 8,
791 is_4state: false,
792 },
793 StateObjectLayout {
794 address: 2,
795 width: 8,
796 is_4state: false,
797 },
798 ],
799 working_addresses: Vec::new(),
800 sparse_addresses: Vec::new(),
801 unpacked_arrays: HashMap::default(),
802 requirements,
803 ff_referenced_addresses: HashSet::default(),
804 num_events: 0,
805 runtime_event_sites: Vec::new(),
806 lane_writers: Default::default(),
807 access_rank: Default::default(),
808 }
809 }
810 }
811
812 let layout = MemoryLayout::build(&AliasLayoutSource, false, MemoryLayoutMode::Packed);
813 assert_eq!(layout.offsets[&1], layout.offsets[&2]);
814 }
815
816 #[test]
817 fn access_rank_orders_homes_and_fills_padding_nearby() {
818 struct RankedLayoutSource;
819
820 impl LayoutSource<u32> for RankedLayoutSource {
821 fn layout_input(&self, _mode: MemoryLayoutMode) -> LayoutInput<u32> {
822 let object = |address, width| StateObjectLayout {
823 address,
824 width,
825 is_4state: false,
826 };
827 let access_rank = [(4, 0), (2, 1), (1, 2), (5, 3), (3, 4)]
830 .into_iter()
831 .collect();
832 LayoutInput {
833 state_objects: vec![
834 object(1, 8),
835 object(2, 64),
836 object(3, 64),
837 object(4, 8),
838 object(5, 8 * (LARGE_OBJECT_BYTES + 8)),
839 object(6, 64),
840 ],
841 working_addresses: Vec::new(),
842 sparse_addresses: Vec::new(),
843 unpacked_arrays: HashMap::default(),
844 requirements: LayoutRequirements::default(),
845 ff_referenced_addresses: HashSet::default(),
846 num_events: 0,
847 runtime_event_sites: Vec::new(),
848 lane_writers: Default::default(),
849 access_rank,
850 }
851 }
852 }
853
854 let layout = MemoryLayout::build(&RankedLayoutSource, false, MemoryLayoutMode::Packed);
855 let offset = |address: u32| layout.offsets[&address];
856 assert_eq!(offset(4), STATE_HEADER_SIZE);
859 assert_eq!(offset(2), STATE_HEADER_SIZE + 8);
860 assert_eq!(offset(1), STATE_HEADER_SIZE + 1);
861 assert_eq!(offset(3), STATE_HEADER_SIZE + 16);
862 assert!(offset(5) > offset(3));
865 assert!(offset(6) > offset(5));
866 }
867
868 #[test]
869 fn lane_writers_separate_homes_into_guarded_segments() {
870 struct LaneLayoutSource;
871
872 impl LayoutSource<u32> for LaneLayoutSource {
873 fn layout_input(&self, _mode: MemoryLayoutMode) -> LayoutInput<u32> {
874 let mut lane_writers = LaneWriters::default();
875 lane_writers.stable.insert(1, 0b01);
877 lane_writers.stable.insert(2, 0b10);
878 lane_writers.stable.insert(3, 0b11);
879 lane_writers.working.insert(5, 0b10);
880 let object = |address, width| StateObjectLayout {
881 address,
882 width,
883 is_4state: false,
884 };
885 LayoutInput {
886 state_objects: vec![
887 object(1, 24),
888 object(2, 24),
889 object(3, 8),
890 object(4, 8),
891 object(5, 40),
892 ],
893 working_addresses: vec![5],
894 sparse_addresses: Vec::new(),
895 unpacked_arrays: HashMap::default(),
896 requirements: LayoutRequirements::default(),
897 ff_referenced_addresses: HashSet::default(),
898 num_events: 0,
899 runtime_event_sites: Vec::new(),
900 lane_writers,
901 access_rank: Default::default(),
902 }
903 }
904 }
905
906 let layout = MemoryLayout::build(&LaneLayoutSource, false, MemoryLayoutMode::Packed);
907 let span = |address: u32| {
908 let start = layout.offsets[&address];
909 (start, start + layout.plane_size(&address))
910 };
911 assert!(span(4).0 < span(1).0 && span(5).0 < span(1).0);
913 for address in [1, 2, 3] {
914 assert_eq!(span(address).0 % LANE_SEGMENT_ALIGN, 0);
915 }
916 let mut segments = [span(1), span(2), span(3)];
918 segments.sort_unstable();
919 for pair in segments.windows(2) {
920 assert!(pair[0].1 + LANE_SEGMENT_GUARD <= pair[1].0, "{segments:?}");
921 }
922 assert!(segments[2].1 + LANE_SEGMENT_GUARD <= layout.working_base_offset);
924 assert_eq!(layout.working_base_offset % LANE_SEGMENT_ALIGN, 0);
925 assert_eq!(layout.working_offsets[&5] % LANE_SEGMENT_ALIGN, 0);
926 }
927
928 #[test]
929 fn layout_without_lane_writers_is_unchanged_by_partitioning_support() {
930 struct PackedSource;
931
932 impl LayoutSource<u32> for PackedSource {
933 fn layout_input(&self, _mode: MemoryLayoutMode) -> LayoutInput<u32> {
934 LayoutInput {
935 state_objects: (0..6)
936 .map(|address| StateObjectLayout {
937 address,
938 width: 8 + 7 * address as usize,
939 is_4state: false,
940 })
941 .collect(),
942 working_addresses: vec![1, 3],
943 sparse_addresses: vec![2],
944 unpacked_arrays: HashMap::default(),
945 requirements: LayoutRequirements::default(),
946 ff_referenced_addresses: HashSet::default(),
947 num_events: 9,
948 runtime_event_sites: Vec::new(),
949 lane_writers: LaneWriters::default(),
950 access_rank: Default::default(),
951 }
952 }
953 }
954
955 let layout = MemoryLayout::build(&PackedSource, false, MemoryLayoutMode::Packed);
956 let end = layout
959 .offsets
960 .keys()
961 .map(|address| layout.offsets[address] + layout.plane_size(address))
962 .max()
963 .unwrap();
964 assert_eq!(layout.total_size, end);
965 assert_eq!(layout.working_base_offset, align_up(end, 8));
966 }
967
968 #[test]
969 fn backend_scratch_only_extends_the_final_layout_region() {
970 struct EmptyLayoutSource;
971
972 impl LayoutSource<u32> for EmptyLayoutSource {
973 fn layout_input(&self, _mode: MemoryLayoutMode) -> LayoutInput<u32> {
974 LayoutInput {
975 state_objects: Vec::new(),
976 working_addresses: Vec::new(),
977 sparse_addresses: Vec::new(),
978 unpacked_arrays: HashMap::default(),
979 requirements: LayoutRequirements::default(),
980 ff_referenced_addresses: HashSet::default(),
981 num_events: 3,
982 runtime_event_sites: Vec::new(),
983 lane_writers: Default::default(),
984 access_rank: Default::default(),
985 }
986 }
987 }
988
989 let base = MemoryLayout::build(&EmptyLayoutSource, false, MemoryLayoutMode::Packed);
990 let expanded = base.clone().with_backend_scratch(13);
991
992 assert_eq!(base.scratch_size, 0);
993 assert_eq!(expanded.scratch_base_offset, base.scratch_base_offset);
994 assert_eq!(expanded.scratch_size, 13);
995 assert_eq!(expanded.merged_total_size, base.scratch_base_offset + 16);
996 assert_eq!(expanded.offsets, base.offsets);
997 assert_eq!(expanded.working_offsets, base.working_offsets);
998 assert_eq!(expanded.triggered_bits_offset, base.triggered_bits_offset);
999 }
1000
1001 #[test]
1002 #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
1003 fn state_header_fields_do_not_overlap() {
1004 const {
1005 assert!(
1006 STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET + 8
1007 <= STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET
1008 );
1009 assert!(
1010 STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET + 8
1011 <= STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET
1012 );
1013 assert!(
1014 STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET + 8
1015 <= STATE_HEADER_NATIVE_LOOP_EVENT_SEQ_OFFSET
1016 );
1017 assert!(
1018 STATE_HEADER_NATIVE_LOOP_EVENT_SEQ_OFFSET + 8
1019 <= STATE_HEADER_EXTERN_FUNCTIONS_ADDR_OFFSET
1020 );
1021 assert!(STATE_HEADER_EXTERN_FUNCTIONS_ADDR_OFFSET + 8 <= STATE_HEADER_SIZE);
1022 }
1023 }
1024}