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