1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
//! Data types for visiting metadata ranges at different granularities.
//!
//! Currently, the `break_bit_range` function can break a bit range into sub-ranges of whole bytes
//! and in-byte bits.
//!
//! TODO:
//!
//! - Add a function to break a byte range into sub-ranges of whole words and in-word bytes.
//! - And use it for searching side metadata for non-zero bits.
//! - Add a function to break a byte range at chunk boundaries.
//! - And use it for visiting discontiguous side metadata in bulk.
use crate::util::Address;
/// The type for bit offset in a byte.
pub type BitOffset = u8;
/// A range of bytes or bits within a byte. It is the unit of visiting a contiguous bit range of a
/// side metadata.
///
/// In general, a bit range of a bitmap starts with multiple bits in the byte, followed by many
/// whole bytes, and ends with multiple bits in the last byte.
///
/// A range is never empty.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitByteRange {
/// A range of whole bytes.
Bytes {
/// The starting address (inclusive) of the bytes.
start: Address,
/// The ending address (exclusive) of the bytes.
end: Address,
},
/// A range of bits within a byte.
BitsInByte {
/// The address of the byte.
addr: Address,
/// The starting bit index (inclusive), starting with zero from the low-order bit.
bit_start: BitOffset,
/// The ending bit index (exclusive), starting with zero from the low-order bit. This may
/// be 8 which means the range includes the highest bit. Be careful when shifting a `u8`
/// value because shifting an `u8` by 8 is considered an overflow in Rust.
bit_end: BitOffset,
},
}
/// Break a bit range into sub-ranges of whole bytes and in-byte bits.
///
/// This method is primarily used for iterating side metadata for a data address range. As we cannot
/// guarantee that the data address range can be mapped to whole metadata bytes, we have to deal
/// with visiting only a bit range in a metadata byte.
///
/// The bit range starts at the bit at index `start_bit` in the byte at address `start_addr`, and
/// ends at (but does not include) the bit at index `end_bit` in the byte at address `end_addr`.
///
/// Arguments:
/// * `forwards`: If true, we iterate forwards (from start/low address to end/high address).
/// Otherwise, we iterate backwards (from end/high address to start/low address).
/// * `visitor`: The callback that visits ranges of bits or bytes. It returns whether the
/// itertion is early terminated.
///
/// Returns true if we iterate through every bits in the range. Return false if we abort iteration
/// early.
pub fn break_bit_range<V>(
start_addr: Address,
start_bit: BitOffset,
end_addr: Address,
end_bit: BitOffset,
forwards: bool,
visitor: &mut V,
) -> bool
where
V: FnMut(BitByteRange) -> bool,
{
// The start and the end are the same, we don't need to do anything.
if start_addr == end_addr && start_bit == end_bit {
return false;
}
// If the range is already byte-aligned, visit the entire range as whole bytes.
if start_bit == 0 && end_bit == 0 {
return visitor(BitByteRange::Bytes {
start: start_addr,
end: end_addr,
});
}
// If the start and the end are within the same byte,
// visit the bit range within the byte.
if start_addr == end_addr {
return visitor(BitByteRange::BitsInByte {
addr: start_addr,
bit_start: start_bit,
bit_end: end_bit,
});
}
// If the end is the 0th bit of the next byte of the start,
// visit the bit range from the start to the end (bit 8) of the same byte.
if start_addr + 1usize == end_addr && end_bit == 0 {
return visitor(BitByteRange::BitsInByte {
addr: start_addr,
bit_start: start_bit,
bit_end: 8_u8,
});
}
// Otherwise, the range spans over multiple bytes, and is bit-unaligned at either the start or
// the end. Try to break it into (at most) three sub-ranges.
let start_aligned = start_bit == 0;
let end_aligned = end_bit == 0;
// We cannot let multiple closures capture `visitor` mutably at the same time, so we pass the
// visitor in as `v` every time.
// visit bits within the first byte
let visit_start = |v: &mut V| {
if !start_aligned {
v(BitByteRange::BitsInByte {
addr: start_addr,
bit_start: start_bit,
bit_end: 8_u8,
})
} else {
// The start is already aligned. No sub-byte range at the start.
false
}
};
// visit whole bytes in the middle
let visit_middle = |v: &mut V| {
let start = if start_aligned {
start_addr
} else {
// If the start is not aligned, the whole-byte range starts after the first byte.
start_addr + 1usize
};
let end = end_addr;
if start < end {
v(BitByteRange::Bytes { start, end })
} else {
// There are no whole bytes in the middle.
false
}
};
// visit bits within the last byte
let visit_end = |v: &mut V| {
if !end_aligned {
v(BitByteRange::BitsInByte {
addr: end_addr,
bit_start: 0_u8,
bit_end: end_bit,
})
} else {
// The end is aligned. No sub-byte range at the end.
false
}
};
// Visit the three segments forwards or backwards.
if forwards {
visit_start(visitor) || visit_middle(visitor) || visit_end(visitor)
} else {
visit_end(visitor) || visit_middle(visitor) || visit_start(visitor)
}
}
#[cfg(test)]
mod tests {
use crate::util::constants::BITS_IN_BYTE;
use super::*;
fn mk_addr(addr: usize) -> Address {
unsafe { Address::from_usize(addr) }
}
fn break_bit_range_wrapped(
start_addr: Address,
start_bit: usize,
end_addr: Address,
end_bit: usize,
) -> Vec<BitByteRange> {
let mut vec = vec![];
break_bit_range(
start_addr,
start_bit as u8,
end_addr,
end_bit as u8,
true,
&mut |range| {
vec.push(range);
false
},
);
vec
}
#[test]
fn test_empty_range() {
let base = mk_addr(0x1000);
for bit in 0..BITS_IN_BYTE {
let result = break_bit_range_wrapped(base, bit, base, bit);
assert!(
result.is_empty(),
"Not empty. bit: {bit}, result: {result:?}"
);
}
}
#[test]
fn test_subbyte_range() {
let base = mk_addr(0x1000);
for bit0 in 0..BITS_IN_BYTE {
for bit1 in (bit0 + 1)..BITS_IN_BYTE {
let result = break_bit_range_wrapped(base, bit0, base, bit1);
assert_eq!(
result,
vec![BitByteRange::BitsInByte {
addr: base,
bit_start: bit0 as u8,
bit_end: bit1 as u8
}],
"Not equal. bit0: {bit0}, bit1: {bit1}",
);
}
}
}
#[test]
fn test_end_byte_range() {
let base = mk_addr(0x1000);
for bit0 in 1..BITS_IN_BYTE {
let result = break_bit_range_wrapped(base, bit0, base + 1usize, 0);
assert_eq!(
result,
vec![BitByteRange::BitsInByte {
addr: base,
bit_start: bit0 as u8,
bit_end: BITS_IN_BYTE as u8
}],
"Not equal. bit0: {bit0}",
);
}
}
#[test]
fn test_adjacent_grain_range() {
let base = mk_addr(0x1000);
for bit0 in 1..BITS_IN_BYTE {
for bit1 in 1..BITS_IN_BYTE {
let result = break_bit_range_wrapped(base, bit0, base + 1usize, bit1);
assert_eq!(
result,
vec![
BitByteRange::BitsInByte {
addr: base,
bit_start: bit0 as u8,
bit_end: BITS_IN_BYTE as u8,
},
BitByteRange::BitsInByte {
addr: base + 1usize,
bit_start: 0,
bit_end: bit1 as u8,
},
],
"Not equal. bit0: {bit0}, bit1: {bit1}",
);
}
}
}
#[test]
fn test_left_and_whole_range() {
let base = mk_addr(0x1000);
for bit0 in 1..BITS_IN_BYTE {
for byte1 in 2usize..8 {
let result = break_bit_range_wrapped(base, bit0, base + byte1, 0);
assert_eq!(
result,
vec![
BitByteRange::BitsInByte {
addr: base,
bit_start: bit0 as u8,
bit_end: BITS_IN_BYTE as u8,
},
BitByteRange::Bytes {
start: base + 1usize,
end: base + byte1,
},
],
"Not equal. bit0: {bit0}, byte1: {byte1}",
);
}
}
}
#[test]
fn test_whole_and_right_range() {
let base = mk_addr(0x1000);
for byte0 in 1..8 {
for bit1 in 1..BITS_IN_BYTE {
let result = break_bit_range_wrapped(base - byte0, 0, base, bit1);
assert_eq!(
result,
vec![
BitByteRange::Bytes {
start: base - byte0,
end: base,
},
BitByteRange::BitsInByte {
addr: base,
bit_start: 0,
bit_end: bit1 as u8,
},
],
"Not equal. byte0: {byte0}, bit1: {bit1}",
);
}
}
}
#[test]
fn test_whole_range() {
let base = mk_addr(0x1000);
let result = break_bit_range_wrapped(base, 0, base + 42usize, 0);
assert_eq!(
result,
vec![BitByteRange::Bytes {
start: base,
end: base + 42usize,
},],
);
}
#[test]
fn test_left_whole_right_range() {
let base0 = mk_addr(0x1000);
let base1 = mk_addr(0x2000);
for bit0 in 1..BITS_IN_BYTE {
for bit1 in 1..BITS_IN_BYTE {
let result = break_bit_range_wrapped(base0 - 1usize, bit0, base1, bit1);
assert_eq!(
result,
vec![
BitByteRange::BitsInByte {
addr: base0 - 1usize,
bit_start: bit0 as u8,
bit_end: BITS_IN_BYTE as u8,
},
BitByteRange::Bytes {
start: base0,
end: base1,
},
BitByteRange::BitsInByte {
addr: base1,
bit_start: 0,
bit_end: bit1 as u8,
},
],
"Not equal. bit0: {bit0}, bit1: {bit1}",
);
}
}
}
}