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
14pub const RUNTIME_EVENT_CAPACITY: usize = 1024;
15pub const RUNTIME_EVENT_WRITING: u64 = u64::MAX;
16pub const STATE_HEADER_SIZE: usize = 32;
17pub const STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET: usize = 0;
18#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
20pub const STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET: usize = 8;
21#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
22pub const STATE_HEADER_NATIVE_LOOP_EVENT_SEQ_OFFSET: usize = 24;
23#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
24pub const STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET: usize = 16;
25pub const RUNTIME_EVENT_HEADER_SIZE: usize = 8;
27pub const RUNTIME_EVENT_SLOT_SEQ_OFFSET: usize = 0;
28pub const RUNTIME_EVENT_SLOT_SITE_OFFSET: usize = 8;
29pub const RUNTIME_EVENT_SLOT_ARG_COUNT_OFFSET: usize = 16;
30pub const RUNTIME_EVENT_SLOT_PAYLOAD_OFFSET: usize = 24;
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct RuntimeEventArgLayout {
34 pub value_word_offset: usize,
35 pub mask_word_offset: usize,
36 pub word_count: usize,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct RuntimeEventSiteLayout {
41 pub args: Vec<RuntimeEventArgLayout>,
42 pub payload_words: usize,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct SparseWorkingLayout {
47 pub active_index: usize,
48 pub chunk_count: usize,
49 pub dirty_words_offset: usize,
50 pub dirty_word_count: usize,
51 pub summary_words_offset: usize,
52 pub summary_word_count: usize,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub enum MemoryLayoutMode {
57 Packed,
58 ElementStrided,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62pub struct UnpackedArrayLayout {
63 pub element_width: usize,
64 pub element_count: usize,
65 pub element_stride: usize,
66 pub plane_size: usize,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct StateObjectLayout<A> {
72 pub address: A,
73 pub width: usize,
74 pub is_4state: bool,
75}
76
77#[derive(Debug, Clone)]
84pub struct LayoutRequirements<A> {
85 state_aliases: HashMap<A, A>,
86}
87
88impl<A> Default for LayoutRequirements<A> {
89 fn default() -> Self {
90 Self {
91 state_aliases: HashMap::default(),
92 }
93 }
94}
95
96impl<A> LayoutRequirements<A> {
97 pub fn state_aliases(&self) -> &HashMap<A, A> {
98 &self.state_aliases
99 }
100
101 pub fn state_aliases_mut(&mut self) -> &mut HashMap<A, A> {
102 &mut self.state_aliases
103 }
104
105 pub fn is_empty(&self) -> bool {
106 self.state_aliases.is_empty()
107 }
108
109 pub fn clear(&mut self) {
110 self.state_aliases.clear();
111 }
112}
113
114#[derive(Debug, Clone)]
120pub struct LayoutInput<A> {
121 pub state_objects: Vec<StateObjectLayout<A>>,
122 pub working_addresses: Vec<A>,
123 pub sparse_addresses: Vec<A>,
124 pub unpacked_arrays: HashMap<A, UnpackedArrayLayout>,
125 pub requirements: LayoutRequirements<A>,
126 pub ff_referenced_addresses: HashSet<A>,
127 pub num_events: usize,
128 pub runtime_event_sites: Vec<RuntimeEventSite>,
129}
130
131pub trait LayoutSource<A> {
133 fn layout_input(&self, mode: MemoryLayoutMode) -> LayoutInput<A>;
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(bound(
138 serialize = "A: Serialize + Eq + Hash",
139 deserialize = "A: Deserialize<'de> + Eq + Hash"
140))]
141pub struct MemoryLayout<A> {
142 pub four_state: bool,
143 pub mode: MemoryLayoutMode,
144 pub offsets: HashMap<A, usize>,
146 pub widths: HashMap<A, usize>,
147 pub is_4states: HashMap<A, bool>,
149 pub unpacked_arrays: HashMap<A, UnpackedArrayLayout>,
150 pub total_size: usize,
152
153 pub working_offsets: HashMap<A, usize>,
155 pub working_base_offset: usize,
156 pub sparse_offsets: HashMap<A, usize>,
158 pub sparse_base_offset: usize,
159 pub sparse_layouts: HashMap<A, SparseWorkingLayout>,
160 pub sparse_active_bits_offset: usize,
161 pub sparse_active_capacity: usize,
162 pub merged_total_size: usize,
163
164 pub triggered_bits_offset: usize,
165 pub triggered_bits_total_size: usize,
166
167 pub scratch_base_offset: usize,
168 pub scratch_size: usize,
169
170 pub runtime_event_capacity: usize,
171 pub runtime_event_slot_size: usize,
172 pub runtime_event_buffer_size: usize,
173 pub runtime_event_site_layouts: Vec<RuntimeEventSiteLayout>,
174}
175
176type PhysicalLayoutObject<A> = (A, usize, bool, usize, usize);
177
178fn sort_layout_objects<A: Copy + Ord>(objects: &mut [PhysicalLayoutObject<A>]) {
179 objects.sort_unstable_by_key(|(address, _, _, _, alignment)| {
183 (std::cmp::Reverse(*alignment), *address)
184 });
185}
186
187impl<A> MemoryLayout<A>
188where
189 A: Copy + Eq + Hash + Ord,
190{
191 pub fn build<S>(source: &S, four_state: bool, mode: MemoryLayoutMode) -> Self
192 where
193 S: LayoutSource<A>,
194 {
195 let input = source.layout_input(mode);
196 let LayoutInput {
197 state_objects,
198 working_addresses,
199 sparse_addresses,
200 unpacked_arrays,
201 requirements,
202 ff_referenced_addresses,
203 num_events,
204 runtime_event_sites,
205 } = input;
206
207 let mut stable_objects = state_objects
208 .into_iter()
209 .map(|object| {
210 let size = unpacked_arrays
211 .get(&object.address)
212 .map(|layout| layout.plane_size)
213 .unwrap_or_else(|| get_byte_size(object.width));
214 let alignment = unpacked_arrays
215 .get(&object.address)
216 .map(|layout| layout.element_stride.min(8))
217 .unwrap_or_else(|| get_alignment(object.width));
218 (
219 object.address,
220 object.width,
221 object.is_4state,
222 size,
223 alignment,
224 )
225 })
226 .collect::<Vec<_>>();
227 sort_layout_objects(&mut stable_objects);
228
229 let mut offsets = HashMap::default();
230 let mut widths = HashMap::default();
231 let mut is_4states = HashMap::default();
232 let runtime_event_site_layouts = build_runtime_event_site_layouts(&runtime_event_sites);
233 let runtime_event_slot_size = RUNTIME_EVENT_SLOT_PAYLOAD_OFFSET
234 + runtime_event_site_layouts
235 .iter()
236 .map(|site| site.payload_words)
237 .max()
238 .unwrap_or(0)
239 * 8;
240
241 let mut current_offset = STATE_HEADER_SIZE;
242 for (address, width, is_4state, size, alignment) in stable_objects {
243 current_offset = align_up(current_offset, alignment);
244 offsets.insert(address, current_offset);
245 widths.insert(address, width);
246 is_4states.insert(address, is_4state);
247 current_offset += size;
248 if four_state {
249 current_offset += size;
250 }
251 }
252
253 let mut working_objects = working_addresses
254 .iter()
255 .map(|address| {
256 let width = widths[address];
257 let size = unpacked_arrays
258 .get(address)
259 .map(|layout| layout.plane_size)
260 .unwrap_or_else(|| get_byte_size(width));
261 let alignment = unpacked_arrays
262 .get(address)
263 .map(|layout| layout.element_stride.min(8))
264 .unwrap_or_else(|| get_alignment(width));
265 (*address, width, is_4states[address], size, alignment)
266 })
267 .collect::<Vec<_>>();
268 sort_layout_objects(&mut working_objects);
269
270 let mut working_offsets = HashMap::default();
271 let mut working_size = 0;
272 for (address, _, _, size, alignment) in working_objects {
273 working_size = align_up(working_size, alignment);
274 working_offsets.insert(address, working_size);
275 working_size += size;
276 if four_state {
277 working_size += size;
278 }
279 }
280
281 let mut sparse_objects = sparse_addresses
282 .iter()
283 .map(|address| {
284 let width = widths[address];
285 let size = unpacked_arrays
286 .get(address)
287 .map(|layout| layout.plane_size)
288 .unwrap_or_else(|| get_byte_size(width));
289 let alignment = unpacked_arrays
290 .get(address)
291 .map(|layout| layout.element_stride.min(8))
292 .unwrap_or_else(|| get_alignment(width));
293 (*address, width, is_4states[address], size, alignment)
294 })
295 .collect::<Vec<_>>();
296 sort_layout_objects(&mut sparse_objects);
297
298 let mut sparse_offsets = HashMap::default();
299 let mut sparse_size = 0usize;
300 for (address, _, _, size, alignment) in sparse_objects {
301 sparse_size = align_up(sparse_size, alignment);
302 sparse_offsets.insert(address, sparse_size);
303 let plane_count = if four_state { 2 } else { 1 };
304 let final_chunk_size = align_up(size, 8);
305 let physical_extent = (plane_count - 1) * size + final_chunk_size;
306 sparse_size += align_up(physical_extent, 8);
307 }
308
309 let working_base_offset = align_up(current_offset, 8);
310 let sparse_base_offset = align_up(working_base_offset + working_size, 8);
311 let mut sparse_metadata_offset = align_up(sparse_base_offset + sparse_size, 8);
312 let mut sparse_layouts = HashMap::default();
313 let mut sparse_order = sparse_addresses;
314 sparse_order.sort_unstable();
315 let sparse_active_capacity = sparse_order.len();
316 for (active_index, address) in sparse_order.into_iter().enumerate() {
317 let chunk_count = unpacked_arrays
318 .get(&address)
319 .map(|layout| layout.plane_size.div_ceil(8))
320 .unwrap_or_else(|| widths[&address].div_ceil(64));
321 let dirty_word_count = chunk_count.div_ceil(64);
322 let summary_word_count = dirty_word_count.div_ceil(64);
323 let dirty_words_offset = sparse_metadata_offset;
324 sparse_metadata_offset += dirty_word_count * 8;
325 let summary_words_offset = sparse_metadata_offset;
326 sparse_metadata_offset += summary_word_count * 8;
327 sparse_layouts.insert(
328 address,
329 SparseWorkingLayout {
330 active_index,
331 chunk_count,
332 dirty_words_offset,
333 dirty_word_count,
334 summary_words_offset,
335 summary_word_count,
336 },
337 );
338 }
339
340 let sparse_active_bits_offset = align_up(sparse_metadata_offset, 8);
341 sparse_metadata_offset =
342 sparse_active_bits_offset + sparse_active_capacity.div_ceil(64) * 8;
343 let triggered_bits_offset = align_up(sparse_metadata_offset, 8);
344 let triggered_bits_total_size = num_events.div_ceil(8);
345 let scratch_base_offset = align_up(triggered_bits_offset + triggered_bits_total_size, 8);
346 let runtime_event_buffer_size =
347 RUNTIME_EVENT_HEADER_SIZE + RUNTIME_EVENT_CAPACITY * runtime_event_slot_size;
348 let merged_total_size = scratch_base_offset;
349
350 let mut address_aliases = requirements.state_aliases.into_iter().collect::<Vec<_>>();
351 address_aliases.sort_unstable();
352 for (alias, canonical) in address_aliases {
353 let fourstate_ok = !four_state
354 || (is_4states.get(&alias) == Some(&false)
355 && is_4states.get(&canonical) == Some(&false));
356 let alias_fits = widths
357 .get(&alias)
358 .zip(widths.get(&canonical))
359 .is_some_and(|(&alias_width, &canonical_width)| alias_width <= canonical_width);
360 if fourstate_ok
361 && alias_fits
362 && !ff_referenced_addresses.contains(&alias)
363 && let Some(&canonical_offset) = offsets.get(&canonical)
364 {
365 offsets.insert(alias, canonical_offset);
366 }
367 }
368
369 Self {
370 four_state,
371 mode,
372 offsets,
373 widths,
374 is_4states,
375 unpacked_arrays,
376 total_size: current_offset,
377 working_offsets,
378 working_base_offset,
379 sparse_offsets,
380 sparse_base_offset,
381 sparse_layouts,
382 sparse_active_bits_offset,
383 sparse_active_capacity,
384 merged_total_size,
385 triggered_bits_offset,
386 triggered_bits_total_size,
387 scratch_base_offset,
388 scratch_size: 0,
389 runtime_event_capacity: RUNTIME_EVENT_CAPACITY,
390 runtime_event_slot_size,
391 runtime_event_buffer_size,
392 runtime_event_site_layouts,
393 }
394 }
395
396 pub fn with_backend_scratch(mut self, scratch_size: usize) -> Self {
400 self.scratch_size = scratch_size;
401 self.merged_total_size = align_up(self.scratch_base_offset + scratch_size, 8);
402 self
403 }
404
405 pub fn plane_size(&self, address: &A) -> usize {
406 self.unpacked_arrays
407 .get(address)
408 .map(|layout| layout.plane_size)
409 .unwrap_or_else(|| get_byte_size(self.widths[address]))
410 }
411
412 pub fn region_base_offset<R>(&self, address: &R) -> usize
413 where
414 R: RegionedAddress<A>,
415 {
416 let absolute = address.absolute_address();
417 match address.region() {
418 STABLE_REGION => self.offsets[&absolute],
419 SPARSE_WORKING_REGION => self.sparse_base_offset + self.sparse_offsets[&absolute],
420 _ => self.working_base_offset + self.working_offsets[&absolute],
421 }
422 }
423
424 pub fn map_static_bit_offset(&self, address: &A, bit_offset: usize) -> (usize, usize) {
425 let Some(array) = self.unpacked_arrays.get(address) else {
426 return (bit_offset / 8, bit_offset % 8);
427 };
428 let element = bit_offset / array.element_width;
429 let intra_element = bit_offset % array.element_width;
430 (
431 element * array.element_stride + intra_element / 8,
432 intra_element % 8,
433 )
434 }
435
436 pub fn regioned_static_byte_and_intra<R>(
437 &self,
438 address: &R,
439 bit_offset: usize,
440 ) -> Option<(i32, usize)>
441 where
442 R: RegionedAddress<A>,
443 {
444 let absolute = address.absolute_address();
445 let base = match address.region() {
446 STABLE_REGION => *self.offsets.get(&absolute).unwrap_or(&0),
447 SPARSE_WORKING_REGION => {
448 self.sparse_base_offset + *self.sparse_offsets.get(&absolute).unwrap_or(&0)
449 }
450 _ => self.working_base_offset + *self.working_offsets.get(&absolute).unwrap_or(&0),
451 };
452 let (byte, intra) = self.map_static_bit_offset(&absolute, bit_offset);
453 Some((i32::try_from(base.checked_add(byte)?).ok()?, intra))
454 }
455}
456
457pub trait RegionedAddress<A> {
458 fn region(&self) -> u32;
459 fn absolute_address(&self) -> A;
460}
461
462impl<V: Copy> RegionedAddress<AbsoluteAddrBase<V>> for RegionedAbsoluteAddrBase<V> {
463 fn region(&self) -> u32 {
464 self.region
465 }
466
467 fn absolute_address(&self) -> AbsoluteAddrBase<V> {
468 self.absolute_addr()
469 }
470}
471
472fn build_runtime_event_site_layouts(sites: &[RuntimeEventSite]) -> Vec<RuntimeEventSiteLayout> {
473 sites
474 .iter()
475 .map(|site| {
476 let mut payload_words = 0;
477 let args = site
478 .arg_widths
479 .iter()
480 .map(|width| {
481 let word_count = (*width).div_ceil(64).max(1);
482 let value_word_offset = payload_words;
483 payload_words += word_count;
484 let mask_word_offset = payload_words;
485 payload_words += word_count;
486 RuntimeEventArgLayout {
487 value_word_offset,
488 mask_word_offset,
489 word_count,
490 }
491 })
492 .collect();
493 RuntimeEventSiteLayout {
494 args,
495 payload_words,
496 }
497 })
498 .collect()
499}
500
501const fn align_up(offset: usize, alignment: usize) -> usize {
502 (offset + alignment - 1) & !(alignment - 1)
503}
504
505fn get_alignment(width: usize) -> usize {
506 let size = get_byte_size(width);
507 if size == 0 {
508 1
509 } else if size <= 8 {
510 size.next_power_of_two()
511 } else {
512 8
513 }
514}
515
516pub const fn get_byte_size(width: usize) -> usize {
517 width.div_ceil(8)
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523
524 #[test]
525 fn byte_size_rounds_up_partial_bytes() {
526 assert_eq!(get_byte_size(0), 0);
527 assert_eq!(get_byte_size(1), 1);
528 assert_eq!(get_byte_size(8), 1);
529 assert_eq!(get_byte_size(9), 2);
530 }
531
532 #[test]
533 fn layout_order_is_independent_of_input_iteration_order() {
534 let high_address = (3u32, 64, false, 8, 8);
535 let low_address = (1u32, 64, false, 8, 8);
536 let less_aligned = (0u32, 32, false, 4, 4);
537 let mut forward = vec![high_address, less_aligned, low_address];
538 let mut reverse = forward.iter().copied().rev().collect::<Vec<_>>();
539
540 sort_layout_objects(&mut forward);
541 sort_layout_objects(&mut reverse);
542
543 assert_eq!(forward, reverse);
544 assert_eq!(forward, vec![low_address, high_address, less_aligned]);
545 }
546
547 #[test]
548 fn layout_requirements_own_semantic_aliases_until_layout() {
549 let mut requirements = LayoutRequirements::default();
550 requirements.state_aliases_mut().insert(2u32, 1u32);
551
552 assert_eq!(requirements.state_aliases().get(&2), Some(&1));
553 assert!(!requirements.is_empty());
554
555 requirements.clear();
556 assert!(requirements.is_empty());
557 }
558
559 #[test]
560 fn layout_applies_aliases_from_requirements() {
561 struct AliasLayoutSource;
562
563 impl LayoutSource<u32> for AliasLayoutSource {
564 fn layout_input(&self, _mode: MemoryLayoutMode) -> LayoutInput<u32> {
565 let mut requirements = LayoutRequirements::default();
566 requirements.state_aliases_mut().insert(2, 1);
567 LayoutInput {
568 state_objects: vec![
569 StateObjectLayout {
570 address: 1,
571 width: 8,
572 is_4state: false,
573 },
574 StateObjectLayout {
575 address: 2,
576 width: 8,
577 is_4state: false,
578 },
579 ],
580 working_addresses: Vec::new(),
581 sparse_addresses: Vec::new(),
582 unpacked_arrays: HashMap::default(),
583 requirements,
584 ff_referenced_addresses: HashSet::default(),
585 num_events: 0,
586 runtime_event_sites: Vec::new(),
587 }
588 }
589 }
590
591 let layout = MemoryLayout::build(&AliasLayoutSource, false, MemoryLayoutMode::Packed);
592 assert_eq!(layout.offsets[&1], layout.offsets[&2]);
593 }
594
595 #[test]
596 fn backend_scratch_only_extends_the_final_layout_region() {
597 struct EmptyLayoutSource;
598
599 impl LayoutSource<u32> for EmptyLayoutSource {
600 fn layout_input(&self, _mode: MemoryLayoutMode) -> LayoutInput<u32> {
601 LayoutInput {
602 state_objects: Vec::new(),
603 working_addresses: Vec::new(),
604 sparse_addresses: Vec::new(),
605 unpacked_arrays: HashMap::default(),
606 requirements: LayoutRequirements::default(),
607 ff_referenced_addresses: HashSet::default(),
608 num_events: 3,
609 runtime_event_sites: Vec::new(),
610 }
611 }
612 }
613
614 let base = MemoryLayout::build(&EmptyLayoutSource, false, MemoryLayoutMode::Packed);
615 let expanded = base.clone().with_backend_scratch(13);
616
617 assert_eq!(base.scratch_size, 0);
618 assert_eq!(expanded.scratch_base_offset, base.scratch_base_offset);
619 assert_eq!(expanded.scratch_size, 13);
620 assert_eq!(expanded.merged_total_size, base.scratch_base_offset + 16);
621 assert_eq!(expanded.offsets, base.offsets);
622 assert_eq!(expanded.working_offsets, base.working_offsets);
623 assert_eq!(expanded.triggered_bits_offset, base.triggered_bits_offset);
624 }
625
626 #[test]
627 #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
628 fn state_header_fields_do_not_overlap() {
629 const {
630 assert!(
631 STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET + 8
632 <= STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET
633 );
634 assert!(
635 STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET + 8
636 <= STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET
637 );
638 assert!(
639 STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET + 8
640 <= STATE_HEADER_NATIVE_LOOP_EVENT_SEQ_OFFSET
641 );
642 assert!(STATE_HEADER_NATIVE_LOOP_EVENT_SEQ_OFFSET + 8 <= STATE_HEADER_SIZE);
643 }
644 }
645}