Skip to main content

weaveffi/abi/
contract.rs

1//! Contract tables: the per-declaration fingerprints a consumer checks when
2//! it loads the library.
3//!
4//! The `#[weaveffi::module]` expansion exports, for each top-level module
5//! `m`, `const {prefix}_contract_entry* {prefix}_{m}_contract(size_t*
6//! out_len)`, returning a static table of [`ContractEntry`] values sorted by
7//! id: one per declaration (function, interface and each of its members,
8//! record, enum, callback interface, and error domain) in `m` and its
9//! submodules. The expansion computes every entry at compile time and keeps
10//! only those whose `#[cfg]` holds, using [`contract_len`] and
11//! [`contract_compact`] in constant context, so the table reflects exactly
12//! what the build exports.
13
14/// One declaration's fingerprint (`{prefix}_contract_entry` in C).
15#[repr(C)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct ContractEntry {
18    /// FNV-1a 64 of the declaration's dotted path (`kv.Store.put`).
19    pub id: u64,
20    /// FNV-1a 64 of the declaration's canonical signature.
21    pub hash: u64,
22}
23
24impl ContractEntry {
25    /// An entry with `id` and `hash`.
26    #[must_use]
27    pub const fn new(id: u64, hash: u64) -> Self {
28        Self { id, hash }
29    }
30}
31
32/// How many of `entries` are enabled (their flag is `true`).
33#[must_use]
34pub const fn contract_len<const N: usize>(entries: &[(bool, ContractEntry); N]) -> usize {
35    let mut count = 0;
36    let mut i = 0;
37    while i < N {
38        if entries[i].0 {
39            count += 1;
40        }
41        i += 1;
42    }
43    count
44}
45
46/// The enabled entries of `entries`, in order. `M` must be
47/// [`contract_len`] of `entries`.
48///
49/// # Panics
50///
51/// Panics (at compile time, in a constant) when `M` isn't the number of
52/// enabled entries.
53#[must_use]
54pub const fn contract_compact<const N: usize, const M: usize>(
55    entries: [(bool, ContractEntry); N],
56) -> [ContractEntry; M] {
57    let mut out = [ContractEntry::new(0, 0); M];
58    let mut i = 0;
59    let mut j = 0;
60    while i < N {
61        if entries[i].0 {
62            out[j] = entries[i].1;
63            j += 1;
64        }
65        i += 1;
66    }
67    assert!(j == M, "contract table length mismatch");
68    out
69}
70
71/// The body of `{prefix}_{module}_contract`: write the table's length to
72/// `out_len` and return its first entry (null for an empty table).
73///
74/// # Safety
75///
76/// `out_len` must be null or point to a writable `usize`.
77pub unsafe fn contract_table(
78    table: &'static [ContractEntry],
79    out_len: *mut usize,
80) -> *const ContractEntry {
81    if !out_len.is_null() {
82        // SAFETY: the caller guarantees `out_len` is writable when non-null.
83        unsafe { *out_len = table.len() };
84    }
85    if table.is_empty() {
86        std::ptr::null()
87    } else {
88        table.as_ptr()
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    const ALL: [(bool, ContractEntry); 3] = [
97        (true, ContractEntry::new(1, 10)),
98        (false, ContractEntry::new(2, 20)),
99        (true, ContractEntry::new(3, 30)),
100    ];
101    const LEN: usize = contract_len(&ALL);
102    static TABLE: [ContractEntry; LEN] = contract_compact(ALL);
103
104    #[test]
105    fn disabled_entries_are_compacted_out() {
106        assert_eq!(LEN, 2);
107        assert_eq!(
108            TABLE,
109            [ContractEntry::new(1, 10), ContractEntry::new(3, 30)]
110        );
111        let mut len = 0;
112        let ptr = unsafe { contract_table(&TABLE, &mut len) };
113        assert_eq!(len, 2);
114        assert_eq!(unsafe { *ptr.add(1) }.hash, 30);
115        assert!(unsafe { contract_table(&[], &mut len) }.is_null());
116        assert_eq!(len, 0);
117    }
118}