Skip to main content

alice_det_math/
semantics.rs

1//! Reading arithmetic identifiers back from stored data.
2//!
3//! [`SEMANTICS_ID`] names the arithmetic of this
4//! release. Data written by an earlier release carries that release's
5//! identifier; [`check_semantics`] says whether this release reproduces it.
6//!
7//! Nothing here computes a floating-point value: the functions compare
8//! 32-byte identifiers, so this module is outside the numeric surface the
9//! golden pins cover (and `tests/golden.rs` checks that it stays so).
10
11use crate::SEMANTICS_ID;
12
13/// Identifiers of earlier releases whose functions return **the same bits** as
14/// this release on every input, but which covered fewer functions.
15///
16/// Empty: this release changed the bits of `sin` / `cos` / `sin_cos` (the
17/// reduction near their zeros and the sign of zero), `round` / `round64` /
18/// `cbrt` / `smoothstep` and the other metric functions (the canonical NaN),
19/// `MetricWeights::lipschitz` / `new` (exact for tiny and huge weights) and
20/// both precisions of `powf` (`pow(1, y) = 1`), so neither 0.4.0's identifier
21/// (`d2209b30…398e`) nor the one recorded on the main branch before this
22/// change and never published (`a6fe7dc8…d6af`) is reproduced, and both read as
23/// [`SemanticsCheck::Mismatch`]. `tests/golden.rs` keeps 0.4.0's pin table
24/// and proves that it differs from this release in exactly the pins of those
25/// functions.
26///
27/// An entry is added only together with that release's pin table in
28/// `tests/golden.rs`, and the test proves two things about it: the table
29/// folds to the entry, and every pin in it is reproduced by this release.
30/// So data stamped with an entry here was computed with arithmetic this
31/// release still performs, bit for bit. An identifier whose release changed
32/// any function's output never appears here.
33///
34/// Writers always stamp [`SEMANTICS_ID`]. Readers use [`check_semantics`].
35pub const PREVIOUS_SEMANTICS_IDS: &[[u8; 32]] = &[];
36
37/// What a stored arithmetic identifier says about the data stamped with it.
38///
39/// Returned by [`check_semantics`]. The previous case is its own variant so
40/// a reader never treats "computed by an earlier release with the same bits"
41/// as silently equal to "computed by this release".
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[must_use]
44pub enum SemanticsCheck {
45    /// The identifier is [`SEMANTICS_ID`].
46    Verified,
47    /// The identifier is one of [`PREVIOUS_SEMANTICS_IDS`]: an earlier
48    /// release, same bits on every function it covered.
49    VerifiedPrevious([u8; 32]),
50    /// Unknown: the data was computed with arithmetic this release does not
51    /// promise to reproduce.
52    Mismatch,
53}
54
55impl SemanticsCheck {
56    /// Accepting: [`Verified`](Self::Verified) or
57    /// [`VerifiedPrevious`](Self::VerifiedPrevious).
58    ///
59    /// For readers of stored data (residuals, law identifiers, snapshots):
60    /// the stored values are reproduced exactly by this release.
61    #[must_use]
62    pub const fn is_reproducible(self) -> bool {
63        !matches!(self, Self::Mismatch)
64    }
65
66    /// Strict: [`Verified`](Self::Verified) only.
67    ///
68    /// For comparing identifiers as keys (a cache keyed on the identifier, a
69    /// peer that must run exactly this release, a writer checking what it is
70    /// about to stamp), where an earlier identifier is a different key even
71    /// though the bits agree.
72    #[must_use]
73    pub const fn is_current(self) -> bool {
74        matches!(self, Self::Verified)
75    }
76}
77
78/// Classify an identifier read back from stored data.
79///
80/// ```
81/// use alice_det_math::{check_semantics, SemanticsCheck, SEMANTICS_ID};
82/// assert_eq!(check_semantics(&SEMANTICS_ID), SemanticsCheck::Verified);
83/// assert_eq!(check_semantics(&[0; 32]), SemanticsCheck::Mismatch);
84/// ```
85pub const fn check_semantics(id: &[u8; 32]) -> SemanticsCheck {
86    if bytes_eq(id, &SEMANTICS_ID) {
87        return SemanticsCheck::Verified;
88    }
89    let mut i = 0;
90    while i < PREVIOUS_SEMANTICS_IDS.len() {
91        if bytes_eq(id, &PREVIOUS_SEMANTICS_IDS[i]) {
92            return SemanticsCheck::VerifiedPrevious(PREVIOUS_SEMANTICS_IDS[i]);
93        }
94        i += 1;
95    }
96    SemanticsCheck::Mismatch
97}
98
99const fn bytes_eq(a: &[u8; 32], b: &[u8; 32]) -> bool {
100    let mut i = 0;
101    while i < 32 {
102        if a[i] != b[i] {
103            return false;
104        }
105        i += 1;
106    }
107    true
108}