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}