mmtk/util/metadata/side_metadata/
helpers.rs

1use super::ranges::BitOffset;
2use super::SideMetadataSpec;
3use crate::util::constants::LOG_BYTES_IN_PAGE;
4use crate::util::constants::{BITS_IN_WORD, BYTES_IN_PAGE, LOG_BITS_IN_BYTE};
5use crate::util::conversions::rshift_align_up;
6use crate::util::heap::layout::vm_layout::VMLayout;
7#[cfg(target_pointer_width = "32")]
8use crate::util::metadata::side_metadata::address_to_chunked_meta_address;
9use crate::util::os::*;
10use crate::util::Address;
11use crate::MMAPPER;
12
13/// Performs address translation in contiguous metadata spaces (e.g. global and policy-specific in 64-bits, and global in 32-bits)
14pub(super) fn address_to_contiguous_meta_address(
15    metadata_spec: &SideMetadataSpec,
16    data_addr: Address,
17) -> Address {
18    let log_bits_num = metadata_spec.log_num_of_bits as i32;
19    let log_bytes_in_region = metadata_spec.log_bytes_in_region;
20
21    let shift = (LOG_BITS_IN_BYTE as i32) - log_bits_num;
22
23    if shift >= 0 {
24        metadata_spec.get_starting_address() + ((data_addr >> log_bytes_in_region) >> shift)
25    } else {
26        metadata_spec.get_starting_address() + ((data_addr >> log_bytes_in_region) << (-shift))
27    }
28}
29
30/// Performs reverse address translation from contiguous metadata bits to data addresses.
31/// The input address and bit shift should be aligned.
32///
33/// Arguments:
34/// * `metadata_spec`: The side metadata spec. It should be contiguous side metadata.
35/// * `metadata_addr`; The metadata address. Returned by [`address_to_contiguous_meta_address`].
36/// * `bit`: The bit shift for the metadata. Returned by [`meta_byte_lshift`].
37pub(super) fn contiguous_meta_address_to_address(
38    metadata_spec: &SideMetadataSpec,
39    metadata_addr: Address,
40    bit: u8,
41) -> Address {
42    debug_assert_eq!(
43        align_metadata_address(metadata_spec, metadata_addr, bit),
44        (metadata_addr, bit)
45    );
46    let shift = (LOG_BITS_IN_BYTE as i32) - metadata_spec.log_num_of_bits as i32;
47    let relative_meta_addr = metadata_addr - metadata_spec.get_starting_address();
48
49    let data_addr_intermediate = if shift >= 0 {
50        relative_meta_addr << shift
51    } else {
52        relative_meta_addr >> (-shift)
53    };
54    let data_addr_bit_shift = if shift >= 0 {
55        metadata_spec.log_bytes_in_region - metadata_spec.log_num_of_bits
56    } else {
57        metadata_spec.log_bytes_in_region
58    };
59
60    let data_addr = (data_addr_intermediate << metadata_spec.log_bytes_in_region)
61        + ((bit as usize) << data_addr_bit_shift);
62
63    unsafe { Address::from_usize(data_addr) }
64}
65
66/// Align an pair of a metadata address and a metadata bit offset to the start of this metadata value.
67/// For example, when the metadata is 4 bits, it should only start at bit 0 or bit 4.
68/// When the metadata is 16 bits, it should only start at bit 0, and its metadata address should be aligned to 2 bytes.
69/// This is important, as [`contiguous_meta_address_to_address`] can only convert the start address of metadata to
70/// the data address.
71pub(super) fn align_metadata_address(
72    spec: &SideMetadataSpec,
73    metadata_addr: Address,
74    bit: u8,
75) -> (Address, u8) {
76    if spec.log_num_of_bits >= LOG_BITS_IN_BYTE as usize {
77        (
78            metadata_addr.align_down(1 << (spec.log_num_of_bits - LOG_BITS_IN_BYTE as usize)),
79            0,
80        )
81    } else {
82        (
83            metadata_addr,
84            crate::util::conversions::raw_align_down(
85                bit as usize,
86                (1 << spec.log_num_of_bits) as usize,
87            ) as u8,
88        )
89    }
90}
91
92/// Unmaps the specified metadata range, or panics.
93#[cfg(test)]
94pub(crate) fn ensure_munmap_metadata(start: Address, size: usize) {
95    trace!("ensure_munmap_metadata({}, 0x{:x})", start, size);
96
97    assert!(OS::munmap(start, size).is_ok())
98}
99
100/// Unmaps a metadata space (`spec`) for the specified data address range (`start` and `size`)
101/// Returns the size in bytes that get munmapped.
102#[cfg(test)]
103pub(crate) fn ensure_munmap_contiguous_metadata_space(
104    start: Address,
105    size: usize,
106    spec: &SideMetadataSpec,
107) -> usize {
108    // nearest page-aligned starting address
109    let metadata_start = address_to_meta_address(spec, start);
110    let mmap_start = metadata_start.align_down(BYTES_IN_PAGE);
111    // nearest page-aligned ending address
112    let metadata_size = data_to_meta_size_round_up(spec, size);
113    let mmap_size = (metadata_start + metadata_size).align_up(BYTES_IN_PAGE) - mmap_start;
114    if mmap_size > 0 {
115        ensure_munmap_metadata(mmap_start, mmap_size);
116    }
117    mmap_size
118}
119
120/// Tries to mmap the metadata space (`spec`) for the specified data address range (`start` and `size`).
121/// Setting `no_reserve` to true means the function will only map address range, without reserving swap-space/physical memory.
122/// Returns the size in bytes that gets mmapped in the function if success.
123pub(super) fn try_mmap_contiguous_metadata_space(
124    start: Address,
125    size: usize,
126    spec: &SideMetadataSpec,
127    no_reserve: bool,
128    anno: &MmapAnnotation,
129) -> MmapResult<usize> {
130    debug_assert!(start.is_aligned_to(BYTES_IN_PAGE));
131    debug_assert!(size % BYTES_IN_PAGE == 0);
132
133    // nearest page-aligned starting address
134    let metadata_start = address_to_meta_address(spec, start);
135    let mmap_start = metadata_start.align_down(BYTES_IN_PAGE);
136    // nearest page-aligned ending address
137    let metadata_size = data_to_meta_size_round_up(spec, size);
138    let mmap_size = (metadata_start + metadata_size).align_up(BYTES_IN_PAGE) - mmap_start;
139    if mmap_size > 0 {
140        if !no_reserve {
141            MMAPPER.ensure_mapped(
142                mmap_start,
143                mmap_size >> LOG_BYTES_IN_PAGE,
144                HugePageSupport::No,
145                MmapProtection::ReadWrite,
146                anno,
147            )
148        } else {
149            MMAPPER.quarantine_address_range(
150                mmap_start,
151                mmap_size >> LOG_BYTES_IN_PAGE,
152                HugePageSupport::No,
153                anno,
154            )
155        }
156        .map(|_| mmap_size)
157        .inspect_err(|_| {
158            warn!(
159                "Failed to mmap metadata space: {} - {}",
160                mmap_start,
161                mmap_start + mmap_size
162            );
163        })
164    } else {
165        Ok(0)
166    }
167}
168
169/// Performs the translation of data address (`data_addr`) to metadata address for the specified metadata (`metadata_spec`).
170pub(crate) fn address_to_meta_address(
171    metadata_spec: &SideMetadataSpec,
172    data_addr: Address,
173) -> Address {
174    #[cfg(target_pointer_width = "32")]
175    let res = {
176        if metadata_spec.is_global {
177            address_to_contiguous_meta_address(metadata_spec, data_addr)
178        } else {
179            address_to_chunked_meta_address(metadata_spec, data_addr)
180        }
181    };
182    #[cfg(target_pointer_width = "64")]
183    let res = { address_to_contiguous_meta_address(metadata_spec, data_addr) };
184
185    trace!(
186        "address_to_meta_address({:?}, addr: {}) -> 0x{:x}",
187        metadata_spec,
188        data_addr,
189        res
190    );
191
192    res
193}
194
195/// Return the base-2 logarithm of the ratio of data bits and metadata bits per region.
196///
197/// Suppose a memory region has `data_bits` bits of data, and `meta_bits` bits of metadata for
198/// `metadata_spec`, and the result of `log_data_meta_ratio(metadata_spec)` is `shift`, then
199///
200/// -   `data_bits >> shift == meta_bits`
201/// -   `meta_bits << shift == data_bits`
202pub(super) const fn log_data_meta_ratio(metadata_spec: &SideMetadataSpec) -> usize {
203    let log_data_bits_in_region = (LOG_BITS_IN_BYTE as usize) + metadata_spec.log_bytes_in_region;
204    let log_meta_bits_in_region = metadata_spec.log_num_of_bits;
205
206    // TODO: In theory, it is possible to construct a side metadata that has more metadata bits than
207    // data bits per region.  But such pathological side metadata consumes way too much memory, and
208    // should never be used in any useful applications.  It should be forbidden.
209    log_data_bits_in_region - log_meta_bits_in_region
210}
211
212/// Calculate the amount of metadata needed for the give amount of data memory, round up to nearest
213/// integer. `data_size` can be in any unit, e.g. bits, bytes, pages, blocks, chunks, etc., and the
214/// result has the same unit.
215pub(super) const fn data_to_meta_size_round_up(
216    metadata_spec: &SideMetadataSpec,
217    data_size: usize,
218) -> usize {
219    rshift_align_up(data_size, log_data_meta_ratio(metadata_spec))
220}
221
222/// Calculate the amount of data governed by the give amount of metadata.  `meta_size` can be in any
223/// unit, e.g. bits, bytes, pages, blocks, chunks, etc., and the result has the same unit.
224pub(super) const fn meta_to_data_size(metadata_spec: &SideMetadataSpec, meta_size: usize) -> usize {
225    meta_size << log_data_meta_ratio(metadata_spec)
226}
227
228#[allow(dead_code)]
229pub(super) const fn metadata_address_range_size(metadata_spec: &SideMetadataSpec) -> usize {
230    1usize << (VMLayout::LOG_ARCH_ADDRESS_SPACE - log_data_meta_ratio(metadata_spec))
231}
232
233pub(super) fn meta_byte_lshift(metadata_spec: &SideMetadataSpec, data_addr: Address) -> u8 {
234    let bits_num_log = metadata_spec.log_num_of_bits as i32;
235    if bits_num_log >= 3 {
236        return 0;
237    }
238    let rem_shift = BITS_IN_WORD as i32 - ((LOG_BITS_IN_BYTE as i32) - bits_num_log);
239    ((((data_addr >> metadata_spec.log_bytes_in_region) << rem_shift) >> rem_shift) << bits_num_log)
240        as u8
241}
242
243pub(super) fn meta_byte_mask(metadata_spec: &SideMetadataSpec) -> u8 {
244    let bits_num_log = metadata_spec.log_num_of_bits;
245    ((1usize << (1usize << bits_num_log)) - 1) as u8
246}
247
248/// The result type for find meta bits functions.
249pub enum FindMetaBitResult {
250    Found { addr: Address, bit: u8 },
251    NotFound,
252    UnmappedMetadata,
253}
254
255// Check and find the last bit that is set. We try load words where possible, and fall back to load bytes.
256pub fn find_last_non_zero_bit_in_metadata_bytes(
257    meta_start: Address,
258    meta_end: Address,
259) -> FindMetaBitResult {
260    use crate::util::constants::BYTES_IN_ADDRESS;
261
262    let mmap_granularity = MMAPPER.granularity();
263
264    let mut cur = meta_end;
265    // We need to check if metadata address is mapped or not.  But we make use of the granularity of
266    // the `Mmapper` to reduce the number of checks.  This records the start of a grain that is
267    // tested to be mapped.
268    let mut mapped_grain = Address::MAX;
269    while cur > meta_start {
270        // If we can check the whole word, set step to word size. Otherwise, the step is 1 (byte) and we check byte.
271        let step = if cur.is_aligned_to(BYTES_IN_ADDRESS) && cur - BYTES_IN_ADDRESS >= meta_start {
272            BYTES_IN_ADDRESS
273        } else {
274            1
275        };
276        // Move to the address so we can load from it
277        cur -= step;
278        // The value we check has to be in the range.
279        debug_assert!(
280            cur >= meta_start && cur < meta_end,
281            "Check metadata value at meta address {}, which is not in the range of [{}, {})",
282            cur,
283            meta_start,
284            meta_end
285        );
286
287        // If we are looking at an address that is not in a mapped chunk, we need to check if the chunk if mapped.
288        if cur < mapped_grain {
289            if cur.is_mapped() {
290                // This is mapped. No need to check for this chunk.
291                mapped_grain = cur.align_down(mmap_granularity);
292            } else {
293                return FindMetaBitResult::UnmappedMetadata;
294            }
295        }
296
297        if step == BYTES_IN_ADDRESS {
298            // Load and check a usize word
299            let value = unsafe { cur.load::<usize>() };
300            if value != 0 {
301                let bit = find_last_non_zero_bit::<usize>(value, 0, usize::BITS as u8).unwrap();
302                let byte_offset = bit >> LOG_BITS_IN_BYTE;
303                let bit_offset = bit - ((byte_offset) << LOG_BITS_IN_BYTE);
304                return FindMetaBitResult::Found {
305                    addr: cur + byte_offset as usize,
306                    bit: bit_offset,
307                };
308            }
309        } else {
310            // Load and check a byte
311            let value = unsafe { cur.load::<u8>() };
312            if let Some(bit) = find_last_non_zero_bit::<u8>(value, 0, 8) {
313                return FindMetaBitResult::Found { addr: cur, bit };
314            }
315        }
316    }
317    FindMetaBitResult::NotFound
318}
319
320// Check and find the last non-zero bit in the same byte.
321pub fn find_last_non_zero_bit_in_metadata_bits(
322    addr: Address,
323    start_bit: u8,
324    end_bit: u8,
325) -> FindMetaBitResult {
326    if !addr.is_mapped() {
327        return FindMetaBitResult::UnmappedMetadata;
328    }
329    let byte = unsafe { addr.load::<u8>() };
330    if let Some(bit) = find_last_non_zero_bit::<u8>(byte, start_bit, end_bit) {
331        return FindMetaBitResult::Found { addr, bit };
332    }
333    FindMetaBitResult::NotFound
334}
335
336use num_traits::{CheckedShl, PrimInt};
337fn find_last_non_zero_bit<T>(value: T, start: u8, end: u8) -> Option<u8>
338where
339    T: PrimInt + CheckedShl,
340{
341    let mask = match T::one().checked_shl((end - start) as u32) {
342        Some(shl) => (shl - T::one()) << (start as u32),
343        None => T::max_value() << (start as u32),
344    };
345    let masked = value & mask;
346    if masked.is_zero() {
347        None
348    } else {
349        let leading_zeroes = masked.leading_zeros();
350        let total_bits = std::mem::size_of::<T>() * u8::BITS as usize;
351        Some(total_bits as u8 - leading_zeroes as u8 - 1)
352    }
353}
354
355pub fn find_first_non_zero_bit_in_metadata_bytes(
356    meta_start: Address,
357    meta_end: Address,
358) -> FindMetaBitResult {
359    use crate::util::constants::BYTES_IN_ADDRESS;
360
361    let mmap_granularity = MMAPPER.granularity();
362
363    let mut cursor = meta_start;
364    // We need to check if metadata address is mapped or not.  But we make use of the granularity of
365    // the `Mmapper` to reduce the number of checks.  This records the start of a grain that is
366    // tested to be mapped.
367    let mut mapped_grain = Address::ZERO;
368    while cursor < meta_end {
369        // If we can check the whole word, set step to word size. Otherwise, the step is 1 (byte) and we check byte.
370        let step =
371            if cursor.is_aligned_to(BYTES_IN_ADDRESS) && cursor + BYTES_IN_ADDRESS <= meta_end {
372                BYTES_IN_ADDRESS
373            } else {
374                1
375            };
376        // The value we check has to be in the range.
377        debug_assert!(
378            cursor >= meta_start && cursor < meta_end,
379            "Check metadata value at meta address {}, which is not in the range of [{}, {})",
380            cursor,
381            meta_start,
382            meta_end
383        );
384
385        // If we are looking at an address that is not in a mapped chunk, we need to check if the chunk if mapped.
386        if cursor > mapped_grain {
387            if cursor.is_mapped() {
388                // This is mapped. No need to check for this chunk.
389                mapped_grain = cursor.align_up(mmap_granularity) - 0x1;
390            } else {
391                return FindMetaBitResult::UnmappedMetadata;
392            }
393        }
394
395        if step == BYTES_IN_ADDRESS {
396            // Load and check a usize word
397            let value = unsafe { cursor.load::<usize>() };
398            if value != 0 {
399                let bit = find_first_non_zero_bit::<usize>(value, 0, usize::BITS as u8).unwrap();
400                let byte_offset = bit >> LOG_BITS_IN_BYTE;
401                let bit_offset = bit - ((byte_offset) << LOG_BITS_IN_BYTE);
402                return FindMetaBitResult::Found {
403                    addr: cursor + byte_offset as usize,
404                    bit: bit_offset,
405                };
406            }
407        } else {
408            // Load and check a byte
409            let value = unsafe { cursor.load::<u8>() };
410            if let Some(bit) = find_first_non_zero_bit::<u8>(value, 0, 8) {
411                return FindMetaBitResult::Found { addr: cursor, bit };
412            }
413        }
414        // Move to the address so we can load from it
415        cursor += step;
416    }
417    FindMetaBitResult::NotFound
418}
419
420pub fn find_first_non_zero_bit_in_metadata_bits(
421    addr: Address,
422    start_bit: u8,
423    end_bit: u8,
424) -> FindMetaBitResult {
425    if !addr.is_mapped() {
426        return FindMetaBitResult::UnmappedMetadata;
427    }
428    let byte = unsafe { addr.load::<u8>() };
429    if let Some(bit) = find_first_non_zero_bit::<u8>(byte, start_bit, end_bit) {
430        return FindMetaBitResult::Found { addr, bit };
431    }
432    FindMetaBitResult::NotFound
433}
434
435fn find_first_non_zero_bit<T>(value: T, start: u8, end: u8) -> Option<u8>
436where
437    T: PrimInt + CheckedShl,
438{
439    let mask = match T::one().checked_shl((end - start) as u32) {
440        Some(shl) => (shl - T::one()) << (start as u32),
441        None => T::max_value() << (start as u32),
442    };
443    let masked = value & mask;
444    if masked.is_zero() {
445        None
446    } else {
447        let trailing_zeroes = masked.trailing_zeros();
448        Some(trailing_zeroes as u8)
449    }
450}
451
452pub fn scan_non_zero_bits_in_metadata_bytes(
453    meta_start: Address,
454    meta_end: Address,
455    visit_bit: &mut impl FnMut(Address, BitOffset),
456) {
457    use crate::util::constants::BYTES_IN_ADDRESS;
458
459    let mut cursor = meta_start;
460    while cursor < meta_end && !cursor.is_aligned_to(BYTES_IN_ADDRESS) {
461        let byte = unsafe { cursor.load::<u8>() };
462        scan_non_zero_bits_in_metadata_word(cursor, byte as usize, visit_bit);
463        cursor += 1usize;
464    }
465
466    while cursor + BYTES_IN_ADDRESS < meta_end {
467        let word = unsafe { cursor.load::<usize>() };
468        scan_non_zero_bits_in_metadata_word(cursor, word, visit_bit);
469        cursor += BYTES_IN_ADDRESS;
470    }
471
472    while cursor < meta_end {
473        let byte = unsafe { cursor.load::<u8>() };
474        scan_non_zero_bits_in_metadata_word(cursor, byte as usize, visit_bit);
475        cursor += 1usize;
476    }
477}
478
479fn scan_non_zero_bits_in_metadata_word(
480    meta_addr: Address,
481    mut word: usize,
482    visit_bit: &mut impl FnMut(Address, BitOffset),
483) {
484    while word != 0 {
485        let bit = word.trailing_zeros();
486        visit_bit(meta_addr, bit as u8);
487        word = word & (word - 1);
488    }
489}
490
491pub fn scan_non_zero_bits_in_metadata_bits(
492    meta_addr: Address,
493    bit_start: BitOffset,
494    bit_end: BitOffset,
495    visit_bit: &mut impl FnMut(Address, BitOffset),
496) {
497    let byte = unsafe { meta_addr.load::<u8>() };
498    for bit in bit_start..bit_end {
499        if byte & (1 << bit) != 0 {
500            visit_bit(meta_addr, bit);
501        }
502    }
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use crate::util::metadata::side_metadata::*;
509
510    fn should_skip_spec_on_this_target(spec: &SideMetadataSpec) -> bool {
511        use layout::LOG_GLOBAL_SIDE_METADATA_WORST_CASE_RATIO;
512        log_data_meta_ratio(spec) < LOG_GLOBAL_SIDE_METADATA_WORST_CASE_RATIO
513    }
514
515    fn test_round_trip_conversion(spec: &SideMetadataSpec, test_data: &[Address]) {
516        if should_skip_spec_on_this_target(spec) {
517            eprintln!(
518                "Skipping {} on this target: spec ratio is outside global side metadata worst-case bound",
519                spec.name
520            );
521            return;
522        }
523
524        core_test_initialize_side_metadata();
525
526        for ref_addr in test_data {
527            let addr = *ref_addr;
528
529            // This is an aligned address. When we do roundtrip conversion, we will get back the original address.
530            {
531                assert!(addr.is_aligned_to(1 << spec.log_bytes_in_region));
532                let meta_addr = address_to_contiguous_meta_address(spec, addr);
533                let shift = meta_byte_lshift(spec, addr);
534                assert_eq!(
535                    contiguous_meta_address_to_address(spec, meta_addr, shift),
536                    addr
537                );
538            }
539
540            // This is an unaligned address. When we do roundtrip conversion, we will get the aligned address.
541            {
542                let next_addr = addr + 1usize;
543                let meta_addr = address_to_contiguous_meta_address(spec, next_addr);
544                let shift = meta_byte_lshift(spec, next_addr);
545                assert_eq!(
546                    contiguous_meta_address_to_address(spec, meta_addr, shift),
547                    addr
548                ); // we get back addr (which is the aligned address)
549            }
550        }
551    }
552
553    const TEST_ADDRESS_8B_REGION: [Address; 8] = [
554        unsafe { Address::from_usize(0x8000_0000) },
555        unsafe { Address::from_usize(0x8000_0008) },
556        unsafe { Address::from_usize(0x8000_0010) },
557        unsafe { Address::from_usize(0x8000_0018) },
558        unsafe { Address::from_usize(0x8000_0020) },
559        unsafe { Address::from_usize(0x8001_0000) },
560        unsafe { Address::from_usize(0x8001_0008) },
561        unsafe { Address::from_usize(0xd000_0000) },
562    ];
563
564    #[test]
565    fn test_contiguous_metadata_conversion_0_3() {
566        let spec = SideMetadataSpec {
567            name: "ContiguousMetadataTestSpec",
568            is_global: true,
569            offset: 0,
570            log_num_of_bits: 0,
571            log_bytes_in_region: 3,
572        };
573
574        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
575    }
576
577    #[test]
578    fn test_contiguous_metadata_conversion_1_3() {
579        let spec = SideMetadataSpec {
580            name: "ContiguousMetadataTestSpec",
581            is_global: true,
582            offset: 0,
583            log_num_of_bits: 1,
584            log_bytes_in_region: 3,
585        };
586
587        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
588    }
589
590    #[test]
591    fn test_contiguous_metadata_conversion_4_3() {
592        let spec = SideMetadataSpec {
593            name: "ContiguousMetadataTestSpec",
594            is_global: true,
595            offset: 0,
596            log_num_of_bits: 4,
597            log_bytes_in_region: 3,
598        };
599
600        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
601    }
602
603    #[test]
604    fn test_contiguous_metadata_conversion_5_3() {
605        let spec = SideMetadataSpec {
606            name: "ContiguousMetadataTestSpec",
607            is_global: true,
608            offset: 0,
609            log_num_of_bits: 5,
610            log_bytes_in_region: 3,
611        };
612
613        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
614    }
615
616    const TEST_ADDRESS_4KB_REGION: [Address; 8] = [
617        unsafe { Address::from_usize(0x8000_0000) },
618        unsafe { Address::from_usize(0x8000_1000) },
619        unsafe { Address::from_usize(0x8000_2000) },
620        unsafe { Address::from_usize(0x8000_3000) },
621        unsafe { Address::from_usize(0x8000_4000) },
622        unsafe { Address::from_usize(0x8001_0000) },
623        unsafe { Address::from_usize(0x8001_1000) },
624        unsafe { Address::from_usize(0xd000_0000) },
625    ];
626
627    #[test]
628    fn test_contiguous_metadata_conversion_0_12() {
629        let spec = SideMetadataSpec {
630            name: "ContiguousMetadataTestSpec",
631            is_global: true,
632            offset: 0,
633            log_num_of_bits: 0,
634            log_bytes_in_region: 12, // 4K
635        };
636
637        test_round_trip_conversion(&spec, &TEST_ADDRESS_4KB_REGION);
638    }
639
640    #[test]
641    fn test_find_last_non_zero_bit_in_u8() {
642        use super::find_last_non_zero_bit;
643        let bit = find_last_non_zero_bit::<u8>(0b100101, 0, 1);
644        assert_eq!(bit, Some(0));
645
646        let bit = find_last_non_zero_bit::<u8>(0b100101, 0, 3);
647        assert_eq!(bit, Some(2));
648
649        let bit = find_last_non_zero_bit::<u8>(0b100101, 0, 8);
650        assert_eq!(bit, Some(5));
651
652        let bit = find_last_non_zero_bit::<u8>(0b0, 0, 1);
653        assert_eq!(bit, None);
654    }
655
656    #[test]
657    fn test_align_metadata_address() {
658        let create_spec = |log_num_of_bits: usize| SideMetadataSpec {
659            name: "AlignMetadataBitTestSpec",
660            is_global: true,
661            offset: 0,
662            log_num_of_bits,
663            log_bytes_in_region: 3,
664        };
665
666        const ADDR_1000: Address = unsafe { Address::from_usize(0x1000) };
667        const ADDR_1001: Address = unsafe { Address::from_usize(0x1001) };
668        const ADDR_1002: Address = unsafe { Address::from_usize(0x1002) };
669        const ADDR_1003: Address = unsafe { Address::from_usize(0x1003) };
670        const ADDR_1004: Address = unsafe { Address::from_usize(0x1004) };
671        const ADDR_1005: Address = unsafe { Address::from_usize(0x1005) };
672        const ADDR_1006: Address = unsafe { Address::from_usize(0x1006) };
673        const ADDR_1007: Address = unsafe { Address::from_usize(0x1007) };
674        const ADDR_1008: Address = unsafe { Address::from_usize(0x1008) };
675        const ADDR_1009: Address = unsafe { Address::from_usize(0x1009) };
676
677        let metadata_2bits = create_spec(1);
678        assert_eq!(
679            align_metadata_address(&metadata_2bits, ADDR_1000, 0),
680            (ADDR_1000, 0)
681        );
682        assert_eq!(
683            align_metadata_address(&metadata_2bits, ADDR_1000, 1),
684            (ADDR_1000, 0)
685        );
686        assert_eq!(
687            align_metadata_address(&metadata_2bits, ADDR_1000, 2),
688            (ADDR_1000, 2)
689        );
690        assert_eq!(
691            align_metadata_address(&metadata_2bits, ADDR_1000, 3),
692            (ADDR_1000, 2)
693        );
694        assert_eq!(
695            align_metadata_address(&metadata_2bits, ADDR_1000, 4),
696            (ADDR_1000, 4)
697        );
698        assert_eq!(
699            align_metadata_address(&metadata_2bits, ADDR_1000, 5),
700            (ADDR_1000, 4)
701        );
702        assert_eq!(
703            align_metadata_address(&metadata_2bits, ADDR_1000, 6),
704            (ADDR_1000, 6)
705        );
706        assert_eq!(
707            align_metadata_address(&metadata_2bits, ADDR_1000, 7),
708            (ADDR_1000, 6)
709        );
710
711        let metadata_4bits = create_spec(2);
712        assert_eq!(
713            align_metadata_address(&metadata_4bits, ADDR_1000, 0),
714            (ADDR_1000, 0)
715        );
716        assert_eq!(
717            align_metadata_address(&metadata_4bits, ADDR_1000, 1),
718            (ADDR_1000, 0)
719        );
720        assert_eq!(
721            align_metadata_address(&metadata_4bits, ADDR_1000, 2),
722            (ADDR_1000, 0)
723        );
724        assert_eq!(
725            align_metadata_address(&metadata_4bits, ADDR_1000, 3),
726            (ADDR_1000, 0)
727        );
728        assert_eq!(
729            align_metadata_address(&metadata_4bits, ADDR_1000, 4),
730            (ADDR_1000, 4)
731        );
732        assert_eq!(
733            align_metadata_address(&metadata_4bits, ADDR_1000, 5),
734            (ADDR_1000, 4)
735        );
736        assert_eq!(
737            align_metadata_address(&metadata_4bits, ADDR_1000, 6),
738            (ADDR_1000, 4)
739        );
740        assert_eq!(
741            align_metadata_address(&metadata_4bits, ADDR_1000, 7),
742            (ADDR_1000, 4)
743        );
744
745        let metadata_8bits = create_spec(3);
746        assert_eq!(
747            align_metadata_address(&metadata_8bits, ADDR_1000, 0),
748            (ADDR_1000, 0)
749        );
750        assert_eq!(
751            align_metadata_address(&metadata_8bits, ADDR_1000, 1),
752            (ADDR_1000, 0)
753        );
754        assert_eq!(
755            align_metadata_address(&metadata_8bits, ADDR_1000, 2),
756            (ADDR_1000, 0)
757        );
758        assert_eq!(
759            align_metadata_address(&metadata_8bits, ADDR_1000, 3),
760            (ADDR_1000, 0)
761        );
762        assert_eq!(
763            align_metadata_address(&metadata_8bits, ADDR_1000, 4),
764            (ADDR_1000, 0)
765        );
766        assert_eq!(
767            align_metadata_address(&metadata_8bits, ADDR_1000, 5),
768            (ADDR_1000, 0)
769        );
770        assert_eq!(
771            align_metadata_address(&metadata_8bits, ADDR_1000, 6),
772            (ADDR_1000, 0)
773        );
774        assert_eq!(
775            align_metadata_address(&metadata_8bits, ADDR_1000, 7),
776            (ADDR_1000, 0)
777        );
778
779        let metadata_16bits = create_spec(4);
780        assert_eq!(
781            align_metadata_address(&metadata_16bits, ADDR_1000, 0),
782            (ADDR_1000, 0)
783        );
784        assert_eq!(
785            align_metadata_address(&metadata_16bits, ADDR_1000, 1),
786            (ADDR_1000, 0)
787        );
788        assert_eq!(
789            align_metadata_address(&metadata_16bits, ADDR_1000, 2),
790            (ADDR_1000, 0)
791        );
792        assert_eq!(
793            align_metadata_address(&metadata_16bits, ADDR_1000, 3),
794            (ADDR_1000, 0)
795        );
796        assert_eq!(
797            align_metadata_address(&metadata_16bits, ADDR_1000, 4),
798            (ADDR_1000, 0)
799        );
800        assert_eq!(
801            align_metadata_address(&metadata_16bits, ADDR_1000, 5),
802            (ADDR_1000, 0)
803        );
804        assert_eq!(
805            align_metadata_address(&metadata_16bits, ADDR_1000, 6),
806            (ADDR_1000, 0)
807        );
808        assert_eq!(
809            align_metadata_address(&metadata_16bits, ADDR_1000, 7),
810            (ADDR_1000, 0)
811        );
812        assert_eq!(
813            align_metadata_address(&metadata_16bits, ADDR_1001, 0),
814            (ADDR_1000, 0)
815        );
816        assert_eq!(
817            align_metadata_address(&metadata_16bits, ADDR_1001, 1),
818            (ADDR_1000, 0)
819        );
820        assert_eq!(
821            align_metadata_address(&metadata_16bits, ADDR_1001, 2),
822            (ADDR_1000, 0)
823        );
824        assert_eq!(
825            align_metadata_address(&metadata_16bits, ADDR_1001, 3),
826            (ADDR_1000, 0)
827        );
828        assert_eq!(
829            align_metadata_address(&metadata_16bits, ADDR_1001, 4),
830            (ADDR_1000, 0)
831        );
832        assert_eq!(
833            align_metadata_address(&metadata_16bits, ADDR_1001, 5),
834            (ADDR_1000, 0)
835        );
836        assert_eq!(
837            align_metadata_address(&metadata_16bits, ADDR_1001, 6),
838            (ADDR_1000, 0)
839        );
840        assert_eq!(
841            align_metadata_address(&metadata_16bits, ADDR_1001, 7),
842            (ADDR_1000, 0)
843        );
844    }
845}