Skip to main content

storage_units/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Types for representing and performing efficient power-of-two block size arithmetic and
6//! alignments.
7
8use std::borrow::Borrow;
9use std::cmp::Ordering;
10use std::fmt::{self, Debug};
11use std::ops::{
12    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Range, Rem, RemAssign, Sub, SubAssign,
13};
14
15#[cfg(target_os = "fuchsia")]
16mod page;
17#[cfg(target_os = "fuchsia")]
18pub use crate::page::*;
19
20/// Defines the block size specification for use with [`GenericBlockSize`]. Only block sizes which
21/// are a power of 2 are supported.
22// TODO(https://github.com/rust-lang/rust/issues/143874): Make this a const trait.
23pub trait BlockSizeSpec: Copy + Clone + Debug + Eq + PartialEq {
24    /// Returns the block size in bytes (e.g. `4096` for 4096-byte blocks).
25    fn size(self) -> u64;
26
27    /// Returns the bitmask corresponding to `size - 1` (e.g. `4095` for 4096-byte blocks).
28    fn mask(self) -> u64;
29
30    /// Returns the power-of-two bit shift (e.g. `12` for 4096-byte blocks).
31    fn shift(self) -> u32;
32}
33
34/// Represents a block size that is a power of 2, parameterized by a [`BlockSizeSpec`].
35///
36/// Provides zero-cost bitwise operations for alignments, conversions, and arithmetic with
37/// integers.
38#[derive(Copy, Clone, Debug, Eq)]
39pub struct GenericBlockSize<T: BlockSizeSpec>(T);
40
41impl<T: BlockSizeSpec> GenericBlockSize<T> {
42    #[inline(always)]
43    pub fn from_spec(spec: T) -> Self {
44        Self(spec)
45    }
46
47    /// Returns the block size in bytes.
48    #[inline(always)]
49    pub fn get(self) -> u64 {
50        self.0.size()
51    }
52
53    /// Returns the alignment mask (`size - 1`).
54    #[inline(always)]
55    pub fn mask(self) -> u64 {
56        self.0.mask()
57    }
58
59    /// Returns the power-of-two bit shift (e.g. `12` for 4096-byte blocks).
60    #[inline(always)]
61    pub fn shift(self) -> u32 {
62        self.0.shift()
63    }
64
65    /// Returns `true` if `value` is aligned to this block size.
66    #[inline(always)]
67    pub fn is_aligned(self, value: impl IsAligned<T>) -> bool {
68        value.is_aligned(self)
69    }
70
71    /// Aligns `bytes` up to the nearest block boundary, returning `None` on overflow.
72    #[inline(always)]
73    pub fn align_up(self, bytes: u64) -> Option<u64> {
74        match bytes.checked_add(self.mask()) {
75            Some(bytes) => Some(self.align_down(bytes)),
76            None => None,
77        }
78    }
79
80    /// Aligns `bytes` down to the nearest block boundary.
81    #[inline(always)]
82    pub fn align_down(self, bytes: u64) -> u64 {
83        bytes & !self.mask()
84    }
85
86    /// Aligns `bytes` up to the nearest block boundary and returns the total number of blocks.
87    #[inline(always)]
88    pub fn align_up_to_blocks(self, bytes: u64) -> u64 {
89        bytes / self + ((bytes % self != 0) as u64)
90    }
91
92    /// Aligns the range outwards to block boundaries (`start` aligned down, `end` aligned up).
93    ///
94    /// Returns `None` if aligning `end` overflows `u64`.
95    #[inline(always)]
96    pub fn align_range_outwards(self, range: impl Borrow<Range<u64>>) -> Option<Range<u64>> {
97        let range = range.borrow();
98        let start = self.align_down(range.start);
99        let end = self.align_up(range.end)?;
100        Some(start..end)
101    }
102}
103
104/// A [`BlockSizeSpec`] that packs the alignment mask in the upper 32 bits and the power-of-two bit
105/// shift in the lower 32 bits: `(mask << 32) | shift`.
106///
107/// This representation is an extremely efficient way to store a runtime power-of-two block size on
108/// modern 64-bit architectures (AArch64 and x86_64).
109///
110/// ### Why this representation was chosen:
111///
112/// 1. **Zero Dynamic Bit-Scanning Latency:** Representations that only store the size or the mask
113///    must dynamically compute `shift` via trailing-zero counting (`trailing_zeros` or
114///    `trailing_ones`).
115///    - On **ARM64**, computing trailing ones requires a dependent instruction chain: `mvn` +
116///      `rbit` + `clz`. On in-order efficiency cores, this 3-instruction dependency stalls the
117///      pipeline, making operations like `shift`, `div`, and `mul` more than 2x slower.
118///
119/// 2. **The 6-Bit Shift Rule (Zero-Instruction Unpacking for Shifts):** Variable shift instructions
120///    on both AArch64 (`lsr xd, xn, xm` / `lsl xd, xn, xm`) and x86_64 (`shrx r64, r64, r64`) only
121///    inspect the lowest 6 bits of the register operand (`shift_amount mod 64`). Because `shift`
122///    (which is between 0 and 32) is placed in the low 32 bits (`[31..0]`), the lowest 6 bits of
123///    the raw `u64` are *literally* the shift amount. Consequently, compilers do not need to mask,
124///    clear, or move `shift` into a temporary register before shifting. The raw 64-bit
125///    `MaskShiftSpec` register can be passed directly as the shift operand, executing in a **single
126///    instruction and single cycle** (`lsr x_val, x_val, x_bs`).
127///
128/// 3. **ARM64 Fused Barrel Shifter (`adds ..., lsr #32`):** ARM64 ALU instructions feature a
129///    hardware barrel shifter that can shift an input operand at zero extra cycle cost. To add the
130///    mask to an offset (such as during `align_up` or `align_up_to_blocks`), the compiler emits:
131///    ```text
132///    adds x_res, x_val, x_bs, lsr #32
133///    ```
134///    This shifts out the low 32 bits and adds the high 32-bit mask in a **single cycle**. In
135///    `align_up_to_blocks`, the compiler follows this immediately with `lsr x_res, x_res, x_bs`,
136///    performing the combined align-and-divide in just **two back-to-back single-cycle
137///    instructions**.
138///
139/// 4. **Packed `u64` vs. Two Separate `u32` Fields:** Storing a single packed `u64` is superior to
140///    a struct with two `u32`s (`{ shift: u32, mask: u32 }`):
141///    - **Register Pressure & ABI:** Under Rust's internal calling convention, small multi-field
142///      structs are scalarized across multiple registers when passed by value. A packed `u64`
143///      always consumes a single argument register, avoiding register pressure and spills in
144///      functions with many arguments.
145///    - **Register Reuse on ARM64:** With a single `u64`, the exact same register can be fed
146///      directly into both the barrel-shifted mask operation (`adds ..., x0, lsr #32`) and the
147///      shift (`lsr ..., x0`). Separate fields force values into different registers and require
148///      moving or zero-extending them.
149#[derive(Copy, Clone, Debug, Eq, PartialEq)]
150pub struct MaskShiftSpec(u64);
151
152impl BlockSizeSpec for MaskShiftSpec {
153    #[inline(always)]
154    fn size(self) -> u64 {
155        self.mask() + 1
156    }
157
158    #[inline(always)]
159    fn mask(self) -> u64 {
160        self.0 >> 32
161    }
162
163    #[inline(always)]
164    fn shift(self) -> u32 {
165        self.0 as u32
166    }
167}
168
169impl MaskShiftSpec {
170    /// Constructs a new MaskShiftSpec.
171    ///
172    /// #Panics
173    ///
174    /// Panics in debug mode if `block_size` is not a power of 2 or is greater than 4GiB.
175    #[inline(always)]
176    const fn new(block_size: u64) -> Self {
177        debug_assert!(block_size.is_power_of_two() && block_size <= (1 << 32));
178        let mask = (block_size - 1) as u32 as u64;
179        let shift = (block_size - 1).trailing_ones();
180        Self((mask << 32) | (shift as u64))
181    }
182}
183
184/// A [`GenericBlockSize`] configured with a block size stored as a [`MaskShiftSpec`].
185pub type BlockSize = GenericBlockSize<MaskShiftSpec>;
186
187impl BlockSize {
188    pub const SIZE_512B: Self = Self::new(1 << 9).unwrap();
189    pub const SIZE_1KIB: Self = Self::new(1 << 10).unwrap();
190    pub const SIZE_2KIB: Self = Self::new(1 << 11).unwrap();
191    pub const SIZE_4KIB: Self = Self::new(1 << 12).unwrap();
192    pub const SIZE_8KIB: Self = Self::new(1 << 13).unwrap();
193    pub const SIZE_16KIB: Self = Self::new(1 << 14).unwrap();
194    pub const SIZE_32KIB: Self = Self::new(1 << 15).unwrap();
195    pub const SIZE_64KIB: Self = Self::new(1 << 16).unwrap();
196    pub const SIZE_128KIB: Self = Self::new(1 << 17).unwrap();
197    pub const SIZE_256KIB: Self = Self::new(1 << 18).unwrap();
198    pub const SIZE_512KIB: Self = Self::new(1 << 19).unwrap();
199    pub const SIZE_1MIB: Self = Self::new(1 << 20).unwrap();
200
201    /// Constructs a `BlockSize` from a `u32` byte count if it is a power of 2 and not equal to 0.
202    #[inline(always)]
203    pub const fn new(block_size: u32) -> Option<Self> {
204        Self::from_u64(block_size as u64)
205    }
206
207    /// Constructs a `BlockSize` from a `u64` if it is a power of 2, not equal to 0, and the mask
208    /// fits in a u32. This is equivalent to `Self::new` but allows for constructing a 4GiB block
209    /// size.
210    #[inline(always)]
211    pub const fn from_u64(block_size: u64) -> Option<Self> {
212        if block_size.is_power_of_two() && block_size <= (1 << 32) {
213            Some(GenericBlockSize(MaskShiftSpec::new(block_size)))
214        } else {
215            None
216        }
217    }
218
219    /// Returns the block size in bytes.
220    ///
221    /// Equivalent to `Self::get` but can be called from a const context.
222    // TODO(https://github.com/rust-lang/rust/issues/143874) Remove once `BlockSizeSpec::mask` is
223    // const.
224    #[inline(always)]
225    pub const fn size(self) -> u64 {
226        (self.0.0 >> 32) + 1
227    }
228}
229
230macro_rules! impl_binary_op {
231    ($trait:ident, $method:ident, $lhs:ty, $rhs:ty, |$a:ident, $b:ident| $expr:expr) => {
232        impl<T: BlockSizeSpec> $trait<$rhs> for $lhs {
233            type Output = u64;
234            #[inline(always)]
235            fn $method(self, other: $rhs) -> u64 {
236                let $a = self;
237                let $b = other;
238                $expr
239            }
240        }
241    };
242}
243
244// Add: GenericBlockSize + u64 and u64 + GenericBlockSize (and reference variants)
245impl_binary_op!(Add, add, GenericBlockSize<T>, u64, |bs, val| bs.get() + val);
246impl_binary_op!(Add, add, GenericBlockSize<T>, &u64, |bs, val| bs.get() + *val);
247impl_binary_op!(Add, add, &GenericBlockSize<T>, u64, |bs, val| bs.get() + val);
248impl_binary_op!(Add, add, &GenericBlockSize<T>, &u64, |bs, val| bs.get() + *val);
249
250impl_binary_op!(Add, add, u64, GenericBlockSize<T>, |val, bs| val + bs.get());
251impl_binary_op!(Add, add, u64, &GenericBlockSize<T>, |val, bs| val + bs.get());
252impl_binary_op!(Add, add, &u64, GenericBlockSize<T>, |val, bs| *val + bs.get());
253impl_binary_op!(Add, add, &u64, &GenericBlockSize<T>, |val, bs| *val + bs.get());
254
255// Sub: GenericBlockSize - u64 and u64 - GenericBlockSize (and reference variants)
256impl_binary_op!(Sub, sub, GenericBlockSize<T>, u64, |bs, val| bs.get() - val);
257impl_binary_op!(Sub, sub, GenericBlockSize<T>, &u64, |bs, val| bs.get() - *val);
258impl_binary_op!(Sub, sub, &GenericBlockSize<T>, u64, |bs, val| bs.get() - val);
259impl_binary_op!(Sub, sub, &GenericBlockSize<T>, &u64, |bs, val| bs.get() - *val);
260
261impl_binary_op!(Sub, sub, u64, GenericBlockSize<T>, |val, bs| val - bs.get());
262impl_binary_op!(Sub, sub, u64, &GenericBlockSize<T>, |val, bs| val - bs.get());
263impl_binary_op!(Sub, sub, &u64, GenericBlockSize<T>, |val, bs| *val - bs.get());
264impl_binary_op!(Sub, sub, &u64, &GenericBlockSize<T>, |val, bs| *val - bs.get());
265
266#[inline(always)]
267fn mul_block_size<T: BlockSizeSpec>(val: u64, bs: GenericBlockSize<T>) -> u64 {
268    let shift = bs.shift();
269    let res = val << shift;
270    // Preserve the panic on overflow during multiplication in debug builds.
271    debug_assert_eq!(res >> shift, val, "attempt to multiply with overflow");
272    res
273}
274
275// Mul: GenericBlockSize * u64 and u64 * GenericBlockSize (and reference variants)
276impl_binary_op!(Mul, mul, GenericBlockSize<T>, u64, |bs, val| mul_block_size(val, bs));
277impl_binary_op!(Mul, mul, GenericBlockSize<T>, &u64, |bs, val| mul_block_size(*val, bs));
278impl_binary_op!(Mul, mul, &GenericBlockSize<T>, u64, |bs, val| mul_block_size(val, *bs));
279impl_binary_op!(Mul, mul, &GenericBlockSize<T>, &u64, |bs, val| mul_block_size(*val, *bs));
280
281impl_binary_op!(Mul, mul, u64, GenericBlockSize<T>, |val, bs| mul_block_size(val, bs));
282impl_binary_op!(Mul, mul, u64, &GenericBlockSize<T>, |val, bs| mul_block_size(val, *bs));
283impl_binary_op!(Mul, mul, &u64, GenericBlockSize<T>, |val, bs| mul_block_size(*val, bs));
284impl_binary_op!(Mul, mul, &u64, &GenericBlockSize<T>, |val, bs| mul_block_size(*val, *bs));
285
286// Div: u64 / GenericBlockSize (and reference variants)
287impl_binary_op!(Div, div, u64, GenericBlockSize<T>, |val, bs| val >> bs.shift());
288impl_binary_op!(Div, div, u64, &GenericBlockSize<T>, |val, bs| val >> bs.shift());
289impl_binary_op!(Div, div, &u64, GenericBlockSize<T>, |val, bs| *val >> bs.shift());
290impl_binary_op!(Div, div, &u64, &GenericBlockSize<T>, |val, bs| *val >> bs.shift());
291
292// Rem: u64 % GenericBlockSize (and reference variants)
293impl_binary_op!(Rem, rem, u64, GenericBlockSize<T>, |val, bs| val & bs.mask());
294impl_binary_op!(Rem, rem, u64, &GenericBlockSize<T>, |val, bs| val & bs.mask());
295impl_binary_op!(Rem, rem, &u64, GenericBlockSize<T>, |val, bs| *val & bs.mask());
296impl_binary_op!(Rem, rem, &u64, &GenericBlockSize<T>, |val, bs| *val & bs.mask());
297
298macro_rules! impl_assign_op {
299    ($trait:ident, $method:ident, $rhs:ty, |$a:ident, $b:ident| $expr:expr) => {
300        impl<T: BlockSizeSpec> $trait<$rhs> for u64 {
301            #[inline(always)]
302            fn $method(&mut self, other: $rhs) {
303                let $a = self;
304                let $b = other;
305                $expr
306            }
307        }
308    };
309}
310
311// Assign operations: u64 <op>= GenericBlockSize (and reference variants)
312impl_assign_op!(AddAssign, add_assign, GenericBlockSize<T>, |val, bs| *val += bs.get());
313impl_assign_op!(AddAssign, add_assign, &GenericBlockSize<T>, |val, bs| *val += bs.get());
314
315impl_assign_op!(SubAssign, sub_assign, GenericBlockSize<T>, |val, bs| *val -= bs.get());
316impl_assign_op!(SubAssign, sub_assign, &GenericBlockSize<T>, |val, bs| *val -= bs.get());
317
318impl_assign_op!(MulAssign, mul_assign, GenericBlockSize<T>, |val, bs| *val =
319    mul_block_size(*val, bs));
320impl_assign_op!(MulAssign, mul_assign, &GenericBlockSize<T>, |val, bs| *val =
321    mul_block_size(*val, *bs));
322
323impl_assign_op!(DivAssign, div_assign, GenericBlockSize<T>, |val, bs| *val >>= bs.shift());
324impl_assign_op!(DivAssign, div_assign, &GenericBlockSize<T>, |val, bs| *val >>= bs.shift());
325
326impl_assign_op!(RemAssign, rem_assign, GenericBlockSize<T>, |val, bs| *val &= bs.mask());
327impl_assign_op!(RemAssign, rem_assign, &GenericBlockSize<T>, |val, bs| *val &= bs.mask());
328
329macro_rules! impl_partial_eq_ord {
330    ($lhs:ty, $rhs:ty, |$a:ident, $b:ident| ($expr_a:expr, $expr_b:expr)) => {
331        impl<T: BlockSizeSpec> PartialEq<$rhs> for $lhs {
332            #[inline(always)]
333            fn eq(&self, other: &$rhs) -> bool {
334                let $a = self;
335                let $b = other;
336                $expr_a == $expr_b
337            }
338        }
339
340        impl<T: BlockSizeSpec> PartialOrd<$rhs> for $lhs {
341            #[inline(always)]
342            fn partial_cmp(&self, other: &$rhs) -> Option<Ordering> {
343                let $a = self;
344                let $b = other;
345                $expr_a.partial_cmp(&$expr_b)
346            }
347        }
348    };
349}
350
351// Cross-type comparisons: PartialEq and PartialOrd between GenericBlockSize and u64 (and reference variants)
352impl_partial_eq_ord!(GenericBlockSize<T>, u64, |bs, val| (bs.get(), *val));
353impl_partial_eq_ord!(GenericBlockSize<T>, &u64, |bs, val| (bs.get(), **val));
354impl_partial_eq_ord!(&GenericBlockSize<T>, u64, |bs, val| (bs.get(), *val));
355impl_partial_eq_ord!(u64, GenericBlockSize<T>, |val, bs| (*val, bs.get()));
356impl_partial_eq_ord!(u64, &GenericBlockSize<T>, |val, bs| (*val, bs.get()));
357impl_partial_eq_ord!(&u64, GenericBlockSize<T>, |val, bs| (**val, bs.get()));
358
359impl<T: BlockSizeSpec, U: BlockSizeSpec> PartialEq<GenericBlockSize<T>> for GenericBlockSize<U> {
360    #[inline(always)]
361    fn eq(&self, other: &GenericBlockSize<T>) -> bool {
362        self.get() == other.get()
363    }
364}
365
366impl<T: BlockSizeSpec, U: BlockSizeSpec> PartialOrd<GenericBlockSize<T>> for GenericBlockSize<U> {
367    #[inline(always)]
368    fn partial_cmp(&self, other: &GenericBlockSize<T>) -> Option<Ordering> {
369        self.get().partial_cmp(&other.get())
370    }
371}
372
373impl<T: BlockSizeSpec> Ord for GenericBlockSize<T> {
374    #[inline(always)]
375    fn cmp(&self, other: &Self) -> Ordering {
376        self.get().cmp(&other.get())
377    }
378}
379
380impl<T: BlockSizeSpec> std::hash::Hash for GenericBlockSize<T> {
381    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
382        self.get().hash(state);
383    }
384}
385
386macro_rules! impl_fmt {
387    ($($trait:ident),*) => {
388        $(
389            impl<T: BlockSizeSpec> fmt::$trait for GenericBlockSize<T> {
390                #[inline(always)]
391                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392                    fmt::$trait::fmt(&self.get(), f)
393                }
394            }
395        )*
396    };
397}
398
399// Formatting traits: Display, Binary, LowerHex, UpperHex, Octal
400impl_fmt!(Display, Binary, LowerHex, UpperHex, Octal);
401
402/// A trait for checking if something is aligned to a block size.
403pub trait IsAligned<T: BlockSizeSpec> {
404    fn is_aligned(self, block_size: GenericBlockSize<T>) -> bool;
405}
406
407impl<T: BlockSizeSpec> IsAligned<T> for u64 {
408    #[inline(always)]
409    fn is_aligned(self, block_size: GenericBlockSize<T>) -> bool {
410        self % block_size == 0
411    }
412}
413
414impl<T: BlockSizeSpec> IsAligned<T> for &u64 {
415    #[inline(always)]
416    fn is_aligned(self, block_size: GenericBlockSize<T>) -> bool {
417        block_size.is_aligned(*self)
418    }
419}
420
421impl<T: BlockSizeSpec> IsAligned<T> for Range<u64> {
422    #[inline(always)]
423    fn is_aligned(self, block_size: GenericBlockSize<T>) -> bool {
424        block_size.is_aligned(&self)
425    }
426}
427
428impl<T: BlockSizeSpec> IsAligned<T> for &Range<u64> {
429    #[inline(always)]
430    fn is_aligned(self, block_size: GenericBlockSize<T>) -> bool {
431        (self.start | self.end) % block_size == 0
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    #[fuchsia::test]
440    fn test_new() {
441        assert!(BlockSize::new(0).is_none());
442        assert_eq!(BlockSize::new(1).unwrap().get(), 1);
443        assert!(BlockSize::new(3).is_none());
444        assert_eq!(BlockSize::new(512).unwrap().get(), 512);
445        assert_eq!(BlockSize::new(4096).unwrap().get(), 4096);
446        assert!(BlockSize::new(u32::MAX).is_none());
447
448        assert!(BlockSize::from_u64(0).is_none());
449        assert_eq!(BlockSize::from_u64(1).unwrap().get(), 1);
450        assert!(BlockSize::from_u64(3).is_none());
451        assert_eq!(BlockSize::from_u64(4096).unwrap().get(), 4096);
452        assert_eq!(BlockSize::from_u64(u32::MAX as u64 + 1).unwrap().get(), 1 << 32);
453        assert!(BlockSize::from_u64((1 << 32) + 1).is_none());
454        assert!(BlockSize::from_u64(1 << 33).is_none());
455        assert!(BlockSize::from_u64(u64::MAX).is_none());
456    }
457
458    #[fuchsia::test]
459    fn test_getters_and_constants() {
460        let bs = BlockSize::SIZE_4KIB;
461        assert_eq!(bs.get(), 4096);
462        assert_eq!(bs.size(), 4096);
463        assert_eq!(bs.mask(), 4095);
464        assert_eq!(bs.shift(), 12);
465
466        assert_eq!(BlockSize::SIZE_512B.get(), 512);
467        assert_eq!(BlockSize::SIZE_1KIB.get(), 1024);
468        assert_eq!(BlockSize::SIZE_2KIB.get(), 2048);
469        assert_eq!(BlockSize::SIZE_4KIB.get(), 4096);
470        assert_eq!(BlockSize::SIZE_8KIB.get(), 8192);
471        assert_eq!(BlockSize::SIZE_16KIB.get(), 16384);
472        assert_eq!(BlockSize::SIZE_32KIB.get(), 32768);
473        assert_eq!(BlockSize::SIZE_64KIB.get(), 65536);
474        assert_eq!(BlockSize::SIZE_128KIB.get(), 131072);
475        assert_eq!(BlockSize::SIZE_256KIB.get(), 262144);
476        assert_eq!(BlockSize::SIZE_512KIB.get(), 524288);
477        assert_eq!(BlockSize::SIZE_1MIB.get(), 1048576);
478    }
479
480    #[fuchsia::test]
481    fn test_alignment_helpers() {
482        let bs = BlockSize::SIZE_4KIB;
483
484        assert!(bs.is_aligned(0u64));
485        assert!(bs.is_aligned(4096u64));
486        assert!(bs.is_aligned(&4096u64));
487        assert!(!bs.is_aligned(4095u64));
488        assert!(!bs.is_aligned(4097u64));
489
490        assert!(bs.is_aligned(0..4096));
491        assert!(bs.is_aligned(&(4096..8192)));
492        assert!(!bs.is_aligned(0..4095));
493        assert!(!bs.is_aligned(1..4096));
494        assert!(!bs.is_aligned(1..4095));
495
496        assert_eq!(bs.align_down(0), 0);
497        assert_eq!(bs.align_down(4095), 0);
498        assert_eq!(bs.align_down(4096), 4096);
499        assert_eq!(bs.align_down(4097), 4096);
500
501        assert_eq!(bs.align_up(0), Some(0));
502        assert_eq!(bs.align_up(1), Some(4096));
503        assert_eq!(bs.align_up(4095), Some(4096));
504        assert_eq!(bs.align_up(4096), Some(4096));
505        // Exact overflow boundary for 4KiB blocks:
506        assert_eq!(bs.align_up(u64::MAX - 4095), Some(u64::MAX - 4095));
507        assert_eq!(bs.align_up(u64::MAX - 4094), None);
508        assert_eq!(bs.align_up(u64::MAX), None);
509
510        assert_eq!(bs.align_up_to_blocks(0), 0);
511        assert_eq!(bs.align_up_to_blocks(1), 1);
512        assert_eq!(bs.align_up_to_blocks(4095), 1);
513        assert_eq!(bs.align_up_to_blocks(4096), 1);
514        assert_eq!(bs.align_up_to_blocks(4097), 2);
515        assert_eq!(bs.align_up_to_blocks(u64::MAX), 1 << 52);
516
517        assert_eq!(bs.align_range_outwards(1..4095), Some(0..4096));
518        assert_eq!(bs.align_range_outwards(&(0..4096)), Some(0..4096));
519        assert_eq!(bs.align_range_outwards(4096..4096), Some(4096..4096));
520        assert_eq!(bs.align_range_outwards(10..10), Some(0..4096));
521        assert_eq!(bs.align_range_outwards(0..u64::MAX), None);
522    }
523
524    #[fuchsia::test]
525    fn test_arithmetic_ops() {
526        let bs = BlockSize::SIZE_4KIB;
527        let bs_ref = &bs;
528        let val = 8192u64;
529        let val_ref = &val;
530
531        // Add
532        assert_eq!(bs + val, 12288);
533        assert_eq!(bs + val_ref, 12288);
534        assert_eq!(bs_ref + val, 12288);
535        assert_eq!(bs_ref + val_ref, 12288);
536        assert_eq!(val + bs, 12288);
537        assert_eq!(val + bs_ref, 12288);
538        assert_eq!(val_ref + bs, 12288);
539        assert_eq!(val_ref + bs_ref, 12288);
540
541        // Sub
542        assert_eq!(val - bs, 4096);
543        assert_eq!(val - bs_ref, 4096);
544        assert_eq!(val_ref - bs, 4096);
545        assert_eq!(val_ref - bs_ref, 4096);
546        assert_eq!(bs - 100u64, 3996);
547        assert_eq!(bs - &100u64, 3996);
548        assert_eq!(bs_ref - 100u64, 3996);
549        assert_eq!(bs_ref - &100u64, 3996);
550
551        // Mul
552        assert_eq!(bs * 3u64, 12288);
553        assert_eq!(bs * &3u64, 12288);
554        assert_eq!(bs_ref * 3u64, 12288);
555        assert_eq!(bs_ref * &3u64, 12288);
556        assert_eq!(3u64 * bs, 12288);
557        assert_eq!(3u64 * bs_ref, 12288);
558        assert_eq!(&3u64 * bs, 12288);
559        assert_eq!(&3u64 * bs_ref, 12288);
560
561        // Div
562        assert_eq!(val / bs, 2);
563        assert_eq!(val / bs_ref, 2);
564        assert_eq!(val_ref / bs, 2);
565        assert_eq!(val_ref / bs_ref, 2);
566
567        // Rem
568        assert_eq!(5000u64 % bs, 904);
569        assert_eq!(5000u64 % bs_ref, 904);
570        assert_eq!(&5000u64 % bs, 904);
571        assert_eq!(&5000u64 % bs_ref, 904);
572    }
573
574    #[fuchsia::test]
575    fn test_assign_ops() {
576        let bs = BlockSize::SIZE_4KIB;
577
578        let mut v = 100u64;
579        v += bs;
580        assert_eq!(v, 4196);
581        v += &bs;
582        assert_eq!(v, 8292);
583
584        v -= bs;
585        assert_eq!(v, 4196);
586        v -= &bs;
587        assert_eq!(v, 100);
588
589        let mut blocks = 3u64;
590        blocks *= bs;
591        assert_eq!(blocks, 12288);
592        let mut blocks2 = 3u64;
593        blocks2 *= &bs;
594        assert_eq!(blocks2, 12288);
595
596        let mut bytes = 12288u64;
597        bytes /= bs;
598        assert_eq!(bytes, 3);
599        let mut bytes2 = 12288u64;
600        bytes2 /= &bs;
601        assert_eq!(bytes2, 3);
602
603        let mut rem = 5000u64;
604        rem %= bs;
605        assert_eq!(rem, 904);
606        let mut rem2 = 5000u64;
607        rem2 %= &bs;
608        assert_eq!(rem2, 904);
609    }
610
611    #[fuchsia::test]
612    fn test_comparisons_and_hash() {
613        use std::collections::hash_map::DefaultHasher;
614        use std::hash::{Hash, Hasher};
615
616        #[derive(Copy, Clone, Debug, Eq, PartialEq)]
617        struct Custom4KiBSpec;
618        impl BlockSizeSpec for Custom4KiBSpec {
619            fn size(self) -> u64 {
620                4096
621            }
622            fn mask(self) -> u64 {
623                4095
624            }
625            fn shift(self) -> u32 {
626                12
627            }
628        }
629        let custom_bs = GenericBlockSize(Custom4KiBSpec);
630
631        let bs1 = BlockSize::SIZE_512B;
632        let bs2 = BlockSize::SIZE_4KIB;
633
634        assert!(bs1 < bs2);
635        assert!(bs2 > bs1);
636        assert_eq!(bs1.min(bs2), bs1);
637        assert_eq!(bs1.max(bs2), bs2);
638
639        // Cross-spec comparisons and Hash consistency
640        assert_eq!(bs2, custom_bs);
641        assert_eq!(custom_bs, bs2);
642        assert!(bs1 < custom_bs);
643        assert!(custom_bs > bs1);
644
645        fn hash_val<H: Hash>(v: &H) -> u64 {
646            let mut hasher = DefaultHasher::new();
647            v.hash(&mut hasher);
648            hasher.finish()
649        }
650        assert_eq!(hash_val(&bs2), hash_val(&custom_bs));
651        assert_ne!(hash_val(&bs1), hash_val(&bs2));
652
653        assert_eq!(bs2, 4096u64);
654        assert_eq!(bs2, &4096u64);
655        assert_eq!(&bs2, 4096u64);
656        assert_eq!(&bs2, &4096u64);
657        assert_eq!(4096u64, bs2);
658        assert_eq!(4096u64, &bs2);
659        assert_eq!(&4096u64, bs2);
660        assert_eq!(&4096u64, &bs2);
661
662        assert!(bs2 > 512u64);
663        assert!(bs2 > &512u64);
664        assert!(&bs2 > 512u64);
665        assert!(&bs2 > &512u64);
666        assert!(512u64 < bs2);
667        assert!(512u64 < &bs2);
668        assert!(&512u64 < bs2);
669        assert!(&512u64 < &bs2);
670    }
671
672    #[cfg(debug_assertions)]
673    #[fuchsia::test]
674    #[should_panic(expected = "attempt to multiply with overflow")]
675    fn test_mul_overflow() {
676        let _ = BlockSize::SIZE_4KIB * (1u64 << 60);
677    }
678
679    #[cfg(debug_assertions)]
680    #[fuchsia::test]
681    #[should_panic(expected = "attempt to multiply with overflow")]
682    fn test_mul_assign_overflow() {
683        let mut v = 1u64 << 60;
684        v *= BlockSize::SIZE_4KIB;
685    }
686
687    #[fuchsia::test]
688    fn test_formatting() {
689        let bs = BlockSize::SIZE_4KIB;
690        assert_eq!(format!("{}", bs), "4096");
691        assert_eq!(format!("{:x}", bs), "1000");
692        assert_eq!(format!("{:X}", bs), "1000");
693        assert_eq!(format!("{:b}", bs), "1000000000000");
694        assert_eq!(format!("{:o}", bs), "10000");
695    }
696
697    #[fuchsia::test]
698    fn test_block_size_one() {
699        let block_size = BlockSize::new(1).unwrap();
700
701        assert_eq!(block_size.align_up(0), Some(0));
702        assert_eq!(block_size.align_up(20), Some(20));
703        assert_eq!(block_size.align_up(u64::MAX), Some(u64::MAX));
704
705        assert_eq!(block_size.align_down(0), 0);
706        assert_eq!(block_size.align_down(20), 20);
707        assert_eq!(block_size.align_down(u64::MAX), u64::MAX);
708
709        assert_eq!(block_size * 0, 0);
710        assert_eq!(block_size * 20, 20);
711        assert_eq!(block_size * u64::MAX, u64::MAX);
712
713        assert_eq!(0 / block_size, 0);
714        assert_eq!(20 / block_size, 20);
715        assert_eq!(u64::MAX / block_size, u64::MAX);
716
717        assert_eq!(0 % block_size, 0);
718        assert_eq!(20 % block_size, 0);
719        assert_eq!(u64::MAX % block_size, 0);
720    }
721
722    #[fuchsia::test]
723    fn test_block_size_max_4gib() {
724        let bs = BlockSize::from_u64(1 << 32).unwrap();
725        assert_eq!(bs.get(), 1 << 32);
726        assert_eq!(bs.mask(), u32::MAX as u64);
727        assert_eq!(bs.shift(), 32);
728
729        assert!(bs.is_aligned(0u64));
730        assert!(bs.is_aligned(1u64 << 32));
731        assert!(!bs.is_aligned((1u64 << 32) - 1));
732
733        assert_eq!(bs.align_up(1), Some(1 << 32));
734        assert_eq!(bs.align_down((1 << 32) + 123), 1 << 32);
735        assert_eq!(bs * 3u64, 3 << 32);
736        assert_eq!((3u64 << 32) / bs, 3);
737        assert_eq!(((3u64 << 32) + 55) % bs, 55);
738    }
739}