pub(crate) const GROW_AHEAD: usize = 16 << 20;
pub(crate) const PAGE_BYTES: usize = 1 << 16;
pub(crate) const HEAP_LIMIT_PAGES: usize = 1 << 15;
#[inline]
pub(crate) const fn pages_below(addr: usize) -> usize {
addr.div_ceil(PAGE_BYTES)
}
#[inline]
pub(crate) fn fits(brk: usize, own_end: usize, increment: usize) -> Option<usize> {
brk.checked_add(increment).filter(|&end| end <= own_end)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Grow {
pub need: usize,
pub ahead: usize,
}
pub(crate) fn grow_plan(brk: usize, own_end: usize, increment: usize, now: usize) -> Option<Grow> {
let need = if now.checked_mul(PAGE_BYTES) == Some(own_end) {
pages_below(brk.checked_add(increment)?).checked_sub(now)?
} else {
pages_below(increment)
};
let room = HEAP_LIMIT_PAGES.saturating_sub(now);
if need == 0 || need > room {
return None;
}
Some(Grow {
need,
ahead: need.max(GROW_AHEAD / PAGE_BYTES).min(room),
})
}
#[cfg(test)]
mod tests {
use super::*;
const MIB: usize = 1 << 20;
const AHEAD_PAGES: usize = GROW_AHEAD / PAGE_BYTES;
const INIT_PAGES: usize = 16384;
const HEAP_END: usize = 1001 * PAGE_BYTES;
const RESERVE_END: usize = INIT_PAGES * PAGE_BYTES;
#[test]
fn increment_inside_the_reserve_does_not_grow() {
assert_eq!(
fits(HEAP_END, RESERVE_END, 64 * MIB),
Some(HEAP_END + 64 * MIB)
);
assert_eq!(
fits(HEAP_END, RESERVE_END, RESERVE_END - HEAP_END),
Some(RESERVE_END)
);
assert_eq!(
fits(HEAP_END, RESERVE_END, RESERVE_END - HEAP_END + 1),
None
);
}
#[test]
fn crossing_the_reserve_end_grows_the_part_past_it() {
let brk = RESERVE_END - 4 * MIB;
assert_eq!(
grow_plan(brk, RESERVE_END, 5 * MIB, INIT_PAGES),
Some(Grow {
need: 16,
ahead: AHEAD_PAGES
})
);
assert_eq!(
grow_plan(RESERVE_END - 8, RESERVE_END, 9, INIT_PAGES),
Some(Grow {
need: 1,
ahead: AHEAD_PAGES
})
);
assert_eq!(
grow_plan(brk, RESERVE_END, 4 * MIB + 32 * MIB + 1, INIT_PAGES),
Some(Grow {
need: 513,
ahead: 513
})
);
}
#[test]
fn increment_larger_than_room_fits_when_the_part_past_the_end_does() {
let room = HEAP_LIMIT_PAGES - INIT_PAGES;
let increment = 17000 * PAGE_BYTES;
assert!(pages_below(increment) > room);
assert_eq!(
grow_plan(HEAP_END, RESERVE_END, increment, INIT_PAGES),
Some(Grow {
need: 1617,
ahead: 1617
})
);
let to_limit = HEAP_LIMIT_PAGES * PAGE_BYTES - HEAP_END;
assert_eq!(
grow_plan(HEAP_END, RESERVE_END, to_limit, INIT_PAGES),
Some(Grow {
need: room,
ahead: room
})
);
assert_eq!(
grow_plan(HEAP_END, RESERVE_END, to_limit + 1, INIT_PAGES),
None
);
}
#[test]
fn rolldown_heap_ceiling_holds() {
for (hold_mib, need) in [(1000, 617), (1100, 2217), (1536, 9193)] {
let increment = hold_mib * MIB;
let plan = grow_plan(HEAP_END, RESERVE_END, increment, INIT_PAGES).unwrap();
assert_eq!(plan, Grow { need, ahead: need }, "hold {hold_mib} MiB");
assert_eq!(
(INIT_PAGES + plan.need) * PAGE_BYTES,
HEAP_END + increment,
"hold {hold_mib} MiB"
);
}
let grown_end = (INIT_PAGES + 16000) * PAGE_BYTES;
assert_eq!(
grow_plan(grown_end, grown_end, 1536 * MIB, INIT_PAGES + 16000),
None
);
}
#[test]
fn foreign_growth_grows_the_whole_increment() {
let now = INIT_PAGES + 100;
assert_eq!(
grow_plan(RESERVE_END - 4 * MIB, RESERVE_END, 5 * MIB, now),
Some(Grow {
need: 80,
ahead: AHEAD_PAGES
})
);
let room = HEAP_LIMIT_PAGES - now;
assert_eq!(
grow_plan(HEAP_END, RESERVE_END, (room + 1) * PAGE_BYTES, now),
None
);
}
#[test]
fn never_grows_past_two_gib() {
let limit = HEAP_LIMIT_PAGES * PAGE_BYTES;
assert_eq!(grow_plan(limit - 8, limit, 16, HEAP_LIMIT_PAGES), None);
assert_eq!(grow_plan(limit, limit, 16, HEAP_LIMIT_PAGES + 10), None);
let now = HEAP_LIMIT_PAGES - 10;
let end = now * PAGE_BYTES;
assert_eq!(
grow_plan(end, end, 1, now),
Some(Grow { need: 1, ahead: 10 })
);
assert_eq!(grow_plan(end, end, usize::MAX, now), None);
}
}