mmtk/util/metadata/side_metadata/
sanity.rs

1use crate::util::Address;
2use std::collections::HashMap;
3use std::io::{Error, ErrorKind, Result};
4use std::sync::{Mutex, RwLock};
5
6use super::layout::{
7    LOG_GLOBAL_SIDE_METADATA_WORST_CASE_RATIO, LOG_LOCAL_SIDE_METADATA_WORST_CASE_RATIO,
8};
9use super::{SideMetadataContext, SideMetadataSpec};
10#[cfg(target_pointer_width = "64")]
11use crate::util::heap::layout::vm_layout::vm_layout;
12use crate::util::heap::layout::vm_layout::VMLayout;
13#[cfg(target_pointer_width = "32")]
14use crate::util::heap::layout::vm_layout::LOG_BYTES_IN_CHUNK;
15
16/// An internal enum to enhance code style for add/sub
17#[cfg(feature = "extreme_assertions")]
18enum MathOp {
19    Add,
20    Sub,
21}
22
23/// An internal str used as a name for global side metadata
24/// (policy-specific metadata is named after the policy who own it)
25static GLOBAL_META_NAME: &str = "Global";
26
27/// This struct includes a hashmap to store the metadata specs information for policy-specific and global metadata for each plan.
28/// It uses policy name (or GLOBAL_META_NAME for globals) as the key and keeps a vector of specs as the value.
29/// Each plan needs its exclusive instance of this struct to use side metadata specification and content sanity checker.
30///
31/// NOTE:
32/// Content sanity check is expensive and is only activated with the `extreme_assertions` feature.
33///
34/// FIXME: This struct should be pub(crate) visible, but changing its scope will need changing other scopes, such as the Space trait's. For now, I will not do that.
35pub struct SideMetadataSanity {
36    specs_sanity_map: HashMap<&'static str, Vec<SideMetadataSpec>>,
37}
38
39lazy_static! {
40    /// This is a two-level hashmap to store the metadata content for verification purposes.
41    /// It keeps a map from side metadata specifications to a second hashmap
42    /// which maps data addresses to their current metadata content.
43    /// Use u64 to store side metadata values, as u64 is the max length of side metadata we support.
44    static ref CONTENT_SANITY_MAP: RwLock<HashMap<SideMetadataSpec, HashMap<Address, u64>>> =
45        RwLock::new(HashMap::new());
46    pub(crate) static ref SANITY_LOCK: Mutex<()> = Mutex::new(());
47}
48
49/// A test helper function which resets contents map to prevent propagation of test failure
50#[cfg(test)]
51pub(crate) fn reset() {
52    CONTENT_SANITY_MAP.write().unwrap().clear()
53}
54
55/// Checks whether the input global specifications fit within the current upper bound for all global metadata (limited by `metadata::constants::LOG_GLOBAL_SIDE_METADATA_WORST_CASE_RATIO`).
56///
57/// Returns `Ok` if all global specs fit and `Err` otherwise.
58///
59/// Arguments:
60/// * `g_specs`: a slice of global specs to be checked.
61///
62fn verify_global_specs_total_size(g_specs: &[SideMetadataSpec]) -> Result<()> {
63    let mut total_size = 0usize;
64    for spec in g_specs {
65        total_size += super::metadata_address_range_size(spec);
66    }
67
68    if total_size
69        <= 1usize << (VMLayout::LOG_ARCH_ADDRESS_SPACE - LOG_GLOBAL_SIDE_METADATA_WORST_CASE_RATIO)
70    {
71        Ok(())
72    } else {
73        Err(Error::new(
74            ErrorKind::InvalidInput,
75            format!("Not enough global metadata space for: \n{:?}", g_specs),
76        ))
77    }
78}
79
80/// (For 64-bits targets) Checks whether the input local specifications fit within the current upper bound for each local metadata (limited for each local metadata by `metadata::constants::LOG_LOCAL_SIDE_METADATA_WORST_CASE_RATIO`).
81///
82/// Returns `Ok` if all local specs fit and `Err` otherwise.
83///
84/// Arguments:
85/// * `l_specs`: a slice of local specs to be checked.
86///
87#[cfg(target_pointer_width = "64")]
88fn verify_local_specs_size(l_specs: &[SideMetadataSpec]) -> Result<()> {
89    for spec in l_specs {
90        if super::metadata_address_range_size(spec)
91            > 1usize
92                << (VMLayout::LOG_ARCH_ADDRESS_SPACE - LOG_LOCAL_SIDE_METADATA_WORST_CASE_RATIO)
93        {
94            return Err(Error::new(
95                ErrorKind::InvalidInput,
96                format!("Local metadata is too big: \n{:?}", spec),
97            ));
98        }
99    }
100
101    Ok(())
102}
103
104/// (For 32-bits targets) Checks whether the input local specifications fit within the current upper bound for all chunked local metadata (limited for all chunked local metadata by `metadata::constants::LOG_LOCAL_SIDE_METADATA_WORST_CASE_RATIO`).
105///
106/// Returns `Ok` if all local specs fit and `Err` otherwise.
107///
108/// Arguments:
109/// * `l_specs`: a slice of local specs to be checked.
110///
111#[cfg(target_pointer_width = "32")]
112fn verify_local_specs_size(l_specs: &[SideMetadataSpec]) -> Result<()> {
113    let mut total_size = 0usize;
114    for spec in l_specs {
115        total_size +=
116            super::metadata_bytes_per_chunk(spec.log_bytes_in_region, spec.log_num_of_bits);
117    }
118
119    if total_size > 1usize << (LOG_BYTES_IN_CHUNK - LOG_LOCAL_SIDE_METADATA_WORST_CASE_RATIO) {
120        return Err(Error::new(
121            ErrorKind::InvalidInput,
122            format!(
123                "Not enough local metadata space per chunk for: \n{:?}",
124                l_specs
125            ),
126        ));
127    }
128
129    Ok(())
130}
131
132/// (For contiguous metadata) Checks whether two input specifications overlap, considering their offsets and maximum size.
133///
134/// Returns `Err` if overlap is detected.
135///
136/// Arguments:
137/// * `spec_1`: first target specification
138/// * `spec_2`: second target specification
139///
140fn verify_no_overlap_contiguous(
141    spec_1: &SideMetadataSpec,
142    spec_2: &SideMetadataSpec,
143) -> Result<()> {
144    let base = crate::util::metadata::side_metadata::layout::global_side_metadata_base_address();
145    let end_1 = base + super::metadata_address_range_size(spec_1);
146    let end_2 = base + super::metadata_address_range_size(spec_2);
147
148    if !(spec_1.get_starting_address() >= end_2 || spec_2.get_starting_address() >= end_1) {
149        return Err(Error::new(
150            ErrorKind::InvalidInput,
151            format!(
152                "Overlapping metadata specs detected:\nTHIS:\n{:#?}\nAND:\n{:#?}",
153                spec_1, spec_2
154            ),
155        ));
156    }
157    Ok(())
158}
159
160/// (For chunked metadata) Checks whether two input specifications overlap, considering their offsets and maximum per-chunk size.
161///
162/// Returns `Err` if overlap is detected.
163///
164/// Arguments:
165/// * `spec_1`: first target specification
166/// * `spec_2`: second target specification
167///
168#[cfg(target_pointer_width = "32")]
169fn verify_no_overlap_chunked(spec_1: &SideMetadataSpec, spec_2: &SideMetadataSpec) -> Result<()> {
170    let end_1 = spec_1.get_offset_for_chunked()
171        + super::metadata_bytes_per_chunk(spec_1.log_bytes_in_region, spec_1.log_num_of_bits);
172    let end_2 = spec_2.get_offset_for_chunked()
173        + super::metadata_bytes_per_chunk(spec_2.log_bytes_in_region, spec_2.log_num_of_bits);
174
175    if !(spec_1.get_offset_for_chunked() >= end_2 || spec_2.get_offset_for_chunked() >= end_1) {
176        return Err(Error::new(
177            ErrorKind::InvalidInput,
178            format!(
179                "Overlapping metadata specs detected:\nTHIS:\n{:#?}\nAND:\n{:#?}",
180                spec_1, spec_2
181            ),
182        ));
183    }
184    Ok(())
185}
186
187/// Checks whether a slice of global specifications fit within the memory limits and don't overlap.
188///
189/// Returns `Ok` if no issue is detected, or otherwise an `Err` explaining the issue.
190///
191/// Arguments:
192/// * `g_specs`: the slice of global specifications to be checked
193///
194fn verify_global_specs(g_specs: &[SideMetadataSpec]) -> Result<()> {
195    verify_global_specs_total_size(g_specs)?;
196
197    for spec_1 in g_specs {
198        for spec_2 in g_specs {
199            if spec_1 != spec_2 {
200                verify_no_overlap_contiguous(spec_1, spec_2)?;
201            }
202        }
203    }
204
205    Ok(())
206}
207
208// Clippy likes this
209impl Default for SideMetadataSanity {
210    fn default() -> Self {
211        Self::new()
212    }
213}
214
215impl SideMetadataSanity {
216    /// Creates a new SideMetadataSanity instance.
217    pub fn new() -> SideMetadataSanity {
218        SideMetadataSanity {
219            specs_sanity_map: HashMap::new(),
220        }
221    }
222    /// Returns all global or policy-specific specs based-on the input argument.
223    ///
224    /// Returns a vector of globals if `global` is true and a vector of locals otherwise.
225    ///
226    /// Arguments:
227    /// * `global`: a boolean to show whether global (`true`) or policy-specific (`false`) specs are required.
228    ///
229    fn get_all_specs(&self, global: bool) -> Vec<SideMetadataSpec> {
230        let mut specs = vec![];
231        for (k, v) in self.specs_sanity_map.iter() {
232            if !(global ^ (*k == GLOBAL_META_NAME)) {
233                specs.append(&mut (*v).clone());
234            }
235        }
236        // Deduplicate the specs using a hashset
237        std::collections::HashSet::<SideMetadataSpec>::from_iter(specs)
238            .into_iter()
239            .collect()
240    }
241
242    /// Verifies that all local side metadata specs:
243    /// 1 - are not too big,
244    /// 2 - do not overlap.
245    ///
246    /// Returns `Ok(())` if no issue is detected, or `Err` otherwise.
247    ///
248    fn verify_local_specs(&self) -> Result<()> {
249        let local_specs = self.get_all_specs(false);
250
251        verify_local_specs_size(&local_specs)?;
252
253        for spec_1 in &local_specs {
254            for spec_2 in &local_specs {
255                if spec_1 != spec_2 {
256                    #[cfg(target_pointer_width = "64")]
257                    verify_no_overlap_contiguous(spec_1, spec_2)?;
258                    #[cfg(target_pointer_width = "32")]
259                    verify_no_overlap_chunked(spec_1, spec_2)?;
260                }
261            }
262        }
263        Ok(())
264    }
265
266    /// An internal method to ensure that a metadata context does not have any issues.
267    ///
268    /// Arguments:
269    /// * `policy_name`: name of the policy of the calling space
270    /// * `metadata_context`: the metadata context to examine
271    ///
272    /// NOTE:
273    /// Any unit test using metadata directly or indirectly may need to make sure:
274    /// 1 - it uses `util::test_util::serial_test` to prevent metadata sanity conflicts,
275    /// 2 - uses exclusive SideMetadata instances (v.s. static instances), and
276    /// 3 - uses `util::test_util::with_cleanup` to call `sanity::reset` to cleanup the metadata sanity states to prevent future conflicts.
277    ///
278    pub(crate) fn verify_metadata_context(
279        &mut self,
280        policy_name: &'static str,
281        metadata_context: &SideMetadataContext,
282    ) {
283        let mut content_sanity_map = CONTENT_SANITY_MAP.write().unwrap();
284
285        // is this the first call of this function?
286        let first_call = !self.specs_sanity_map.contains_key(&GLOBAL_META_NAME);
287
288        if first_call {
289            // global metadata combination is the same for all contexts
290            verify_global_specs(&metadata_context.global).unwrap();
291            self.specs_sanity_map
292                .insert(GLOBAL_META_NAME, metadata_context.global.clone());
293        } else {
294            // make sure the global metadata in the current context has the same length as before
295            let g_specs = self.specs_sanity_map.get(&GLOBAL_META_NAME).unwrap();
296            assert!(
297            g_specs.len() == metadata_context.global.len(),
298            "Global metadata must not change between policies! NEW SPECS: {:#?} OLD SPECS: {:#?}",
299            metadata_context.global,
300            g_specs
301        );
302        }
303
304        for spec in &metadata_context.global {
305            // Make sure all input global specs are actually global
306            assert!(
307                spec.is_global,
308                "Policy-specific spec {:#?} detected in the global specs: {:#?}",
309                spec, metadata_context.global
310            );
311            // On the first call to the function, initialise the content sanity map, and
312            // on the future calls, checks the global metadata specs have not changed
313            if first_call {
314                // initialise the related hashmap
315                content_sanity_map.insert(*spec, HashMap::new());
316            } else if !self
317                .specs_sanity_map
318                .get(&GLOBAL_META_NAME)
319                .unwrap()
320                .contains(spec)
321            {
322                panic!("Global metadata must not change between policies! NEW SPEC: {:#?} OLD SPECS: {:#?}", spec, self.get_all_specs(true));
323            }
324        }
325
326        // Is this the first time this function is called by any space of a policy?
327        let first_call = !self.specs_sanity_map.contains_key(&policy_name);
328
329        if first_call {
330            self.specs_sanity_map
331                .insert(policy_name, metadata_context.local.clone());
332        }
333
334        for spec in &metadata_context.local {
335            // Make sure all input local specs are actually local
336            assert!(
337                !spec.is_global,
338                "Global spec {:#?} detected in the policy-specific specs: {:#?}",
339                spec, metadata_context.local
340            );
341            // The first call from each policy inserts the relevant (spec, hashmap) pair.
342            // Future calls only check that the metadata specs have not changed.
343            // This should work with multi mmtk instances, because the local side metadata specs are assumed to be constant per policy.
344            if first_call {
345                // initialise the related hashmap
346                content_sanity_map.insert(*spec, HashMap::new());
347            } else if !self
348                .specs_sanity_map
349                .get(policy_name)
350                .unwrap()
351                .contains(spec)
352            {
353                panic!(
354                    "Policy-specific metadata for -{}- changed from {:#?} to {:#?}",
355                    policy_name,
356                    self.specs_sanity_map.get(policy_name).unwrap(),
357                    metadata_context.local
358                )
359            }
360        }
361
362        self.verify_local_specs().unwrap();
363    }
364
365    #[cfg(test)]
366    pub fn reset(&mut self) {
367        let mut content_sanity_map = CONTENT_SANITY_MAP.write().unwrap();
368        self.specs_sanity_map.clear();
369        content_sanity_map.clear();
370    }
371}
372
373/// This verifies two things:
374/// 1. Check if data_addr is within the address space that we are supposed to use (LOG_ADDRESS_SPACE). If this fails, we log a warning.
375/// 2. Check if metadata address is out of bounds. If this fails, we will panic.
376fn verify_metadata_address_bound(spec: &SideMetadataSpec, data_addr: Address) {
377    #[cfg(target_pointer_width = "32")]
378    assert_eq!(VMLayout::LOG_ARCH_ADDRESS_SPACE, 32, "We assume we use all address space in 32 bits. This seems not true any more, we need a proper check here.");
379    #[cfg(target_pointer_width = "32")]
380    let data_addr_in_address_space = true;
381    #[cfg(target_pointer_width = "64")]
382    let data_addr_in_address_space =
383        data_addr <= unsafe { Address::from_usize(1usize << vm_layout().log_address_space) };
384
385    if !data_addr_in_address_space {
386        warn!(
387            "We try get metadata {} for {}, which is not within the address space we should use",
388            data_addr, spec.name
389        );
390    }
391
392    let metadata_addr =
393        crate::util::metadata::side_metadata::address_to_meta_address(spec, data_addr);
394    let metadata_addr_bound = if spec.uses_contiguous_side_metadata() {
395        spec.upper_bound_address_for_contiguous()
396    } else {
397        #[cfg(target_pointer_width = "32")]
398        {
399            spec.upper_bound_address_for_chunked(data_addr)
400        }
401        #[cfg(target_pointer_width = "64")]
402        {
403            unreachable!()
404        }
405    };
406    assert!(
407        metadata_addr < metadata_addr_bound,
408        "We try access metadata address for address {} of spec {} that is not within the bound {}.",
409        data_addr,
410        spec.name,
411        metadata_addr_bound
412    );
413}
414
415/// Commits a side metadata bulk zero operation.
416/// Panics if the metadata spec is not valid.
417///
418/// Arguments:
419/// * `metadata_spec`: the metadata spec to perform the bulk zeroing on
420/// * `start`: the starting address of the source data
421/// * `size`: size of the source data
422///
423#[cfg(feature = "extreme_assertions")]
424pub fn verify_bzero(metadata_spec: &SideMetadataSpec, start: Address, size: usize) {
425    let sanity_map = &mut CONTENT_SANITY_MAP.write().unwrap();
426    let start = align_to_region_start(metadata_spec, start);
427    let end = align_to_region_start(metadata_spec, start + size);
428    match sanity_map.get_mut(metadata_spec) {
429        Some(spec_sanity_map) => {
430            // zero entries where the key (data_addr) is in the range (start, start+size)
431            for (k, v) in spec_sanity_map.iter_mut() {
432                // If the source address is in the bzero's range
433                if *k >= start && *k < end {
434                    *v = 0;
435                }
436            }
437        }
438        None => {
439            panic!("Invalid Metadata Spec: {}", metadata_spec.name);
440        }
441    }
442}
443
444/// Commits a side metadata bulk set operation (set the related bits to all 1s).
445/// Panics if the metadata spec is not valid.
446///
447/// Arguments:
448/// * `metadata_spec`: the metadata spec to perform the bulk set on
449/// * `start`: the starting address of the source data
450/// * `size`: size of the source data
451#[cfg(feature = "extreme_assertions")]
452pub fn verify_bset(metadata_spec: &SideMetadataSpec, start: Address, size: usize) {
453    let sanity_map = &mut CONTENT_SANITY_MAP.write().unwrap();
454    let start = align_to_region_start(metadata_spec, start);
455    let end = align_to_region_start(metadata_spec, start + size);
456    let max_value = (1 << (1 << metadata_spec.log_num_of_bits)) - 1;
457    match sanity_map.get_mut(metadata_spec) {
458        Some(spec_sanity_map) => {
459            let mut cursor = start;
460            let step: usize = 1 << metadata_spec.log_bytes_in_region;
461            while cursor < end {
462                spec_sanity_map.insert(cursor, max_value);
463                cursor += step;
464            }
465        }
466        None => {
467            panic!("Invalid Metadata Spec!");
468        }
469    }
470}
471
472/// Commits a side metadata bulk copy operation
473/// (set the bits to the corresponding bits of another metadata).
474/// Panics if the metadata spec is not valid.
475///
476/// The source and destination metadata must have the same granularity.
477///
478/// Arguments:
479/// * `dst_spec`: the metadata spec to bulk copy to
480/// * `start`: the starting address of the data
481/// * `size`: size of the data
482/// * `src_spec`: the metadata spec to bulk copy from
483#[cfg(feature = "extreme_assertions")]
484pub fn verify_bcopy(
485    dst_spec: &SideMetadataSpec,
486    start: Address,
487    size: usize,
488    src_spec: &SideMetadataSpec,
489) {
490    assert_eq!(src_spec.log_num_of_bits, dst_spec.log_num_of_bits);
491    assert_eq!(src_spec.log_bytes_in_region, dst_spec.log_bytes_in_region);
492
493    let sanity_map = &mut CONTENT_SANITY_MAP.write().unwrap();
494    let start = align_to_region_start(dst_spec, start);
495    let end = align_to_region_start(dst_spec, start + size);
496
497    // Rust doesn't like mutably borrowing two entries from `sanity_map` at the same time.
498    // So we load all values from `sanity_map[src_spec]` into an intermediate HashMap,
499    // and then store them to `sanity_map[dst_spec]`.
500
501    let mut tmp_map = HashMap::new();
502
503    {
504        let src_map = sanity_map
505            .get_mut(src_spec)
506            .expect("Invalid source Metadata Spec!");
507
508        let mut cursor = start;
509        let step: usize = 1 << src_spec.log_bytes_in_region;
510        while cursor < end {
511            let src_value = src_map.get(&cursor).copied().unwrap_or(0u64);
512            tmp_map.insert(cursor, src_value);
513            cursor += step;
514        }
515    }
516    {
517        let dst_map = sanity_map
518            .get_mut(dst_spec)
519            .expect("Invalid destination Metadata Spec!");
520
521        let mut cursor = start;
522        let step: usize = 1 << dst_spec.log_bytes_in_region;
523        while cursor < end {
524            let src_value = tmp_map.get(&cursor).copied().unwrap();
525            dst_map.insert(cursor, src_value);
526            cursor += step;
527        }
528    }
529}
530
531#[cfg(feature = "extreme_assertions")]
532use crate::util::metadata::metadata_val_traits::*;
533
534#[cfg(feature = "extreme_assertions")]
535fn truncate_value<T: MetadataValue>(log_num_of_bits: usize, val: u64) -> u64 {
536    // truncate the val if metadata's bits is fewer than the type's bits
537    if log_num_of_bits < T::LOG2 as usize {
538        val & ((1 << (1 << log_num_of_bits)) - 1)
539    } else {
540        val
541    }
542}
543
544#[cfg(feature = "extreme_assertions")]
545#[cfg(test)]
546mod truncate_tests {
547    use super::*;
548
549    #[test]
550    fn test_truncate() {
551        assert_eq!(truncate_value::<u8>(2, 0), 0);
552        assert_eq!(truncate_value::<u8>(2, 15), 15);
553        assert_eq!(truncate_value::<u8>(2, 16), 0);
554        assert_eq!(truncate_value::<u8>(2, 17), 1);
555    }
556}
557
558// When storing a value for a data address, we align the data address to the region start.
559// So when accessing any data address in the region, we will use the same data address to fetch the metadata value.
560fn align_to_region_start(spec: &SideMetadataSpec, data_addr: Address) -> Address {
561    data_addr.align_down(1 << spec.log_bytes_in_region)
562}
563
564/// Ensures a side metadata load operation returns the correct side metadata content.
565/// Panics if:
566/// 1 - the metadata spec is not valid,
567/// 2 - data address is not valid,
568/// 3 - the loaded side metadata content is not equal to the correct content.
569///
570/// Arguments:
571/// * `metadata_spec`: the metadata spec to verify the loaded content for
572/// * `data_addr`: the address of the source data
573/// * `actual_val`: the actual content returned by the side metadata load operation
574#[cfg(feature = "extreme_assertions")]
575pub fn verify_load<T: MetadataValue>(
576    metadata_spec: &SideMetadataSpec,
577    data_addr: Address,
578    actual_val: T,
579) {
580    let data_addr = align_to_region_start(metadata_spec, data_addr);
581    let actual_val: u64 = actual_val.to_u64().unwrap();
582    verify_metadata_address_bound(metadata_spec, data_addr);
583    let sanity_map = &mut CONTENT_SANITY_MAP.read().unwrap();
584    match sanity_map.get(metadata_spec) {
585        Some(spec_sanity_map) => {
586            // A content of None is Ok because we may load before store
587            let expected_val = if let Some(expected_val) = spec_sanity_map.get(&data_addr) {
588                *expected_val
589            } else {
590                0u64
591            };
592            assert!(
593                expected_val == actual_val,
594                "verify_load({:#?}, {}) -> Expected (0x{:x}) but found (0x{:x})",
595                metadata_spec,
596                data_addr,
597                expected_val,
598                actual_val
599            );
600        }
601        None => panic!("Invalid Metadata Spec: {:#?}", metadata_spec),
602    }
603}
604
605/// Commits a side metadata store operation.
606/// Panics if:
607/// 1 - the loaded side metadata content is not equal to the correct content.
608///
609/// Arguments:
610/// * `metadata_spec`: the metadata spec to commit the store operation for
611/// * `data_addr`: the address of the source data
612/// * `metadata`: the metadata content to store
613#[cfg(feature = "extreme_assertions")]
614pub fn verify_store<T: MetadataValue>(
615    metadata_spec: &SideMetadataSpec,
616    data_addr: Address,
617    metadata: T,
618) {
619    let data_addr = align_to_region_start(metadata_spec, data_addr);
620    let metadata: u64 = metadata.to_u64().unwrap();
621    verify_metadata_address_bound(metadata_spec, data_addr);
622    let new_val_wrapped = truncate_value::<T>(metadata_spec.log_num_of_bits, metadata);
623    let sanity_map = &mut CONTENT_SANITY_MAP.write().unwrap();
624    match sanity_map.get_mut(metadata_spec) {
625        Some(spec_sanity_map) => {
626            // Newly mapped memory including the side metadata memory is zeroed
627            let content = spec_sanity_map.entry(data_addr).or_insert(0);
628            *content = new_val_wrapped;
629        }
630        None => panic!("Invalid Metadata Spec: {:#?}", metadata_spec),
631    }
632}
633
634/// Commits an update operation and ensures it returns the correct old side metadata content.
635/// Panics if:
636/// 1 - the metadata spec is not valid,
637/// 2 - the old side metadata content is not equal to the correct old content.
638///
639/// Arguments:
640/// * `metadata_spec`: the metadata spec to verify the old content for
641/// * `data_addr`: the address of the source data
642/// * `old_val`: the expected old value
643/// * `new_val`: the new value the metadata should hold.
644#[cfg(feature = "extreme_assertions")]
645pub fn verify_update<T: MetadataValue>(
646    metadata_spec: &SideMetadataSpec,
647    data_addr: Address,
648    old_val: T,
649    new_val: T,
650) {
651    let data_addr = align_to_region_start(metadata_spec, data_addr);
652    verify_metadata_address_bound(metadata_spec, data_addr);
653
654    // truncate the new_val if metadata's bits is fewer than the type's bits
655    let new_val_wrapped =
656        truncate_value::<T>(metadata_spec.log_num_of_bits, new_val.to_u64().unwrap());
657
658    let sanity_map = &mut CONTENT_SANITY_MAP.write().unwrap();
659    match sanity_map.get_mut(metadata_spec) {
660        Some(spec_sanity_map) => {
661            let cur_val = spec_sanity_map.entry(data_addr).or_insert(0);
662            assert_eq!(
663                old_val.to_u64().unwrap(),
664                *cur_val,
665                "Expected old value: {} but found {}",
666                old_val,
667                cur_val
668            );
669            *cur_val = new_val_wrapped;
670        }
671        None => panic!("Invalid metadata spec: {:#?}", metadata_spec),
672    }
673}
674
675#[cfg(test)]
676mod tests {
677    use super::super::*;
678    use super::*;
679
680    #[test]
681    fn test_side_metadata_sanity_verify_global_specs_total_size() {
682        let spec_1 = SideMetadataSpec {
683            name: "spec_1",
684            is_global: true,
685            offset: 0,
686            log_num_of_bits: 0,
687            log_bytes_in_region: 0,
688        };
689        let spec_2 = SideMetadataSpec {
690            name: "spec_2",
691            is_global: true,
692            offset: side_metadata_offset_after(&spec_1),
693            log_num_of_bits: 0,
694            log_bytes_in_region: 0,
695        };
696
697        assert!(verify_global_specs_total_size(&[spec_1]).is_ok());
698        #[cfg(target_pointer_width = "64")]
699        assert!(verify_global_specs_total_size(&[spec_1, spec_2]).is_ok());
700        #[cfg(target_pointer_width = "32")]
701        assert!(verify_global_specs_total_size(&[spec_1, spec_2]).is_err());
702
703        let spec_2 = SideMetadataSpec {
704            name: "spec_2",
705            is_global: true,
706            offset: side_metadata_offset_after(&spec_1),
707            log_num_of_bits: 3,
708            log_bytes_in_region: 1,
709        };
710
711        assert!(verify_global_specs_total_size(&[spec_1, spec_2]).is_err());
712
713        let spec_1 = SideMetadataSpec {
714            name: "spec_1",
715            is_global: true,
716            offset: 0,
717            log_num_of_bits: 1,
718            #[cfg(target_pointer_width = "64")]
719            log_bytes_in_region: 0,
720            #[cfg(target_pointer_width = "32")]
721            log_bytes_in_region: 2,
722        };
723        let spec_2 = SideMetadataSpec {
724            name: "spec_2",
725            is_global: true,
726            offset: side_metadata_offset_after(&spec_1),
727            log_num_of_bits: 3,
728            #[cfg(target_pointer_width = "64")]
729            log_bytes_in_region: 2,
730            #[cfg(target_pointer_width = "32")]
731            log_bytes_in_region: 4,
732        };
733
734        assert!(verify_global_specs_total_size(&[spec_1, spec_2]).is_ok());
735        assert!(verify_global_specs_total_size(&[spec_1, spec_2, spec_1]).is_err());
736    }
737
738    #[test]
739    fn test_side_metadata_sanity_verify_no_overlap_contiguous() {
740        let spec_1 = SideMetadataSpec {
741            name: "spec_1",
742            is_global: true,
743            offset: 0,
744            log_num_of_bits: 0,
745            log_bytes_in_region: 0,
746        };
747        let spec_2 = SideMetadataSpec {
748            name: "spec_2",
749            is_global: true,
750            offset: side_metadata_offset_after(&spec_1),
751            log_num_of_bits: 0,
752            log_bytes_in_region: 0,
753        };
754
755        assert!(verify_no_overlap_contiguous(&spec_1, &spec_1).is_err());
756        assert!(verify_no_overlap_contiguous(&spec_1, &spec_2).is_ok());
757
758        let spec_1 = SideMetadataSpec {
759            name: "spec_1",
760            is_global: true,
761            offset: 1,
762            log_num_of_bits: 0,
763            log_bytes_in_region: 0,
764        };
765
766        // spec_2 starts after spec_1 end because spec_1 base is shifted by 1.
767        // With relative offsets and runtime base, they no longer overlap.
768        assert!(verify_no_overlap_contiguous(&spec_1, &spec_2).is_ok());
769
770        let spec_1 = SideMetadataSpec {
771            name: "spec_1",
772            is_global: true,
773            offset: 0,
774            log_num_of_bits: 0,
775            log_bytes_in_region: 0,
776        };
777        let spec_2 = SideMetadataSpec {
778            name: "spec_2",
779            is_global: true,
780            // We specifically make up an invalid offset
781            offset: (Address::ZERO + metadata_address_range_size(&spec_1) - 1).as_usize(),
782            log_num_of_bits: 0,
783            log_bytes_in_region: 0,
784        };
785
786        assert!(verify_no_overlap_contiguous(&spec_1, &spec_2).is_err());
787    }
788
789    #[cfg(target_pointer_width = "32")]
790    #[test]
791    fn test_side_metadata_sanity_verify_no_overlap_chunked() {
792        let spec_1 = SideMetadataSpec {
793            name: "spec_1",
794            is_global: false,
795            offset: 0,
796            log_num_of_bits: 0,
797            log_bytes_in_region: 0,
798        };
799        let spec_2 = SideMetadataSpec {
800            name: "spec_2",
801            is_global: false,
802            offset: side_metadata_offset_after(&spec_1),
803            log_num_of_bits: 0,
804            log_bytes_in_region: 0,
805        };
806
807        assert!(verify_no_overlap_chunked(&spec_1, &spec_1).is_err());
808        assert!(verify_no_overlap_chunked(&spec_1, &spec_2).is_ok());
809
810        let spec_1 = SideMetadataSpec {
811            name: "spec_1",
812            is_global: false,
813            offset: 1,
814            log_num_of_bits: 0,
815            log_bytes_in_region: 0,
816        };
817
818        assert!(verify_no_overlap_chunked(&spec_1, &spec_2).is_err());
819
820        let spec_1 = SideMetadataSpec {
821            name: "spec_1",
822            is_global: false,
823            offset: 0,
824            log_num_of_bits: 0,
825            log_bytes_in_region: 0,
826        };
827        let spec_2 = SideMetadataSpec {
828            name: "spec_2",
829            is_global: false,
830            // We make up an invalid offset
831            offset: spec_1.get_offset_for_chunked()
832                + metadata_bytes_per_chunk(spec_1.log_bytes_in_region, spec_1.log_num_of_bits)
833                - 1,
834            log_num_of_bits: 0,
835            log_bytes_in_region: 0,
836        };
837
838        assert!(verify_no_overlap_chunked(&spec_1, &spec_2).is_err());
839    }
840
841    #[cfg(target_pointer_width = "32")]
842    #[test]
843    fn test_side_metadata_sanity_verify_local_specs_size() {
844        let spec_1 = SideMetadataSpec {
845            name: "spec_1",
846            is_global: false,
847            offset: 0,
848            log_num_of_bits: 0,
849            log_bytes_in_region: 0,
850        };
851
852        assert!(verify_local_specs_size(&[spec_1]).is_ok());
853        assert!(verify_local_specs_size(&[spec_1, spec_1]).is_err());
854        assert!(verify_local_specs_size(&[spec_1, spec_1, spec_1, spec_1, spec_1]).is_err());
855    }
856
857    #[test]
858    fn test_side_metadata_sanity_get_all_local_specs() {
859        let spec_1 = SideMetadataSpec {
860            name: "spec_1",
861            is_global: false,
862            offset: 0,
863            log_num_of_bits: 0,
864            log_bytes_in_region: 0,
865        };
866
867        let mut sanity = SideMetadataSanity::new();
868        sanity.verify_metadata_context(
869            "policy1",
870            &SideMetadataContext {
871                global: vec![],
872                local: vec![spec_1],
873            },
874        );
875        sanity.verify_metadata_context(
876            "policy2",
877            &SideMetadataContext {
878                global: vec![],
879                local: vec![spec_1],
880            },
881        );
882
883        let local_specs = sanity.get_all_specs(false);
884        assert_eq!(local_specs.len(), 1);
885    }
886}