use crate::inline::RawInline;
pub fn and_words(words: &mut [u32], other: &[u32]) {
debug_assert_eq!(words.len(), other.len());
for (w, o) in words.iter_mut().zip(other.iter()) {
*w &= *o;
}
}
pub fn or_words(words: &mut [u32], other: &[u32]) {
debug_assert_eq!(words.len(), other.len());
for (w, o) in words.iter_mut().zip(other.iter()) {
*w |= *o;
}
}
pub const LIVENESS_WORD_BITS: usize = 32;
const BITS: usize = LIVENESS_WORD_BITS;
fn words_for(bits: usize) -> usize {
bits.div_ceil(BITS)
}
fn bit_range_mask(lo: usize, hi: usize) -> u32 {
debug_assert!(lo < hi && hi <= BITS);
(u32::MAX >> (BITS - (hi - lo))) << lo
}
fn set_bits(live: &mut [u32], from: usize, to: usize) {
if from >= to {
return;
}
let first = from / BITS;
let last = (to - 1) / BITS;
if first == last {
live[first] |= bit_range_mask(from - first * BITS, to - first * BITS);
return;
}
live[first] |= bit_range_mask(from - first * BITS, BITS);
for w in &mut live[first + 1..last] {
*w = u32::MAX;
}
live[last] |= bit_range_mask(0, to - last * BITS);
}
fn region_mask(bit_offset: usize, len: usize, k: usize) -> u32 {
let word_lo = k * BITS;
let word_hi = word_lo + BITS;
let lo = bit_offset.clamp(word_lo, word_hi);
let hi = (bit_offset + len).clamp(word_lo, word_hi);
if lo >= hi {
0
} else {
bit_range_mask(lo - word_lo, hi - word_lo)
}
}
fn truncate_live(live: &mut Vec<u32>, len: usize) {
live.truncate(words_for(len));
let tail = len % BITS;
if tail != 0 {
let last = live.len() - 1;
live[last] &= bit_range_mask(0, tail);
}
}
#[derive(Clone, Debug, Default)]
pub struct ProposalBuffer {
entries: Vec<RawInline>,
live: Vec<u32>,
parents: Vec<u32>,
parent: u32,
}
impl ProposalBuffer {
pub const fn new() -> Self {
ProposalBuffer {
entries: Vec::new(),
live: Vec::new(),
parents: Vec::new(),
parent: 0,
}
}
pub fn open(&mut self, parent: u32) {
self.parent = parent;
}
pub fn parent_of(&self, i: usize) -> u32 {
self.parents[i]
}
pub fn push(&mut self, value: RawInline) {
let i = self.entries.len();
self.entries.push(value);
self.parents.push(self.parent);
if i % BITS == 0 {
self.live.push(0);
}
self.live[i / BITS] |= 1 << (i % BITS);
}
pub fn extend(&mut self, iter: impl IntoIterator<Item = RawInline>) {
for value in iter {
self.push(value);
}
}
pub fn extend_from_slice(&mut self, slice: &[RawInline]) {
let from = self.entries.len();
self.entries.extend_from_slice(slice);
let to = self.entries.len();
self.parents.resize(to, self.parent);
self.live.resize(words_for(to), 0);
set_bits(&mut self.live, from, to);
}
pub fn clear(&mut self) {
self.entries.clear();
self.live.clear();
self.parents.clear();
self.parent = 0;
}
pub fn capacity(&self) -> usize {
self.entries.capacity()
}
pub fn reserve_exact(&mut self, additional: usize) {
self.entries.reserve_exact(additional);
self.parents.reserve_exact(additional);
let needed = words_for(self.entries.len() + additional);
self.live
.reserve_exact(needed.saturating_sub(self.live.len()));
}
pub fn next_live(&self, from: usize) -> Option<usize> {
if from >= self.entries.len() {
return None;
}
let mut w = from / BITS;
let mut word = self.live[w] & (u32::MAX << (from % BITS));
loop {
if word != 0 {
return Some(w * BITS + word.trailing_zeros() as usize);
}
w += 1;
if w >= self.live.len() {
return None;
}
word = self.live[w];
}
}
pub fn count_live(&self, from: usize) -> usize {
if from >= self.entries.len() {
return 0;
}
let first = from / BITS;
let head = self.live[first] & (u32::MAX << (from % BITS));
head.count_ones() as usize
+ self.live[first + 1..]
.iter()
.map(|w| w.count_ones() as usize)
.sum::<usize>()
}
pub fn is_live(&self, i: usize) -> bool {
debug_assert!(i < self.entries.len(), "liveness read past the buffer");
(self.live[i / BITS] >> (i % BITS)) & 1 != 0
}
pub fn live_values(&self, from: usize) -> impl Iterator<Item = &RawInline> {
let live: &[u32] = &self.live;
self.entries[from..]
.iter()
.enumerate()
.filter_map(move |(k, value)| {
let i = from + k;
((live[i / BITS] >> (i % BITS)) & 1 != 0).then_some(value)
})
}
pub fn region(&mut self, base: usize) -> Candidates<'_> {
let first = base / BITS;
Candidates {
values: &self.entries[base..],
parents: &self.parents[base..],
words: &mut self.live[first..],
bit_offset: base % BITS,
}
}
pub fn tagged(&self, base: usize) -> impl Iterator<Item = (u32, RawInline)> + '_ {
let live: &[u32] = &self.live;
self.parents[base..]
.iter()
.copied()
.zip(self.entries[base..].iter().copied())
.enumerate()
.filter_map(move |(k, pair)| {
let i = base + k;
((live[i / BITS] >> (i % BITS)) & 1 != 0).then_some(pair)
})
}
pub fn retain_region(&mut self, base: usize, mut keep: impl FnMut(u32, &RawInline) -> bool) {
let mut write = base;
for read in base..self.entries.len() {
if keep(self.parents[read], &self.entries[read]) {
self.entries[write] = self.entries[read];
self.parents[write] = self.parents[read];
let bit = (self.live[read / BITS] >> (read % BITS)) & 1;
let w = write / BITS;
let b = write % BITS;
self.live[w] = (self.live[w] & !(1u32 << b)) | (bit << b);
write += 1;
}
}
self.entries.truncate(write);
self.parents.truncate(write);
truncate_live(&mut self.live, write);
}
pub fn rewrite_region(&mut self, base: usize, values: Vec<(u32, RawInline)>) {
self.entries.truncate(base);
self.parents.truncate(base);
truncate_live(&mut self.live, base);
for (parent, value) in values {
let i = self.entries.len();
self.entries.push(value);
self.parents.push(parent);
if i % BITS == 0 {
self.live.push(0);
}
self.live[i / BITS] |= 1 << (i % BITS);
}
}
pub fn remap_region(&mut self, base: usize, map: &[u32]) {
for parent in &mut self.parents[base..] {
*parent = map[*parent as usize];
}
}
}
impl std::ops::Deref for ProposalBuffer {
type Target = [RawInline];
fn deref(&self) -> &[RawInline] {
&self.entries
}
}
impl<'a> IntoIterator for &'a ProposalBuffer {
type Item = &'a RawInline;
type IntoIter = std::slice::Iter<'a, RawInline>;
fn into_iter(self) -> Self::IntoIter {
self.entries.iter()
}
}
pub struct Candidates<'a> {
values: &'a [RawInline],
parents: &'a [u32],
words: &'a mut [u32],
bit_offset: usize,
}
impl<'a> Candidates<'a> {
pub fn values(&self) -> &[RawInline] {
self.values
}
pub fn parent(&self, i: usize) -> u32 {
self.parents[i]
}
pub fn parents(&self) -> &[u32] {
self.parents
}
pub fn for_each_parent(&mut self, mut confirm: impl FnMut(u32, &mut Candidates<'_>)) {
let values = self.values;
let parents = self.parents;
let bit_offset = self.bit_offset;
let mut start = 0;
while start < parents.len() {
let parent = parents[start];
let mut end = start + 1;
while end < parents.len() && parents[end] == parent {
end += 1;
}
let first = (bit_offset + start) / BITS;
let last = (bit_offset + end - 1) / BITS;
let mut run = Candidates {
values: &values[start..end],
parents: &parents[start..end],
words: &mut self.words[first..=last],
bit_offset: (bit_offset + start) % BITS,
};
confirm(parent, &mut run);
start = end;
}
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn is_live(&self, i: usize) -> bool {
debug_assert!(i < self.values.len(), "liveness read past the region");
let b = self.bit_offset + i;
(self.words[b / BITS] >> (b % BITS)) & 1 != 0
}
pub fn kill(&mut self, i: usize) {
debug_assert!(i < self.values.len(), "kill past the region");
let b = self.bit_offset + i;
self.words[b / BITS] &= !(1u32 << (b % BITS));
}
pub fn retain(&mut self, mut keep: impl FnMut(&RawInline) -> bool) {
for i in 0..self.values.len() {
if self.is_live(i) && !keep(&self.values[i]) {
self.kill(i);
}
}
}
pub fn kill_all(&mut self) {
let (offset, len) = (self.bit_offset, self.values.len());
for (k, w) in self.words.iter_mut().enumerate() {
*w &= !region_mask(offset, len, k);
}
}
pub fn reborrow(&mut self) -> Candidates<'_> {
Candidates {
values: self.values,
parents: self.parents,
words: self.words,
bit_offset: self.bit_offset,
}
}
pub fn live_word_len(&self) -> usize {
self.words.len()
}
pub fn bit_offset(&self) -> usize {
self.bit_offset
}
#[cfg(feature = "parallel")]
#[track_caller]
pub(crate) fn split_at_word_boundary(self, mid: usize) -> (Candidates<'a>, Candidates<'a>) {
assert!(mid > 0 && mid < self.values.len(), "split must be interior");
let word_mid_bits = self.bit_offset + mid;
assert!(
word_mid_bits.is_multiple_of(BITS),
"candidate split does not fall on a liveness-word boundary"
);
let word_mid = word_mid_bits / BITS;
let Candidates {
values,
parents,
words,
bit_offset,
} = self;
let (left_values, right_values) = values.split_at(mid);
let (left_parents, right_parents) = parents.split_at(mid);
let (left_words, right_words) = words.split_at_mut(word_mid);
(
Candidates {
values: left_values,
parents: left_parents,
words: left_words,
bit_offset,
},
Candidates {
values: right_values,
parents: right_parents,
words: right_words,
bit_offset: 0,
},
)
}
pub fn live_words(&self) -> Vec<u32> {
let (offset, len) = (self.bit_offset, self.values.len());
self.words
.iter()
.enumerate()
.map(|(k, w)| *w & region_mask(offset, len, k))
.collect()
}
pub fn set_live_words(&mut self, words: &[u32]) {
debug_assert_eq!(words.len(), self.words.len());
let (offset, len) = (self.bit_offset, self.values.len());
for (k, (word, incoming)) in self.words.iter_mut().zip(words.iter()).enumerate() {
let mask = region_mask(offset, len, k);
*word = (*word & !mask) | (*incoming & mask);
}
}
pub fn scratch<'b>(&self, words: &'b mut [u32]) -> Candidates<'b>
where
'a: 'b,
{
debug_assert_eq!(words.len(), self.words.len());
Candidates {
values: self.values,
parents: self.parents,
words,
bit_offset: self.bit_offset,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inline::RawInline;
fn v(i: usize) -> RawInline {
let mut x = [0u8; 32];
x[30] = (i >> 8) as u8;
x[31] = i as u8;
x
}
fn filled(n: usize) -> ProposalBuffer {
let mut b = ProposalBuffer::new();
for i in 0..n {
b.push(v(i));
}
b
}
fn live_indices(b: &ProposalBuffer) -> Vec<usize> {
(0..b.len()).filter(|&i| b.is_live(i)).collect()
}
#[test]
fn push_is_live_across_word_boundaries() {
let b = filled(70);
assert_eq!(b.len(), 70);
assert_eq!(b.count_live(0), 70);
assert_eq!(b.count_live(33), 37);
assert_eq!(b.count_live(70), 0);
assert_eq!(b.next_live(0), Some(0));
assert_eq!(b.next_live(32), Some(32));
assert_eq!(b.next_live(69), Some(69));
assert_eq!(b.next_live(70), None);
}
#[test]
fn extend_from_slice_starts_mid_word() {
let values: Vec<RawInline> = (0..70).map(v).collect();
let mut b = ProposalBuffer::new();
b.push(v(1000));
b.extend_from_slice(&values);
assert_eq!(b.len(), 71);
assert_eq!(b.count_live(0), 71);
assert_eq!(&b[1..], &values[..]);
}
#[test]
fn kills_are_skipped_by_next_and_counted_out() {
let mut b = filled(70);
{
let mut r = b.region(0);
for i in [0usize, 1, 31, 32, 33, 69] {
r.kill(i);
}
}
assert_eq!(b.count_live(0), 64);
assert_eq!(b.next_live(0), Some(2));
assert_eq!(b.next_live(31), Some(34));
assert_eq!(b.next_live(69), None);
assert!(!b.is_live(31));
assert!(b.is_live(34));
}
#[test]
fn live_values_agrees_with_is_live() {
let mut b = filled(70);
{
let mut r = b.region(0);
r.kill(1);
r.kill(33);
r.kill(69);
}
let dead = [1usize, 33, 69];
let got: Vec<RawInline> = b.live_values(0).copied().collect();
let want: Vec<RawInline> = (0..70).filter(|i| !dead.contains(i)).map(v).collect();
assert_eq!(got, want);
let got_from: Vec<RawInline> = b.live_values(33).copied().collect();
let want_from: Vec<RawInline> = (33..70).filter(|i| !dead.contains(i)).map(v).collect();
assert_eq!(got_from, want_from);
}
#[test]
fn kill_all_spares_the_entries_below_the_region() {
let mut b = filled(70);
{
let mut r = b.region(5);
assert_eq!(r.len(), 65);
r.kill_all();
}
assert_eq!(live_indices(&b), (0..5).collect::<Vec<_>>());
}
#[test]
fn kill_all_spares_a_shared_word_below_a_later_base() {
let mut b = filled(70);
{
let mut r = b.region(40);
r.kill_all();
}
assert_eq!(b.count_live(0), 40);
assert!(b.is_live(39));
assert!(!b.is_live(40));
}
#[test]
fn region_indices_are_relative_to_the_base() {
let mut b = filled(70);
{
let mut r = b.region(5);
r.kill(0);
r.kill(27);
}
assert!(!b.is_live(5));
assert!(!b.is_live(32));
assert!(b.is_live(4));
assert!(b.is_live(31));
assert_eq!(b.count_live(0), 68);
}
#[test]
fn retain_spares_dead_neighbours_and_skips_dead_entries() {
let mut b = filled(70);
{
let mut r = b.region(5);
r.retain(|value| value[31] % 2 == 0);
}
let expected: Vec<usize> = (0..5).chain((5..70).filter(|i| i % 2 == 0)).collect();
assert_eq!(live_indices(&b), expected);
}
#[test]
fn live_words_are_zero_outside_the_region() {
let mut b = filled(70);
{
let mut r = b.region(5);
r.kill_all();
}
assert!(b.is_live(0));
let r = b.region(5);
assert!(r.live_words().iter().all(|w| *w == 0));
}
#[test]
fn set_live_words_spares_the_entries_below_the_region() {
let mut b = filled(70);
{
let mut r = b.region(5);
let zeros = vec![0u32; r.live_word_len()];
r.set_live_words(&zeros);
}
assert_eq!(live_indices(&b), (0..5).collect::<Vec<_>>());
}
#[test]
fn live_word_len_describes_the_copies() {
let mut b = filled(70);
for base in [0usize, 1, 5, 31, 32, 33, 63, 64, 69, 70] {
let r = b.region(base);
assert_eq!(r.live_words().len(), r.live_word_len());
}
}
#[test]
fn bit_offset_describes_the_word_geometry() {
let mut b = filled(70);
for base in [0usize, 1, 5, 31, 32, 33, 63, 64, 69, 70] {
let r = b.region(base);
let offset = r.bit_offset();
assert!(
offset < LIVENESS_WORD_BITS,
"bit offset {offset} is not below a word"
);
assert_eq!(offset, base % LIVENESS_WORD_BITS);
assert_eq!(
r.live_word_len(),
(offset + r.len()).div_ceil(LIVENESS_WORD_BITS)
);
}
}
#[cfg(feature = "parallel")]
#[test]
fn word_boundary_split_yields_disjoint_killable_regions() {
let mut b = ProposalBuffer::new();
for row in 0..5u32 {
b.open(row);
for i in 0..20 {
b.push(v(row as usize * 100 + i));
}
}
{
let mut all = b.region(0);
all.kill(40);
}
{
let region = b.region(5);
let (mut left, mut right) = region.split_at_word_boundary(27);
assert_eq!((left.len(), left.bit_offset()), (27, 5));
assert_eq!((right.len(), right.bit_offset()), (68, 0));
assert_eq!((left.live_word_len(), right.live_word_len()), (1, 3));
assert_eq!(left.parent(0), 0);
assert_eq!(left.parent(14), 0);
assert_eq!(left.parent(15), 1);
assert_eq!(right.parent(0), 1);
assert_eq!(right.parent(67), 4);
for i in (0..left.len()).step_by(2) {
left.kill(i);
}
for i in (0..right.len()).step_by(3) {
right.kill(i);
}
}
for i in 0..100 {
let want = if i < 5 {
true
} else if i < 32 {
(i - 5) % 2 != 0
} else {
i != 40 && (i - 32) % 3 != 0
};
assert_eq!(b.is_live(i), want, "entry {i}");
}
}
#[cfg(feature = "parallel")]
#[test]
#[should_panic(expected = "liveness-word boundary")]
fn word_boundary_split_rejects_a_shared_boundary_word() {
let mut b = filled(70);
let region = b.region(5);
let _ = region.split_at_word_boundary(32);
}
#[test]
fn live_words_reconstruct_is_live_bit_by_bit() {
let mut b = filled(70);
{
let mut r = b.region(0);
for i in [0usize, 1, 30, 31, 32, 33, 40, 63, 64, 69] {
r.kill(i);
}
}
for base in [0usize, 1, 5, 31, 32, 33, 63, 64, 69, 70] {
let r = b.region(base);
let words = r.live_words();
let offset = r.bit_offset();
for i in 0..r.len() {
let bit = offset + i;
let reconstructed =
(words[bit / LIVENESS_WORD_BITS] >> (bit % LIVENESS_WORD_BITS)) & 1 != 0;
assert_eq!(
reconstructed,
r.is_live(i),
"entry {i} of region({base}) reconstructs wrong"
);
}
}
}
#[test]
fn or_composition_over_scratch_copies() {
let mut b = filled(70);
{
let mut r = b.region(5);
let mut any = vec![0u32; r.live_word_len()];
for step in [2usize, 3] {
let mut scratch = r.live_words();
{
let mut s = r.scratch(&mut scratch);
for i in 0..s.len() {
if i % step != 0 {
s.kill(i);
}
}
}
or_words(&mut any, &scratch);
}
r.set_live_words(&any);
}
let expected: Vec<usize> = (0..5)
.chain((5..70).filter(|i| (i - 5) % 2 == 0 || (i - 5) % 3 == 0))
.collect();
assert_eq!(live_indices(&b), expected);
}
#[test]
fn and_composition_over_scratch_copies() {
let mut b = filled(70);
{
let mut r = b.region(5);
let mut all = r.live_words();
for step in [2usize, 3] {
let mut scratch = r.live_words();
{
let mut s = r.scratch(&mut scratch);
for i in 0..s.len() {
if i % step != 0 {
s.kill(i);
}
}
}
and_words(&mut all, &scratch);
}
r.set_live_words(&all);
}
let expected: Vec<usize> = (0..5).chain((5..70).filter(|i| (i - 5) % 6 == 0)).collect();
assert_eq!(live_indices(&b), expected);
}
#[test]
fn rewrite_region_drops_the_old_tails_liveness() {
let mut b = filled(70);
{
let mut r = b.region(0);
r.kill(3);
}
b.rewrite_region(5, vec![(0, v(100)), (0, v(101))]);
assert_eq!(b.len(), 7);
assert_eq!(b[5], v(100));
assert_eq!(b[6], v(101));
assert_eq!(b.count_live(0), 6);
assert_eq!(live_indices(&b), vec![0, 1, 2, 4, 5, 6]);
}
#[test]
fn rewrite_region_at_zero_and_at_a_word_boundary() {
let mut b = filled(70);
b.rewrite_region(0, vec![(0, v(1)), (0, v(2))]);
assert_eq!(b.len(), 2);
assert_eq!(b.count_live(0), 2);
let mut c = filled(70);
c.rewrite_region(32, vec![(3, v(1))]);
assert_eq!(c.len(), 33);
assert_eq!(c.count_live(0), 33);
assert_eq!(c.parent_of(32), 3);
}
#[test]
fn tagged_skips_the_dead_at_a_non_aligned_base() {
let mut b = ProposalBuffer::new();
for row in 0..3u32 {
b.open(row);
for i in 0..25 {
b.push(v(row as usize * 100 + i));
}
}
{
let mut r = b.region(5);
for i in [0usize, 27, 40, 69] {
r.kill(i);
}
}
let fresh: Vec<(u32, RawInline)> = b.tagged(5).collect();
assert_eq!(fresh.len(), 70 - 4);
let dead = [5usize, 32, 45, 74];
let want: Vec<(u32, RawInline)> = (5..75)
.filter(|i| !dead.contains(i))
.map(|i| ((i / 25) as u32, v((i / 25) * 100 + i % 25)))
.collect();
assert_eq!(fresh, want);
b.rewrite_region(5, fresh);
assert_eq!(b.len(), 5 + 66);
assert_eq!(b.count_live(0), 5 + 66);
}
#[test]
fn retain_region_moves_liveness_bits_down() {
let mut b = ProposalBuffer::new();
for row in 0..4u32 {
b.open(row);
for i in 0..20 {
b.push(v(row as usize * 100 + i));
}
}
{
let mut r = b.region(0);
for i in [1usize, 34, 60, 79] {
r.kill(i);
}
}
b.retain_region(7, |row, _| row % 2 == 1);
let kept: Vec<usize> = (7..80).filter(|i| (i / 20) % 2 == 1).collect();
assert_eq!(b.len(), 7 + kept.len());
for (k, &source) in kept.iter().enumerate() {
let i = 7 + k;
assert_eq!(b[i], v((source / 20) * 100 + source % 20));
assert_eq!(b.parent_of(i), (source / 20) as u32);
assert_eq!(
b.is_live(i),
![34usize, 60, 79].contains(&source),
"entry {i} (from {source}) has the wrong liveness"
);
}
assert_eq!(live_indices(&b)[..6], [0, 2, 3, 4, 5, 6]);
}
#[test]
fn retain_region_dropping_everything_clears_the_tail() {
let mut b = filled(70);
b.retain_region(5, |_, _| false);
assert_eq!(b.len(), 5);
assert_eq!(b.count_live(0), 5);
b.push(v(999));
assert_eq!(b.len(), 6);
assert_eq!(b.count_live(0), 6);
}
#[test]
fn for_each_parent_runs_do_not_leak_across_their_boundaries() {
let mut b = ProposalBuffer::new();
for row in 0..6u32 {
b.open(row);
for i in 0..11 {
b.push(v(row as usize * 100 + i));
}
}
{
let mut r = b.region(5);
let mut seen = Vec::new();
r.for_each_parent(|row, run| {
seen.push((row, run.len()));
if row % 2 == 1 {
run.kill_all();
}
});
assert_eq!(
seen,
vec![(0, 6), (1, 11), (2, 11), (3, 11), (4, 11), (5, 11)]
);
}
let expected: Vec<usize> = (0..66).filter(|i| (i / 11) % 2 == 0).collect();
assert_eq!(live_indices(&b), expected);
}
#[test]
fn for_each_parent_runs_carry_their_own_bit_offset() {
let mut b = ProposalBuffer::new();
for row in 0..4u32 {
b.open(row);
for i in 0..17 {
b.push(v(row as usize * 100 + i));
}
}
{
let mut r = b.region(3);
let mut base = 3usize;
r.for_each_parent(|_, run| {
assert_eq!(run.bit_offset(), base % LIVENESS_WORD_BITS);
assert_eq!(
run.live_word_len(),
(run.bit_offset() + run.len()).div_ceil(LIVENESS_WORD_BITS)
);
let mut scratch = run.live_words();
{
let mut s = run.scratch(&mut scratch);
for i in (0..s.len()).step_by(2) {
s.kill(i);
}
}
run.set_live_words(&scratch);
base += run.len();
});
}
let starts = [0usize, 14, 31, 48];
for i in 0..68 {
let want = if i < 3 {
true
} else {
let r = i - 3;
let run = starts.iter().rposition(|&s| s <= r).unwrap();
(r - starts[run]) % 2 == 1
};
assert_eq!(b.is_live(i), want, "entry {i}");
}
}
#[test]
fn empty_region_at_the_end_is_inert() {
let mut b = filled(64);
{
let mut r = b.region(64);
assert_eq!(r.len(), 0);
assert!(r.is_empty());
r.kill_all();
assert!(r.live_words().iter().all(|w| *w == 0));
}
assert_eq!(b.count_live(0), 64);
}
#[test]
fn clear_resets_liveness() {
let mut b = filled(70);
{
let mut r = b.region(0);
r.kill_all();
}
b.clear();
assert_eq!(b.len(), 0);
assert_eq!(b.count_live(0), 0);
for i in 0..70 {
b.push(v(i));
}
assert_eq!(b.count_live(0), 70);
}
#[test]
fn reserve_exact_keeps_contents() {
let mut b = filled(70);
{
let mut r = b.region(0);
r.kill(5);
}
b.reserve_exact(1000);
assert_eq!(b.count_live(0), 69);
assert!(!b.is_live(5));
b.push(v(70));
assert_eq!(b.count_live(0), 70);
assert!(b.is_live(70));
}
}