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