| use super::*; |
| use crate::mir::interpret::alloc_range; |
| |
| #[test] |
| fn uninit_mask() { |
| let mut mask = InitMask::new(Size::from_bytes(500), false); |
| assert!(!mask.get(Size::from_bytes(499))); |
| mask.set_range(alloc_range(Size::from_bytes(499), Size::from_bytes(1)), true); |
| assert!(mask.get(Size::from_bytes(499))); |
| mask.set_range((100..256).into(), true); |
| for i in 0..100 { |
| assert!(!mask.get(Size::from_bytes(i)), "{i} should not be set"); |
| } |
| for i in 100..256 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| for i in 256..499 { |
| assert!(!mask.get(Size::from_bytes(i)), "{i} should not be set"); |
| } |
| } |
| |
| /// Returns the number of materialized blocks for this mask. |
| fn materialized_block_count(mask: &InitMask) -> usize { |
| match mask.blocks { |
| InitMaskBlocks::Lazy { .. } => 0, |
| InitMaskBlocks::Materialized(ref blocks) => blocks.blocks.len(), |
| } |
| } |
| |
| #[test] |
| fn materialize_mask_within_range() { |
| // To have spare bits, we use a mask size smaller than its block size of 64. |
| let mut mask = InitMask::new(Size::from_bytes(16), false); |
| assert_eq!(materialized_block_count(&mask), 0); |
| |
| // Forces materialization, but doesn't require growth. This is case #1 documented in the |
| // `set_range` method. |
| mask.set_range((8..16).into(), true); |
| assert_eq!(materialized_block_count(&mask), 1); |
| |
| for i in 0..8 { |
| assert!(!mask.get(Size::from_bytes(i)), "{i} should not be set"); |
| } |
| for i in 8..16 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| } |
| |
| #[test] |
| fn grow_within_unused_bits_with_full_overwrite() { |
| // To have spare bits, we use a mask size smaller than its block size of 64. |
| let mut mask = InitMask::new(Size::from_bytes(16), true); |
| for i in 0..16 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| |
| // Grow without requiring an additional block. Full overwrite. |
| // This can be fully handled without materialization. |
| let range = (0..32).into(); |
| mask.set_range(range, true); |
| |
| for i in 0..32 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| |
| assert_eq!(materialized_block_count(&mask), 0); |
| } |
| |
| // This test checks that an initmask's spare capacity is correctly used when growing within block |
| // capacity. This can be fully handled without materialization. |
| #[test] |
| fn grow_same_state_within_unused_bits() { |
| // To have spare bits, we use a mask size smaller than its block size of 64. |
| let mut mask = InitMask::new(Size::from_bytes(16), true); |
| for i in 0..16 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| |
| // Grow without requiring an additional block. The gap between the current length and the |
| // range's beginning should be set to the same value as the range. |
| let range = (24..32).into(); |
| mask.set_range(range, true); |
| |
| // We want to make sure the unused bits in the first block are correct |
| for i in 16..24 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| |
| for i in 24..32 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| |
| assert_eq!(1, mask.range_as_init_chunks((0..32).into()).count()); |
| assert_eq!(materialized_block_count(&mask), 0); |
| } |
| |
| // This is the same test as `grow_same_state_within_unused_bits` but with both init and uninit |
| // states: this forces materialization; otherwise the mask could stay lazy even when needing to |
| // grow. |
| #[test] |
| fn grow_mixed_state_within_unused_bits() { |
| // To have spare bits, we use a mask size smaller than its block size of 64. |
| let mut mask = InitMask::new(Size::from_bytes(16), true); |
| for i in 0..16 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| |
| // Grow without requiring an additional block. The gap between the current length and the |
| // range's beginning should be set to the same value as the range. Note: since this is fully |
| // out-of-bounds of the current mask, this is case #3 described in the `set_range` method. |
| let range = (24..32).into(); |
| mask.set_range(range, false); |
| |
| // We want to make sure the unused bits in the first block are correct |
| for i in 16..24 { |
| assert!(!mask.get(Size::from_bytes(i)), "{i} should not be set"); |
| } |
| |
| for i in 24..32 { |
| assert!(!mask.get(Size::from_bytes(i)), "{i} should not be set"); |
| } |
| |
| assert_eq!(1, mask.range_as_init_chunks((0..16).into()).count()); |
| assert_eq!(2, mask.range_as_init_chunks((0..32).into()).count()); |
| assert_eq!(materialized_block_count(&mask), 1); |
| } |
| |
| // This is similar to `grow_mixed_state_within_unused_bits` to force materialization, but the range |
| // to set partially overlaps the mask, so this requires a different growth + write pattern in the |
| // mask. |
| #[test] |
| fn grow_within_unused_bits_with_overlap() { |
| // To have spare bits, we use a mask size smaller than its block size of 64. |
| let mut mask = InitMask::new(Size::from_bytes(16), true); |
| for i in 0..16 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| |
| // Grow without requiring an additional block, but leave no gap after the current len. Note: |
| // since this is partially out-of-bounds of the current mask, this is case #2 described in the |
| // `set_range` method. |
| let range = (8..24).into(); |
| mask.set_range(range, false); |
| |
| // We want to make sure the unused bits in the first block are correct |
| for i in 8..24 { |
| assert!(!mask.get(Size::from_bytes(i)), "{i} should not be set"); |
| } |
| |
| assert_eq!(1, mask.range_as_init_chunks((0..8).into()).count()); |
| assert_eq!(2, mask.range_as_init_chunks((0..24).into()).count()); |
| assert_eq!(materialized_block_count(&mask), 1); |
| } |
| |
| // Force materialization before a full overwrite: the mask can now become lazy. |
| #[test] |
| fn grow_mixed_state_within_unused_bits_and_full_overwrite() { |
| // To have spare bits, we use a mask size smaller than its block size of 64. |
| let mut mask = InitMask::new(Size::from_bytes(16), true); |
| let range = (0..16).into(); |
| assert!(mask.is_range_initialized(range).is_ok()); |
| |
| // Force materialization. |
| let range = (8..24).into(); |
| mask.set_range(range, false); |
| assert!(mask.is_range_initialized(range).is_err()); |
| assert_eq!(materialized_block_count(&mask), 1); |
| |
| // Full overwrite, lazy blocks would be enough from now on. |
| let range = (0..32).into(); |
| mask.set_range(range, true); |
| assert!(mask.is_range_initialized(range).is_ok()); |
| |
| assert_eq!(1, mask.range_as_init_chunks((0..32).into()).count()); |
| assert_eq!(materialized_block_count(&mask), 0); |
| } |
| |
| // Check that growth outside the current capacity can still be lazy: if the init state doesn't |
| // change, we don't need materialized blocks. |
| #[test] |
| fn grow_same_state_outside_capacity() { |
| // To have spare bits, we use a mask size smaller than its block size of 64. |
| let mut mask = InitMask::new(Size::from_bytes(16), true); |
| for i in 0..16 { |
| assert!(mask.get(Size::from_bytes(i)), "{i} should be set"); |
| } |
| assert_eq!(materialized_block_count(&mask), 0); |
| |
| // Grow to 10 blocks with the same init state. |
| let range = (24..640).into(); |
| mask.set_range(range, true); |
| |
| assert_eq!(1, mask.range_as_init_chunks((0..640).into()).count()); |
| assert_eq!(materialized_block_count(&mask), 0); |
| } |