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 scan_non_zero_bits_in_metadata_bytes(
349    meta_start: Address,
350    meta_end: Address,
351    visit_bit: &mut impl FnMut(Address, BitOffset),
352) {
353    use crate::util::constants::BYTES_IN_ADDRESS;
354
355    let mut cursor = meta_start;
356    while cursor < meta_end && !cursor.is_aligned_to(BYTES_IN_ADDRESS) {
357        let byte = unsafe { cursor.load::<u8>() };
358        scan_non_zero_bits_in_metadata_word(cursor, byte as usize, visit_bit);
359        cursor += 1usize;
360    }
361
362    while cursor + BYTES_IN_ADDRESS < meta_end {
363        let word = unsafe { cursor.load::<usize>() };
364        scan_non_zero_bits_in_metadata_word(cursor, word, visit_bit);
365        cursor += BYTES_IN_ADDRESS;
366    }
367
368    while cursor < meta_end {
369        let byte = unsafe { cursor.load::<u8>() };
370        scan_non_zero_bits_in_metadata_word(cursor, byte as usize, visit_bit);
371        cursor += 1usize;
372    }
373}
374
375fn scan_non_zero_bits_in_metadata_word(
376    meta_addr: Address,
377    mut word: usize,
378    visit_bit: &mut impl FnMut(Address, BitOffset),
379) {
380    while word != 0 {
381        let bit = word.trailing_zeros();
382        visit_bit(meta_addr, bit as u8);
383        word = word & (word - 1);
384    }
385}
386
387pub fn scan_non_zero_bits_in_metadata_bits(
388    meta_addr: Address,
389    bit_start: BitOffset,
390    bit_end: BitOffset,
391    visit_bit: &mut impl FnMut(Address, BitOffset),
392) {
393    let byte = unsafe { meta_addr.load::<u8>() };
394    for bit in bit_start..bit_end {
395        if byte & (1 << bit) != 0 {
396            visit_bit(meta_addr, bit);
397        }
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404    use crate::util::metadata::side_metadata::*;
405
406    fn should_skip_spec_on_this_target(spec: &SideMetadataSpec) -> bool {
407        use layout::LOG_GLOBAL_SIDE_METADATA_WORST_CASE_RATIO;
408        log_data_meta_ratio(spec) < LOG_GLOBAL_SIDE_METADATA_WORST_CASE_RATIO
409    }
410
411    fn test_round_trip_conversion(spec: &SideMetadataSpec, test_data: &[Address]) {
412        if should_skip_spec_on_this_target(spec) {
413            eprintln!(
414                "Skipping {} on this target: spec ratio is outside global side metadata worst-case bound",
415                spec.name
416            );
417            return;
418        }
419
420        core_test_initialize_side_metadata();
421
422        for ref_addr in test_data {
423            let addr = *ref_addr;
424
425            // This is an aligned address. When we do roundtrip conversion, we will get back the original address.
426            {
427                assert!(addr.is_aligned_to(1 << spec.log_bytes_in_region));
428                let meta_addr = address_to_contiguous_meta_address(spec, addr);
429                let shift = meta_byte_lshift(spec, addr);
430                assert_eq!(
431                    contiguous_meta_address_to_address(spec, meta_addr, shift),
432                    addr
433                );
434            }
435
436            // This is an unaligned address. When we do roundtrip conversion, we will get the aligned address.
437            {
438                let next_addr = addr + 1usize;
439                let meta_addr = address_to_contiguous_meta_address(spec, next_addr);
440                let shift = meta_byte_lshift(spec, next_addr);
441                assert_eq!(
442                    contiguous_meta_address_to_address(spec, meta_addr, shift),
443                    addr
444                ); // we get back addr (which is the aligned address)
445            }
446        }
447    }
448
449    const TEST_ADDRESS_8B_REGION: [Address; 8] = [
450        unsafe { Address::from_usize(0x8000_0000) },
451        unsafe { Address::from_usize(0x8000_0008) },
452        unsafe { Address::from_usize(0x8000_0010) },
453        unsafe { Address::from_usize(0x8000_0018) },
454        unsafe { Address::from_usize(0x8000_0020) },
455        unsafe { Address::from_usize(0x8001_0000) },
456        unsafe { Address::from_usize(0x8001_0008) },
457        unsafe { Address::from_usize(0xd000_0000) },
458    ];
459
460    #[test]
461    fn test_contiguous_metadata_conversion_0_3() {
462        let spec = SideMetadataSpec {
463            name: "ContiguousMetadataTestSpec",
464            is_global: true,
465            offset: 0,
466            log_num_of_bits: 0,
467            log_bytes_in_region: 3,
468        };
469
470        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
471    }
472
473    #[test]
474    fn test_contiguous_metadata_conversion_1_3() {
475        let spec = SideMetadataSpec {
476            name: "ContiguousMetadataTestSpec",
477            is_global: true,
478            offset: 0,
479            log_num_of_bits: 1,
480            log_bytes_in_region: 3,
481        };
482
483        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
484    }
485
486    #[test]
487    fn test_contiguous_metadata_conversion_4_3() {
488        let spec = SideMetadataSpec {
489            name: "ContiguousMetadataTestSpec",
490            is_global: true,
491            offset: 0,
492            log_num_of_bits: 4,
493            log_bytes_in_region: 3,
494        };
495
496        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
497    }
498
499    #[test]
500    fn test_contiguous_metadata_conversion_5_3() {
501        let spec = SideMetadataSpec {
502            name: "ContiguousMetadataTestSpec",
503            is_global: true,
504            offset: 0,
505            log_num_of_bits: 5,
506            log_bytes_in_region: 3,
507        };
508
509        test_round_trip_conversion(&spec, &TEST_ADDRESS_8B_REGION);
510    }
511
512    const TEST_ADDRESS_4KB_REGION: [Address; 8] = [
513        unsafe { Address::from_usize(0x8000_0000) },
514        unsafe { Address::from_usize(0x8000_1000) },
515        unsafe { Address::from_usize(0x8000_2000) },
516        unsafe { Address::from_usize(0x8000_3000) },
517        unsafe { Address::from_usize(0x8000_4000) },
518        unsafe { Address::from_usize(0x8001_0000) },
519        unsafe { Address::from_usize(0x8001_1000) },
520        unsafe { Address::from_usize(0xd000_0000) },
521    ];
522
523    #[test]
524    fn test_contiguous_metadata_conversion_0_12() {
525        let spec = SideMetadataSpec {
526            name: "ContiguousMetadataTestSpec",
527            is_global: true,
528            offset: 0,
529            log_num_of_bits: 0,
530            log_bytes_in_region: 12, // 4K
531        };
532
533        test_round_trip_conversion(&spec, &TEST_ADDRESS_4KB_REGION);
534    }
535
536    #[test]
537    fn test_find_last_non_zero_bit_in_u8() {
538        use super::find_last_non_zero_bit;
539        let bit = find_last_non_zero_bit::<u8>(0b100101, 0, 1);
540        assert_eq!(bit, Some(0));
541
542        let bit = find_last_non_zero_bit::<u8>(0b100101, 0, 3);
543        assert_eq!(bit, Some(2));
544
545        let bit = find_last_non_zero_bit::<u8>(0b100101, 0, 8);
546        assert_eq!(bit, Some(5));
547
548        let bit = find_last_non_zero_bit::<u8>(0b0, 0, 1);
549        assert_eq!(bit, None);
550    }
551
552    #[test]
553    fn test_align_metadata_address() {
554        let create_spec = |log_num_of_bits: usize| SideMetadataSpec {
555            name: "AlignMetadataBitTestSpec",
556            is_global: true,
557            offset: 0,
558            log_num_of_bits,
559            log_bytes_in_region: 3,
560        };
561
562        const ADDR_1000: Address = unsafe { Address::from_usize(0x1000) };
563        const ADDR_1001: Address = unsafe { Address::from_usize(0x1001) };
564        const ADDR_1002: Address = unsafe { Address::from_usize(0x1002) };
565        const ADDR_1003: Address = unsafe { Address::from_usize(0x1003) };
566        const ADDR_1004: Address = unsafe { Address::from_usize(0x1004) };
567        const ADDR_1005: Address = unsafe { Address::from_usize(0x1005) };
568        const ADDR_1006: Address = unsafe { Address::from_usize(0x1006) };
569        const ADDR_1007: Address = unsafe { Address::from_usize(0x1007) };
570        const ADDR_1008: Address = unsafe { Address::from_usize(0x1008) };
571        const ADDR_1009: Address = unsafe { Address::from_usize(0x1009) };
572
573        let metadata_2bits = create_spec(1);
574        assert_eq!(
575            align_metadata_address(&metadata_2bits, ADDR_1000, 0),
576            (ADDR_1000, 0)
577        );
578        assert_eq!(
579            align_metadata_address(&metadata_2bits, ADDR_1000, 1),
580            (ADDR_1000, 0)
581        );
582        assert_eq!(
583            align_metadata_address(&metadata_2bits, ADDR_1000, 2),
584            (ADDR_1000, 2)
585        );
586        assert_eq!(
587            align_metadata_address(&metadata_2bits, ADDR_1000, 3),
588            (ADDR_1000, 2)
589        );
590        assert_eq!(
591            align_metadata_address(&metadata_2bits, ADDR_1000, 4),
592            (ADDR_1000, 4)
593        );
594        assert_eq!(
595            align_metadata_address(&metadata_2bits, ADDR_1000, 5),
596            (ADDR_1000, 4)
597        );
598        assert_eq!(
599            align_metadata_address(&metadata_2bits, ADDR_1000, 6),
600            (ADDR_1000, 6)
601        );
602        assert_eq!(
603            align_metadata_address(&metadata_2bits, ADDR_1000, 7),
604            (ADDR_1000, 6)
605        );
606
607        let metadata_4bits = create_spec(2);
608        assert_eq!(
609            align_metadata_address(&metadata_4bits, ADDR_1000, 0),
610            (ADDR_1000, 0)
611        );
612        assert_eq!(
613            align_metadata_address(&metadata_4bits, ADDR_1000, 1),
614            (ADDR_1000, 0)
615        );
616        assert_eq!(
617            align_metadata_address(&metadata_4bits, ADDR_1000, 2),
618            (ADDR_1000, 0)
619        );
620        assert_eq!(
621            align_metadata_address(&metadata_4bits, ADDR_1000, 3),
622            (ADDR_1000, 0)
623        );
624        assert_eq!(
625            align_metadata_address(&metadata_4bits, ADDR_1000, 4),
626            (ADDR_1000, 4)
627        );
628        assert_eq!(
629            align_metadata_address(&metadata_4bits, ADDR_1000, 5),
630            (ADDR_1000, 4)
631        );
632        assert_eq!(
633            align_metadata_address(&metadata_4bits, ADDR_1000, 6),
634            (ADDR_1000, 4)
635        );
636        assert_eq!(
637            align_metadata_address(&metadata_4bits, ADDR_1000, 7),
638            (ADDR_1000, 4)
639        );
640
641        let metadata_8bits = create_spec(3);
642        assert_eq!(
643            align_metadata_address(&metadata_8bits, ADDR_1000, 0),
644            (ADDR_1000, 0)
645        );
646        assert_eq!(
647            align_metadata_address(&metadata_8bits, ADDR_1000, 1),
648            (ADDR_1000, 0)
649        );
650        assert_eq!(
651            align_metadata_address(&metadata_8bits, ADDR_1000, 2),
652            (ADDR_1000, 0)
653        );
654        assert_eq!(
655            align_metadata_address(&metadata_8bits, ADDR_1000, 3),
656            (ADDR_1000, 0)
657        );
658        assert_eq!(
659            align_metadata_address(&metadata_8bits, ADDR_1000, 4),
660            (ADDR_1000, 0)
661        );
662        assert_eq!(
663            align_metadata_address(&metadata_8bits, ADDR_1000, 5),
664            (ADDR_1000, 0)
665        );
666        assert_eq!(
667            align_metadata_address(&metadata_8bits, ADDR_1000, 6),
668            (ADDR_1000, 0)
669        );
670        assert_eq!(
671            align_metadata_address(&metadata_8bits, ADDR_1000, 7),
672            (ADDR_1000, 0)
673        );
674
675        let metadata_16bits = create_spec(4);
676        assert_eq!(
677            align_metadata_address(&metadata_16bits, ADDR_1000, 0),
678            (ADDR_1000, 0)
679        );
680        assert_eq!(
681            align_metadata_address(&metadata_16bits, ADDR_1000, 1),
682            (ADDR_1000, 0)
683        );
684        assert_eq!(
685            align_metadata_address(&metadata_16bits, ADDR_1000, 2),
686            (ADDR_1000, 0)
687        );
688        assert_eq!(
689            align_metadata_address(&metadata_16bits, ADDR_1000, 3),
690            (ADDR_1000, 0)
691        );
692        assert_eq!(
693            align_metadata_address(&metadata_16bits, ADDR_1000, 4),
694            (ADDR_1000, 0)
695        );
696        assert_eq!(
697            align_metadata_address(&metadata_16bits, ADDR_1000, 5),
698            (ADDR_1000, 0)
699        );
700        assert_eq!(
701            align_metadata_address(&metadata_16bits, ADDR_1000, 6),
702            (ADDR_1000, 0)
703        );
704        assert_eq!(
705            align_metadata_address(&metadata_16bits, ADDR_1000, 7),
706            (ADDR_1000, 0)
707        );
708        assert_eq!(
709            align_metadata_address(&metadata_16bits, ADDR_1001, 0),
710            (ADDR_1000, 0)
711        );
712        assert_eq!(
713            align_metadata_address(&metadata_16bits, ADDR_1001, 1),
714            (ADDR_1000, 0)
715        );
716        assert_eq!(
717            align_metadata_address(&metadata_16bits, ADDR_1001, 2),
718            (ADDR_1000, 0)
719        );
720        assert_eq!(
721            align_metadata_address(&metadata_16bits, ADDR_1001, 3),
722            (ADDR_1000, 0)
723        );
724        assert_eq!(
725            align_metadata_address(&metadata_16bits, ADDR_1001, 4),
726            (ADDR_1000, 0)
727        );
728        assert_eq!(
729            align_metadata_address(&metadata_16bits, ADDR_1001, 5),
730            (ADDR_1000, 0)
731        );
732        assert_eq!(
733            align_metadata_address(&metadata_16bits, ADDR_1001, 6),
734            (ADDR_1000, 0)
735        );
736        assert_eq!(
737            align_metadata_address(&metadata_16bits, ADDR_1001, 7),
738            (ADDR_1000, 0)
739        );
740    }
741}