1use crate::config::Config;
5use crate::host::{Eviction, Extent, Host, ReadOutcome, ReadRequest, RequestId, Submit};
6use crate::key::{Class, ResourceKey};
7use crate::pool::{Handle, PagePool, PageRun, Placement};
8
9const NO_ENTRY: usize = usize::MAX;
10
11#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub struct Wanted {
14 pub key: ResourceKey,
16 pub class: Class,
18 pub distance: u32,
21 pub deadline_ticks: u32,
24}
25
26impl Wanted {
27 pub const fn new(key: ResourceKey, class: Class, distance: u32) -> Self {
29 Self {
30 key,
31 class,
32 distance,
33 deadline_ticks: u32::MAX,
34 }
35 }
36
37 pub const fn with_deadline_ticks(mut self, deadline_ticks: u32) -> Self {
39 self.deadline_ticks = deadline_ticks;
40 self
41 }
42
43 const EMPTY: Wanted = Wanted::new(ResourceKey::from_u32(0).unwrap(), Class::Demand, 0);
44
45 fn order(&self) -> (Class, u32, u32, u32) {
46 (
47 self.class,
48 self.deadline_ticks,
49 self.distance,
50 self.key.as_u32(),
51 )
52 }
53}
54
55#[derive(Copy, Clone, Debug, PartialEq, Eq)]
57#[repr(u8)]
58pub enum State {
59 Absent = 0,
61 Reading,
63 Landed,
66 Installing,
68 Resident,
70 Failed,
72}
73
74#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
76pub struct WindowStats {
77 pub requests: u32,
79 pub misses: u32,
81 pub prefetch_requests: u32,
83 pub issued: u32,
85 pub pending_loads: u32,
87 pub adopted: u32,
90 pub promoted: u32,
92 pub cancelled: u32,
94 pub evictions: u32,
96 pub failed_loads: u32,
98 pub protected_full: u32,
100 pub transport_full: u32,
102 pub deferred: u32,
104 pub backoff_skipped: u32,
106 pub unknown_keys: u32,
108 pub unconfigured: u32,
110 pub over_budget: u32,
112 pub table_full: u32,
114 pub wanted_dropped: u32,
116 pub eviction_overflow: u32,
118 pub compactions: u32,
120 pub pages_moved: u32,
122 pub work_used: u32,
124}
125
126#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
128pub struct StepReport {
129 pub stats: WindowStats,
131 pub eviction_count: usize,
133}
134
135#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
137pub struct Totals {
138 pub windows: u32,
140 pub issued: u32,
142 pub completed: u32,
144 pub failed_loads: u32,
146 pub evictions: u32,
148 pub wasted_evictions: u32,
151 pub cancelled: u32,
153}
154
155#[derive(Copy, Clone)]
156struct Entry {
157 key: ResourceKey,
158 in_use: bool,
159 state: State,
160 class: Class,
161 pin_class: Class,
162 pin_count: u8,
163 failures: u8,
164 cancel_requested: bool,
165 used_since_landed: bool,
166 slot: u16,
167 request: u32,
168 hold_until: u32,
169 last_used: u32,
170 wanted_epoch: u32,
171 distance: u32,
172 distance_epoch: u32,
173 deadline_ticks: u32,
174 bytes: u32,
175 lba_end: u32,
176}
177
178struct Budget {
179 left: u32,
180 used: u32,
181}
182
183impl Budget {
184 fn charge(&mut self, units: u32) {
185 self.used = self.used.saturating_add(units);
186 self.left = self.left.saturating_sub(units);
187 }
188
189 fn is_exhausted(&self) -> bool {
190 self.left == 0
191 }
192}
193
194struct EvictionSink<'a> {
195 buffer: &'a mut [Eviction],
196 len: usize,
197}
198
199#[derive(Copy, Clone)]
200struct Requester {
201 class: Class,
202 distance: u32,
203}
204
205type WindowCost = (u8, u32, u32, u32, u32);
209
210#[derive(Copy, Clone)]
211struct RunInfo {
212 first: u32,
213 count: u32,
214 entry: usize,
215 tier: Option<u8>,
216 distance: u32,
217 last_used: u32,
218}
219
220const EMPTY_RUN_INFO: RunInfo = RunInfo {
221 first: 0,
222 count: 0,
223 entry: NO_ENTRY,
224 tier: None,
225 distance: 0,
226 last_used: 0,
227};
228
229pub struct Residency<const ENTRIES: usize, const SLOTS: usize, const CATEGORIES: usize> {
244 config: Config<CATEGORIES>,
245 pools: [PagePool<SLOTS>; CATEGORIES],
246 owner: [[u16; SLOTS]; CATEGORIES],
248 entries: [Entry; ENTRIES],
249 category_pages: [u32; CATEGORIES],
250 category_entries: [u16; CATEGORIES],
251 epoch: u32,
252 next_request: u32,
253 in_flight: u32,
254 failed_mark: u32,
255 totals: Totals,
256}
257
258impl<const ENTRIES: usize, const SLOTS: usize, const CATEGORIES: usize>
259 Residency<ENTRIES, SLOTS, CATEGORIES>
260{
261 const EMPTY_POOL: PagePool<SLOTS> = PagePool::new(0, 0, Placement::FirstFit);
262 const EMPTY_ENTRY: Entry = Entry {
263 key: ResourceKey::from_u32(0).unwrap(),
264 in_use: false,
265 state: State::Absent,
266 class: Class::Demand,
267 pin_class: Class::Demand,
268 pin_count: 0,
269 failures: 0,
270 cancel_requested: false,
271 used_since_landed: false,
272 slot: 0,
273 request: 0,
274 hold_until: 0,
275 last_used: 0,
276 wanted_epoch: 0,
277 distance: 0,
278 distance_epoch: 0,
279 deadline_ticks: u32::MAX,
280 bytes: 0,
281 lba_end: 0,
282 };
283
284 pub const fn new(config: Config<CATEGORIES>) -> Self {
286 const { assert!(ENTRIES < u16::MAX as usize && SLOTS < u16::MAX as usize) };
287 let mut pools = [Self::EMPTY_POOL; CATEGORIES];
288 let mut pool = 0;
289 while pool < CATEGORIES {
290 pools[pool] = PagePool::new(
291 pool as u8,
292 config.pools[pool].capacity_pages,
293 config.pools[pool].placement,
294 );
295 pool += 1;
296 }
297 Self {
298 config,
299 pools,
300 owner: [[0; SLOTS]; CATEGORIES],
301 entries: [Self::EMPTY_ENTRY; ENTRIES],
302 category_pages: [0; CATEGORIES],
303 category_entries: [0; CATEGORIES],
304 epoch: 0,
305 next_request: 1,
306 in_flight: 0,
307 failed_mark: 0,
308 totals: Totals {
309 windows: 0,
310 issued: 0,
311 completed: 0,
312 failed_loads: 0,
313 evictions: 0,
314 wasted_evictions: 0,
315 cancelled: 0,
316 },
317 }
318 }
319
320 pub const fn config(&self) -> &Config<CATEGORIES> {
322 &self.config
323 }
324
325 pub const fn totals(&self) -> Totals {
327 self.totals
328 }
329
330 pub fn pool(&self, pool: usize) -> Option<&PagePool<SLOTS>> {
332 self.pools.get(pool)
333 }
334
335 pub fn category_pages(&self, category: crate::Category) -> u32 {
337 self.category_pages
338 .get(category.number() as usize)
339 .copied()
340 .unwrap_or(0)
341 }
342
343 pub fn category_entry_count(&self, category: crate::Category) -> usize {
345 self.category_entries
346 .get(category.number() as usize)
347 .copied()
348 .unwrap_or(0) as usize
349 }
350
351 pub fn tracked_count(&self) -> usize {
353 self.entries.iter().filter(|entry| entry.in_use).count()
354 }
355
356 pub const fn in_flight_count(&self) -> usize {
358 self.in_flight as usize
359 }
360
361 pub const fn window(&self) -> u32 {
363 self.epoch
364 }
365
366 fn find_entry(&self, key: ResourceKey) -> Option<usize> {
369 self.entries
370 .iter()
371 .position(|entry| entry.in_use && entry.key == key)
372 }
373
374 pub fn state(&self, key: ResourceKey) -> State {
376 self.find_entry(key)
377 .map_or(State::Absent, |index| self.entries[index].state)
378 }
379
380 pub fn is_resident(&self, key: ResourceKey) -> bool {
382 self.state(key) == State::Resident
383 }
384
385 pub fn handle(&self, key: ResourceKey) -> Option<Handle> {
387 let entry = &self.entries[self.find_entry(key)?];
388 self.entry_handle(entry)
389 }
390
391 pub fn resident_handle(&self, key: ResourceKey) -> Option<Handle> {
393 let entry = &self.entries[self.find_entry(key)?];
394 (entry.state == State::Resident)
395 .then(|| self.entry_handle(entry))
396 .flatten()
397 }
398
399 pub fn try_run(&self, handle: Handle) -> Option<PageRun> {
403 self.pools.get(handle.pool() as usize)?.try_run(handle)
404 }
405
406 pub fn payload_bytes(&self, key: ResourceKey) -> usize {
408 self.find_entry(key)
409 .map_or(0, |index| self.entries[index].bytes as usize)
410 }
411
412 pub fn request_of(&self, key: ResourceKey) -> Option<RequestId> {
414 let entry = &self.entries[self.find_entry(key)?];
415 (entry.state == State::Reading).then_some(RequestId(entry.request))
416 }
417
418 fn entry_handle(&self, entry: &Entry) -> Option<Handle> {
419 let pool = self.pool_of_key(entry.key)?;
420 self.pools[pool].handle_of_slot(entry.slot)
421 }
422
423 fn pool_of_key(&self, key: ResourceKey) -> Option<usize> {
424 let category = key.category().number() as usize;
425 let config = self.config.categories.get(category)?;
426 (config.max_entries > 0 && (config.pool as usize) < CATEGORIES)
427 .then_some(config.pool as usize)
428 }
429
430 fn holds_pages(state: State) -> bool {
431 matches!(
432 state,
433 State::Reading | State::Landed | State::Installing | State::Resident
434 )
435 }
436
437 fn hold_active(&self, entry: &Entry) -> bool {
438 entry.failures > 0 && (entry.hold_until.wrapping_sub(self.epoch) as i32) > 0
439 }
440
441 pub fn pin(&mut self, key: ResourceKey, class: Class) -> bool {
447 if self.pool_of_key(key).is_none() {
448 return false;
449 }
450 let Some(index) = self.ensure_entry(key) else {
451 return false;
452 };
453 let entry = &mut self.entries[index];
454 if entry.pin_count == u8::MAX {
455 return false;
456 }
457 entry.pin_class = if entry.pin_count == 0 {
458 class
459 } else {
460 entry.pin_class.min(class)
461 };
462 entry.pin_count += 1;
463 self.sync_lock(index);
464 true
465 }
466
467 pub fn unpin(&mut self, key: ResourceKey) -> bool {
469 let Some(index) = self.find_entry(key) else {
470 return false;
471 };
472 if self.entries[index].pin_count == 0 {
473 return false;
474 }
475 self.entries[index].pin_count -= 1;
476 self.sync_lock(index);
477 self.release_if_idle(index);
478 true
479 }
480
481 pub fn pin_count(&self, key: ResourceKey) -> u8 {
483 self.find_entry(key)
484 .map_or(0, |index| self.entries[index].pin_count)
485 }
486
487 pub fn touch(&mut self, key: ResourceKey) {
490 if let Some(index) = self.find_entry(key) {
491 let entry = &mut self.entries[index];
492 entry.last_used = self.epoch;
493 entry.used_since_landed = true;
494 }
495 }
496
497 fn sync_lock(&mut self, index: usize) {
498 let entry = self.entries[index];
499 if !Self::holds_pages(entry.state) {
500 return;
501 }
502 let Some(pool) = self.pool_of_key(entry.key) else {
503 return;
504 };
505 let category = entry.key.category().number() as usize;
506 let locked = matches!(entry.state, State::Reading | State::Installing)
507 || entry.pin_count > 0
508 || !self.config.categories[category].movable;
509 if let Some(handle) = self.pools[pool].handle_of_slot(entry.slot) {
510 self.pools[pool].set_locked(handle, locked);
511 }
512 }
513
514 fn ensure_entry(&mut self, key: ResourceKey) -> Option<usize> {
517 if let Some(index) = self.find_entry(key) {
518 return Some(index);
519 }
520 let free = self
521 .entries
522 .iter()
523 .position(|entry| !entry.in_use)
524 .or_else(|| {
525 let mut best: Option<(bool, usize)> = None;
527 for (index, entry) in self.entries.iter().enumerate() {
528 if entry.state == State::Failed
529 && entry.pin_count == 0
530 && entry.wanted_epoch != self.epoch
531 {
532 let expired = !self.hold_active(entry);
533 if best.is_none_or(|(best_expired, _)| expired && !best_expired) {
534 best = Some((expired, index));
535 }
536 }
537 }
538 best.map(|(_, index)| index)
539 })?;
540 self.entries[free] = Self::EMPTY_ENTRY;
541 self.entries[free].in_use = true;
542 self.entries[free].key = key;
543 Some(free)
544 }
545
546 fn release_if_idle(&mut self, index: usize) {
547 let entry = &self.entries[index];
548 if entry.in_use
549 && entry.state == State::Absent
550 && entry.pin_count == 0
551 && entry.failures == 0
552 {
553 self.entries[index] = Self::EMPTY_ENTRY;
554 }
555 }
556
557 fn free_pages_of(&mut self, index: usize) -> Option<(Handle, PageRun)> {
560 let entry = self.entries[index];
561 let pool = self.pool_of_key(entry.key)?;
562 let handle = self.pools[pool].handle_of_slot(entry.slot)?;
563 let run = self.pools[pool].try_run(handle)?;
564 self.pools[pool].free(handle);
565 self.owner[pool][entry.slot as usize] = 0;
566 let category = entry.key.category().number() as usize;
567 self.category_pages[category] -= run.page_count;
568 self.category_entries[category] -= 1;
569 Some((handle, run))
570 }
571
572 pub fn complete_read(&mut self, request: RequestId, outcome: ReadOutcome) -> bool {
580 let Some(index) = self.entries.iter().position(|entry| {
581 entry.in_use && entry.state == State::Reading && entry.request == request.0
582 }) else {
583 return false;
584 };
585 self.in_flight -= 1;
586 self.entries[index].cancel_requested = false;
587 match outcome {
588 ReadOutcome::Done => {
589 let category = self.entries[index].key.category().number() as usize;
590 let installs = self.config.categories[category].installs;
591 let entry = &mut self.entries[index];
592 entry.failures = 0;
593 entry.hold_until = self.epoch;
594 entry.state = if installs {
595 State::Landed
596 } else {
597 State::Resident
598 };
599 entry.last_used = self.epoch;
600 entry.used_since_landed = false;
601 self.totals.completed += 1;
602 self.sync_lock(index);
603 }
604 ReadOutcome::Failed => self.fail_entry(index),
605 ReadOutcome::Cancelled => {
606 self.totals.cancelled += 1;
607 self.free_pages_of(index);
608 self.entries[index].state = State::Absent;
609 self.release_if_idle(index);
610 }
611 }
612 true
613 }
614
615 fn fail_entry(&mut self, index: usize) {
616 self.free_pages_of(index);
617 let backoff = self.config.backoff;
618 let epoch = self.epoch;
619 let entry = &mut self.entries[index];
620 entry.failures = entry.failures.saturating_add(1);
621 entry.hold_until = epoch.wrapping_add(backoff.windows(entry.failures));
622 entry.state = State::Failed;
623 self.totals.failed_loads += 1;
624 }
625
626 pub fn begin_install(&mut self, key: ResourceKey) -> Option<Handle> {
630 let index = self.find_entry(key)?;
631 if self.entries[index].state != State::Landed {
632 return None;
633 }
634 self.entries[index].state = State::Installing;
635 self.sync_lock(index);
636 let entry = self.entries[index];
637 self.entry_handle(&entry)
638 }
639
640 pub fn finish_install(&mut self, key: ResourceKey) -> bool {
642 let Some(index) = self.find_entry(key) else {
643 return false;
644 };
645 if self.entries[index].state != State::Installing {
646 return false;
647 }
648 let entry = &mut self.entries[index];
649 entry.state = State::Resident;
650 entry.last_used = self.epoch;
651 self.sync_lock(index);
652 true
653 }
654
655 pub fn abort_install(&mut self, key: ResourceKey) -> bool {
658 let Some(index) = self.find_entry(key) else {
659 return false;
660 };
661 if self.entries[index].state != State::Installing {
662 return false;
663 }
664 self.free_pages_of(index);
665 self.entries[index].state = State::Absent;
666 self.release_if_idle(index);
667 true
668 }
669
670 pub fn fail_install(&mut self, key: ResourceKey) -> bool {
673 let Some(index) = self.find_entry(key) else {
674 return false;
675 };
676 if self.entries[index].state != State::Installing {
677 return false;
678 }
679 self.fail_entry(index);
680 true
681 }
682
683 pub fn next_install(&self, demand: Option<ResourceKey>) -> Option<ResourceKey> {
686 if let Some(key) = demand {
687 if self.state(key) == State::Landed {
688 return Some(key);
689 }
690 }
691 self.entries
692 .iter()
693 .filter(|entry| {
694 entry.in_use && entry.state == State::Landed && entry.wanted_epoch == self.epoch
695 })
696 .min_by_key(|entry| {
697 (
698 entry.class,
699 entry.deadline_ticks,
700 entry.distance,
701 entry.key.as_u32(),
702 )
703 })
704 .map(|entry| entry.key)
705 }
706
707 pub fn evict_unlisted(&mut self, keep: &[ResourceKey], evictions: &mut [Eviction]) -> usize {
711 let mut sink = EvictionSink {
712 buffer: evictions,
713 len: 0,
714 };
715 for index in 0..ENTRIES {
716 let entry = self.entries[index];
717 if !entry.in_use
718 || !matches!(entry.state, State::Resident | State::Landed)
719 || entry.pin_count > 0
720 || keep.contains(&entry.key)
721 {
722 continue;
723 }
724 if sink.len == sink.buffer.len() {
725 break;
726 }
727 self.evict_entry(index, &mut sink);
728 }
729 sink.len
730 }
731
732 fn evict_entry(&mut self, index: usize, sink: &mut EvictionSink<'_>) {
733 let entry = self.entries[index];
734 if let Some((handle, run)) = self.free_pages_of(index) {
735 sink.buffer[sink.len] = Eviction {
736 key: entry.key,
737 handle,
738 run,
739 };
740 sink.len += 1;
741 }
742 self.totals.evictions += 1;
743 if !entry.used_since_landed {
744 self.totals.wasted_evictions += 1;
745 }
746 self.entries[index].state = State::Absent;
747 self.entries[index].failures = 0;
748 self.release_if_idle(index);
749 }
750
751 pub fn step<H: Host>(
762 &mut self,
763 wanted: &[Wanted],
764 host: &mut H,
765 evictions: &mut [Eviction],
766 ) -> StepReport {
767 self.epoch = self.epoch.wrapping_add(1).max(1);
768 self.totals.windows += 1;
769 let mut stats = WindowStats {
770 failed_loads: self.totals.failed_loads - self.failed_mark,
771 ..WindowStats::default()
772 };
773 self.failed_mark = self.totals.failed_loads;
774 let mut budget = Budget {
775 left: self.config.step_budget,
776 used: 0,
777 };
778 let mut sink = EvictionSink {
779 buffer: evictions,
780 len: 0,
781 };
782
783 let mut items = [Wanted::EMPTY; ENTRIES];
785 let mut count = 0usize;
786 for entry in &self.entries {
787 if entry.in_use && entry.pin_count > 0 && count < ENTRIES {
788 items[count] = Wanted::new(entry.key, entry.pin_class, 0);
789 count += 1;
790 }
791 }
792 for want in wanted {
793 let configured = self
794 .config
795 .categories
796 .get(want.key.category().number() as usize)
797 .is_some_and(|category| category.max_entries > 0);
798 if !configured {
799 stats.unconfigured += 1;
800 } else if count == ENTRIES {
801 stats.wanted_dropped += 1;
802 } else {
803 items[count] = *want;
804 count += 1;
805 }
806 }
807 budget.charge(count as u32);
808 for i in 1..count {
810 let mut j = i;
811 while j > 0 && items[j].order() < items[j - 1].order() {
812 items.swap(j, j - 1);
813 j -= 1;
814 budget.charge(1);
815 }
816 }
817
818 let mut needs_issue = [false; ENTRIES];
820 for n in 0..count {
821 let want = items[n];
822 let exempt = want.class <= Class::CombatFill;
823 if !exempt && budget.is_exhausted() {
824 stats.deferred += 1;
825 continue;
826 }
827 budget.charge(1);
828 stats.requests += 1;
829 if want.class >= Class::Lead {
830 stats.prefetch_requests += 1;
831 }
832 let Some(index) = self.find_entry(want.key) else {
833 stats.misses += 1;
834 needs_issue[n] = true;
835 continue;
836 };
837 let epoch = self.epoch;
838 let first_mark = self.entries[index].wanted_epoch != epoch;
839 if first_mark {
840 let entry = self.entries[index];
841 let wanted_last_window = entry.wanted_epoch.wrapping_add(1) == epoch;
842 if matches!(
843 entry.state,
844 State::Reading | State::Landed | State::Installing
845 ) && !wanted_last_window
846 {
847 stats.adopted += 1;
848 }
849 if entry.state == State::Reading && want.class < entry.class {
850 host.promote(RequestId(entry.request), want.class);
851 stats.promoted += 1;
852 }
853 let entry = &mut self.entries[index];
854 entry.class = if entry.state == State::Reading {
855 entry.class.min(want.class)
856 } else {
857 want.class
858 };
859 entry.wanted_epoch = epoch;
860 entry.distance = want.distance;
861 entry.distance_epoch = epoch;
862 entry.deadline_ticks = want.deadline_ticks;
863 }
864 let entry = self.entries[index];
865 match entry.state {
866 State::Resident => {
867 let entry = &mut self.entries[index];
868 entry.last_used = epoch;
869 entry.used_since_landed = true;
870 }
871 State::Reading | State::Landed | State::Installing => {
872 stats.misses += 1;
873 stats.pending_loads += 1;
874 }
875 State::Failed => {
876 stats.misses += 1;
877 if self.hold_active(&entry) {
878 stats.backoff_skipped += 1;
879 } else if first_mark {
880 needs_issue[n] = true;
881 }
882 }
883 State::Absent => {
884 stats.misses += 1;
885 if first_mark {
886 needs_issue[n] = true;
887 }
888 }
889 }
890 }
891
892 for n in 0..count {
894 if !needs_issue[n] {
895 continue;
896 }
897 let want = items[n];
898 let exempt = want.class <= Class::CombatFill;
899 if !exempt && budget.is_exhausted() {
900 stats.deferred += 1;
901 continue;
902 }
903 if let Some(index) = self.find_entry(want.key) {
904 let entry = self.entries[index];
905 let idle = matches!(entry.state, State::Absent)
906 || (entry.state == State::Failed && !self.hold_active(&entry));
907 if !idle {
908 continue;
909 }
910 }
911 if self.active_in_flight() >= self.config.max_in_flight as usize
912 && !(self.config.cancel_for_demand
913 && want.class == Class::Demand
914 && self.cancel_least_urgent(want, host, &mut stats))
915 {
916 stats.transport_full += 1;
917 break;
918 }
919 if !host.can_accept() {
920 stats.transport_full += 1;
921 break;
922 }
923 let Some(extent) = host.extent(want.key) else {
924 stats.unknown_keys += 1;
925 continue;
926 };
927 budget.charge(1);
928 let Some(index) = self.ensure_entry(want.key) else {
929 stats.table_full += 1;
930 continue;
931 };
932 let requester = Requester {
933 class: want.class,
934 distance: want.distance,
935 };
936 let Some((handle, run)) = self.place(
937 want.key,
938 requester,
939 extent,
940 host,
941 &mut sink,
942 &mut budget,
943 &mut stats,
944 ) else {
945 stats.protected_full += 1;
946 self.release_if_idle(index);
947 continue;
948 };
949 let id = RequestId(self.next_request);
950 self.next_request = self.next_request.wrapping_add(1).max(1);
951 let contiguous_with = self
952 .entries
953 .iter()
954 .find(|other| {
955 other.in_use
956 && other.state == State::Reading
957 && !other.cancel_requested
958 && other.lba_end == extent.lba
959 })
960 .map(|other| RequestId(other.request));
961 let request = ReadRequest {
962 id,
963 key: want.key,
964 class: want.class,
965 handle,
966 run,
967 lba: extent.lba,
968 sectors: extent.sectors,
969 deadline_ticks: want.deadline_ticks,
970 contiguous_with,
971 };
972 if host.submit(&request) == Submit::Full {
973 let pool = handle.pool() as usize;
974 self.pools[pool].free(handle);
975 stats.transport_full += 1;
976 self.release_if_idle(index);
977 break;
978 }
979 let pool = handle.pool() as usize;
980 let category = want.key.category().number() as usize;
981 self.owner[pool][handle.slot() as usize] = index as u16 + 1;
982 self.category_pages[category] += run.page_count;
983 self.category_entries[category] += 1;
984 self.in_flight += 1;
985 self.totals.issued += 1;
986 stats.issued += 1;
987 let epoch = self.epoch;
988 let entry = &mut self.entries[index];
989 entry.state = State::Reading;
990 entry.slot = handle.slot();
991 entry.class = want.class;
992 entry.request = id.0;
993 entry.bytes = extent.bytes;
994 entry.lba_end = extent.lba + extent.sectors;
995 entry.cancel_requested = false;
996 entry.used_since_landed = false;
997 entry.last_used = epoch;
998 entry.wanted_epoch = epoch;
999 entry.distance = want.distance;
1000 entry.distance_epoch = epoch;
1001 entry.deadline_ticks = want.deadline_ticks;
1002 self.sync_lock(index);
1003 }
1004
1005 stats.work_used = budget.used;
1006 StepReport {
1007 stats: WindowStats {
1008 evictions: sink.len as u32,
1009 ..stats
1010 },
1011 eviction_count: sink.len,
1012 }
1013 }
1014
1015 fn active_in_flight(&self) -> usize {
1016 self.entries
1017 .iter()
1018 .filter(|entry| {
1019 entry.in_use && entry.state == State::Reading && !entry.cancel_requested
1020 })
1021 .count()
1022 }
1023
1024 fn cancel_least_urgent<H: Host>(
1025 &mut self,
1026 demand: Wanted,
1027 host: &mut H,
1028 stats: &mut WindowStats,
1029 ) -> bool {
1030 let mut worst: Option<(usize, (Class, u32, u32))> = None;
1031 for (index, entry) in self.entries.iter().enumerate() {
1032 if !entry.in_use
1033 || entry.state != State::Reading
1034 || entry.cancel_requested
1035 || entry.pin_count > 0
1036 {
1037 continue;
1038 }
1039 if entry.class <= demand.class {
1040 continue;
1041 }
1042 let rank = (entry.class, entry.deadline_ticks, entry.distance);
1043 if worst.is_none_or(|(_, best)| rank > best) {
1044 worst = Some((index, rank));
1045 }
1046 }
1047 let Some((index, _)) = worst else {
1048 return false;
1049 };
1050 host.cancel(RequestId(self.entries[index].request));
1051 self.entries[index].cancel_requested = true;
1052 stats.cancelled += 1;
1053 true
1054 }
1055
1056 fn entry_pages(&self, index: usize) -> u32 {
1059 let entry = &self.entries[index];
1060 self.pool_of_key(entry.key)
1061 .and_then(|pool| self.pools[pool].handle_of_slot(entry.slot))
1062 .and_then(|handle| self.try_run(handle))
1063 .map_or(0, |run| run.page_count)
1064 }
1065
1066 fn distance_of<H: Host>(&mut self, index: usize, host: &H, budget: &mut Budget) -> u32 {
1067 let entry = &mut self.entries[index];
1068 if entry.distance_epoch != self.epoch {
1069 budget.charge(1);
1070 entry.distance = host.walk_distance(entry.key);
1071 entry.distance_epoch = self.epoch;
1072 }
1073 entry.distance
1074 }
1075
1076 fn victim_tier<H: Host>(
1081 &mut self,
1082 index: usize,
1083 requester: Requester,
1084 host: &H,
1085 budget: &mut Budget,
1086 ) -> Option<u8> {
1087 let entry = self.entries[index];
1088 if !entry.in_use
1089 || !matches!(entry.state, State::Resident | State::Landed)
1090 || entry.pin_count > 0
1091 {
1092 return None;
1093 }
1094 let tier = if entry.wanted_epoch == self.epoch {
1095 if self.config.protect_wanted[entry.class.as_index()] {
1096 return None;
1097 }
1098 let lower = entry.class > requester.class
1099 || (entry.class == requester.class
1100 && entry.distance
1101 > requester
1102 .distance
1103 .saturating_add(self.config.displace_margin));
1104 if !lower {
1105 return None;
1106 }
1107 2
1108 } else {
1109 let distance = self.distance_of(index, host, budget);
1110 let category = entry.key.category().number() as usize;
1111 if distance > self.config.categories[category].keep_radius {
1112 0
1113 } else {
1114 1
1115 }
1116 };
1117 (tier <= self.config.max_victim_tier[requester.class.as_index()]).then_some(tier)
1118 }
1119
1120 fn best_victim<H: Host>(
1123 &mut self,
1124 category: usize,
1125 requester: Requester,
1126 chosen: &[bool; ENTRIES],
1127 host: &H,
1128 budget: &mut Budget,
1129 ) -> Option<usize> {
1130 let mut best: Option<(usize, (u8, u32, u32, u32))> = None;
1131 for (index, &taken) in chosen.iter().enumerate() {
1132 let entry = self.entries[index];
1133 if !entry.in_use
1134 || taken
1135 || entry.key.category().number() as usize != category
1136 || !Self::holds_pages(entry.state)
1137 {
1138 continue;
1139 }
1140 budget.charge(1);
1141 let Some(tier) = self.victim_tier(index, requester, host, budget) else {
1142 continue;
1143 };
1144 let rank = (
1145 tier,
1146 u32::MAX - self.entries[index].distance,
1147 entry.last_used,
1148 entry.key.as_u32(),
1149 );
1150 if best.is_none_or(|(_, best_rank)| rank < best_rank) {
1151 best = Some((index, rank));
1152 }
1153 }
1154 best.map(|(index, _)| index)
1155 }
1156
1157 #[allow(clippy::too_many_arguments)]
1159 fn place<H: Host>(
1160 &mut self,
1161 key: ResourceKey,
1162 requester: Requester,
1163 extent: Extent,
1164 host: &mut H,
1165 sink: &mut EvictionSink<'_>,
1166 budget: &mut Budget,
1167 stats: &mut WindowStats,
1168 ) -> Option<(Handle, PageRun)> {
1169 let category = key.category().number() as usize;
1170 let config = self.config.categories[category];
1171 let pool_number = self.pool_of_key(key)?;
1172 let pages = extent.pages;
1173 if pages == 0
1174 || pages > config.max_pages
1175 || pages > self.pools[pool_number].capacity_pages()
1176 {
1177 stats.over_budget += 1;
1178 return None;
1179 }
1180
1181 let mut chosen = [false; ENTRIES];
1183 let mut chosen_count = 0usize;
1184 let mut held_pages = self.category_pages[category];
1185 let mut held_entries = self.category_entries[category];
1186 while held_pages + pages > config.max_pages || held_entries >= config.max_entries {
1187 let victim = self.best_victim(category, requester, &chosen, host, budget)?;
1188 chosen[victim] = true;
1189 chosen_count += 1;
1190 held_pages -= self.entry_pages(victim);
1191 held_entries -= 1;
1192 }
1193
1194 if chosen_count == 0 && self.pools[pool_number].live_count() < SLOTS {
1196 if let Some(allocation) = self.pools[pool_number].allocate(pages) {
1197 return Some(allocation);
1198 }
1199 while self.pools[pool_number].free_pages() >= pages
1201 && self.pools[pool_number].find_fit(pages).is_none()
1202 && !budget.is_exhausted()
1203 {
1204 let moved = self.pools[pool_number].compact_step(|from, to| {
1205 host.move_pages(pool_number as u8, from.first_page, to, from.page_count);
1206 });
1207 let Some(moved) = moved else { break };
1208 stats.compactions += 1;
1209 stats.pages_moved += moved.from.page_count;
1210 budget.charge(1 + moved.from.page_count / 8);
1211 }
1212 if let Some(allocation) = self.pools[pool_number].allocate(pages) {
1213 return Some(allocation);
1214 }
1215 }
1216
1217 let need_slot = self.pools[pool_number].live_count() >= SLOTS && chosen_count == 0;
1219 let mut info = [EMPTY_RUN_INFO; SLOTS];
1220 let mut live = 0usize;
1221 for (slot, run, _) in self.pools[pool_number].iter_runs() {
1222 let owner = self.owner[pool_number][slot as usize] as usize;
1223 info[live] = RunInfo {
1224 first: run.first_page,
1225 count: run.page_count,
1226 entry: if owner == 0 { NO_ENTRY } else { owner - 1 },
1227 tier: None,
1228 distance: 0,
1229 last_used: 0,
1230 };
1231 live += 1;
1232 }
1233 for run in info.iter_mut().take(live) {
1234 if run.entry == NO_ENTRY {
1235 continue;
1236 }
1237 let tier = if chosen[run.entry] {
1239 Some(0)
1240 } else {
1241 self.victim_tier(run.entry, requester, host, budget)
1242 };
1243 run.tier = tier;
1244 run.distance = self.entries[run.entry].distance;
1245 run.last_used = self.entries[run.entry].last_used;
1246 }
1247 let capacity = self.pools[pool_number].capacity_pages();
1248 let mut best: Option<(WindowCost, usize, usize)> = None;
1249 for start in 0..=live {
1250 let left = if start == 0 {
1251 0
1252 } else {
1253 info[start - 1].first + info[start - 1].count
1254 };
1255 let mut end = start;
1256 let mut max_tier = 0u8;
1257 let mut min_distance = u32::MAX;
1258 let mut max_used = 0u32;
1259 loop {
1260 budget.charge(1);
1261 let right = if end < live {
1262 info[end].first
1263 } else {
1264 capacity
1265 };
1266 let victims = end - start;
1267 if right - left >= pages && (victims > 0 || !need_slot) {
1268 let cost = (
1269 max_tier,
1270 victims as u32,
1271 if victims == 0 {
1272 0
1273 } else {
1274 u32::MAX - min_distance
1275 },
1276 max_used,
1277 left,
1278 );
1279 if best.is_none_or(|(best_cost, _, _)| cost < best_cost) {
1280 best = Some((cost, start, end));
1281 }
1282 break;
1283 }
1284 if end >= live {
1285 break;
1286 }
1287 let Some(tier) = info[end].tier else { break };
1288 max_tier = max_tier.max(tier);
1289 min_distance = min_distance.min(info[end].distance);
1290 max_used = max_used.max(info[end].last_used);
1291 end += 1;
1292 }
1293 }
1294 let (_, start, end) = best?;
1295 for run in &info[start..end] {
1296 if !chosen[run.entry] {
1297 chosen[run.entry] = true;
1298 chosen_count += 1;
1299 }
1300 }
1301 if chosen_count > sink.buffer.len() - sink.len {
1302 stats.eviction_overflow += 1;
1303 return None;
1304 }
1305 for (index, &victim) in chosen.iter().enumerate() {
1306 if victim {
1307 self.evict_entry(index, sink);
1308 }
1309 }
1310 let allocation = self.pools[pool_number].allocate(pages);
1311 debug_assert!(allocation.is_some(), "a window was cleared for this run");
1312 allocation
1313 }
1314
1315 #[cfg(any(test, debug_assertions))]
1317 pub fn check_invariants(&self) {
1318 for pool in &self.pools {
1319 pool.check_invariants();
1320 }
1321 let mut pages = [0u32; CATEGORIES];
1322 let mut entries = [0u16; CATEGORIES];
1323 let mut reading = 0u32;
1324 for (index, entry) in self.entries.iter().enumerate() {
1325 if !entry.in_use {
1326 continue;
1327 }
1328 if entry.state == State::Reading {
1329 reading += 1;
1330 }
1331 if Self::holds_pages(entry.state) {
1332 let pool = self.pool_of_key(entry.key).expect("a configured category");
1333 let handle = self.pools[pool]
1334 .handle_of_slot(entry.slot)
1335 .expect("entry holds a live slot");
1336 let run = self.pools[pool].try_run(handle).unwrap();
1337 assert_eq!(self.owner[pool][entry.slot as usize] as usize, index + 1);
1338 let category = entry.key.category().number() as usize;
1339 pages[category] += run.page_count;
1340 entries[category] += 1;
1341 assert!(run.page_count >= entry.bytes.div_ceil(crate::PAGE_BYTES as u32));
1342 } else {
1343 assert!(
1344 entry.state != State::Resident,
1345 "resident entry without pages"
1346 );
1347 }
1348 for other in &self.entries[index + 1..] {
1350 assert!(!(other.in_use && other.key == entry.key), "duplicate key");
1351 }
1352 }
1353 assert_eq!(pages, self.category_pages);
1354 assert_eq!(entries, self.category_entries);
1355 assert_eq!(reading, self.in_flight);
1356 for category in 0..CATEGORIES {
1357 let config = self.config.categories[category];
1358 assert!(self.category_pages[category] <= config.max_pages);
1359 assert!(self.category_entries[category] <= config.max_entries);
1360 }
1361 for (number, pool) in self.pools.iter().enumerate() {
1362 assert!(pool.used_pages() <= pool.capacity_pages());
1363 let live = self.owner[number]
1364 .iter()
1365 .enumerate()
1366 .filter(|(slot, owner)| **owner != 0 && pool.handle_of_slot(*slot as u16).is_some())
1367 .count();
1368 assert_eq!(live, pool.live_count(), "owner map out of step");
1369 }
1370 }
1371}