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