Skip to main content

dashu_int/third_party/
rkyv_v07.rs

1//! Implement rkyv traits.
2//!
3//! `UBig`/`IBig` archive as their **native word representation**: `ArchivedVec<Word>`, plus a sign
4//! flag for `IBig`. The internal `Repr`'s niche-bit layout is not directly expressible by rkyv's
5//! derive, so the archive delegates to `ArchivedVec` and round-trips through
6//! [`as_words`](UBig::as_words)/[`from_words`](UBig::from_words).
7//!
8//! Serialization and resolution are **allocation-free**: rkyv's `ArchivedVec::serialize_from_slice`
9//! writes the borrowed word slice directly into the serializer's buffer (no intermediate `Vec`),
10//! and `archived_root` yields those words in place (`&[Word]`) with no byte conversion — the
11//! fastest possible same-architecture encoding. The trade-off is that the archive layout depends on
12//! the target's `Word` width and endianness, so it is not portable across 32/64-bit or
13//! across-endianness machines. Users who need a stable, portable encoding should convert explicitly
14//! via `to_le_bytes`/`to_be_bytes` before archiving — matching rkyv's stance that performance comes
15//! before portability.
16
17use rkyv_v07 as rkyv;
18
19use crate::{IBig, UBig, Word};
20use dashu_base::Sign;
21
22impl rkyv::Archive for UBig {
23    type Archived = rkyv::vec::ArchivedVec<Word>;
24    type Resolver = rkyv::vec::VecResolver;
25
26    #[inline]
27    unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
28        rkyv::vec::ArchivedVec::<Word>::resolve_from_slice(self.as_words(), pos, resolver, out);
29    }
30}
31
32impl<S: rkyv::ser::Serializer + rkyv::ser::ScratchSpace + ?Sized> rkyv::Serialize<S> for UBig {
33    #[inline]
34    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
35        rkyv::vec::ArchivedVec::<Word>::serialize_from_slice(self.as_words(), serializer)
36    }
37}
38
39impl<D: rkyv::Fallible + ?Sized> rkyv::Deserialize<UBig, D> for rkyv::vec::ArchivedVec<Word> {
40    #[inline]
41    fn deserialize(&self, _: &mut D) -> Result<UBig, D::Error> {
42        Ok(UBig::from_words(self.as_slice()))
43    }
44}
45
46// `IBig` archives as `(is_negative, words)`, reusing the same word storage as `UBig`. The resolve
47// writes the two tuple fields with `out_field!` (the same offset computation rkyv's own tuple impl
48// uses), and the words go through `serialize_from_slice` — no `Vec` is ever built.
49impl rkyv::Archive for IBig {
50    type Archived = (bool, rkyv::vec::ArchivedVec<Word>);
51    type Resolver = ((), rkyv::vec::VecResolver);
52
53    #[inline]
54    #[allow(clippy::unit_arg)] // `bool`'s resolver is `()`; passing it is required by the Archive call
55    unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
56        let (sign, words) = self.0.as_sign_slice();
57        let (fp0, fo0) = rkyv::out_field!(out.0);
58        <bool as rkyv::Archive>::resolve(
59            &matches!(sign, Sign::Negative),
60            pos + fp0,
61            resolver.0,
62            fo0,
63        );
64        let (fp1, fo1) = rkyv::out_field!(out.1);
65        rkyv::vec::ArchivedVec::<Word>::resolve_from_slice(words, pos + fp1, resolver.1, fo1);
66    }
67}
68
69impl<S: rkyv::ser::Serializer + rkyv::ser::ScratchSpace + ?Sized> rkyv::Serialize<S> for IBig {
70    #[inline]
71    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
72        let (sign, words) = self.0.as_sign_slice();
73        <bool as rkyv::Serialize<S>>::serialize(&matches!(sign, Sign::Negative), serializer)?;
74        let words_resolver =
75            rkyv::vec::ArchivedVec::<Word>::serialize_from_slice(words, serializer)?;
76        Ok(((), words_resolver))
77    }
78}
79
80impl<D: rkyv::Fallible + ?Sized> rkyv::Deserialize<IBig, D>
81    for (bool, rkyv::vec::ArchivedVec<Word>)
82{
83    #[inline]
84    fn deserialize(&self, _: &mut D) -> Result<IBig, D::Error> {
85        // `self.1.as_slice()` reads the words straight out of the archive
86        let mag = UBig::from_words(self.1.as_slice());
87        Ok(if self.0 {
88            -IBig::from(mag)
89        } else {
90            IBig::from(mag)
91        })
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn rkyv_ubig_roundtrip() {
101        let values = [
102            UBig::from(0u8),
103            UBig::from(1u8),
104            UBig::from(123456789u64),
105            UBig::from(1u8) << 100,
106            (UBig::from(3u8) << 300) + UBig::from(5u8),
107        ];
108        for v in &values {
109            let bytes = rkyv::to_bytes::<_, 512>(v).unwrap();
110            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
111            // archived object with the root at the end.
112            let archived = unsafe { rkyv::archived_root::<UBig>(&bytes) };
113            // zero-copy access yields the native words in place
114            assert_eq!(archived.as_slice(), v.as_words(), "word view mismatch for {v}");
115            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
116            // archived object with the root at the end.
117            let v2 = unsafe { rkyv::from_bytes_unchecked::<UBig>(&bytes) }.unwrap();
118            assert_eq!(v2, *v, "UBig round-trip failed for {v}");
119        }
120    }
121
122    #[test]
123    fn rkyv_ibig_roundtrip() {
124        let values = [
125            IBig::from(0),
126            IBig::from(-1),
127            IBig::from(1),
128            IBig::from(123456789i64),
129            IBig::from(-123456789i64),
130            (IBig::from(-1)) << 200,
131        ];
132        for v in &values {
133            let bytes = rkyv::to_bytes::<_, 512>(v).unwrap();
134            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
135            // archived object with the root at the end.
136            let archived = unsafe { rkyv::archived_root::<IBig>(&bytes) };
137            // zero-copy access: the archived sign flag and words
138            assert_eq!(archived.0, v.sign() == Sign::Negative, "sign view mismatch for {v}");
139            let mag = v.clone().into_parts().1;
140            assert_eq!(archived.1.as_slice(), mag.as_words(), "word view mismatch for {v}");
141            // SAFETY: `bytes` came from `rkyv::to_bytes` on the same value, so it is a valid
142            // archived object with the root at the end.
143            let v2 = unsafe { rkyv::from_bytes_unchecked::<IBig>(&bytes) }.unwrap();
144            assert_eq!(v2, *v, "IBig round-trip failed for {v}");
145        }
146    }
147}