Skip to main content

thruput_sdk/
interface_table.rs

1//! The interface table: which interfaces this contract implements, collected
2//! by the linker from its `#[contract]` impl blocks.
3//!
4//! Every `#[contract] impl Trait for T` (and every bare `#[contract] impl T`)
5//! expands to one 8-byte [`InterfaceId`] record via
6//! [`__interface_record!`](crate::__interface_record). [`implements!`](crate::implements)
7//! answers whether a given id is among them. This is what `thruput-erc165-if`
8//! builds `supports_interface` on: the claim is read off the impls that
9//! actually linked, so there is no list to maintain and nothing to get wrong.
10//!
11//! # Layout and cost
12//!
13//! On the guest the records use the entrypoint table's shape: `#[used]`
14//! statics in the `thruput_rv32_interfaces` input section, which the
15//! runtime linker script `KEEP`s and brackets as its own output section.
16//! Whether that section reaches the deployed image is the lowerer's call,
17//! made the way it already drops the raw spec section: the reader below
18//! touches a one-word anchor in `thruput_rv32_interfaces_anchor`, an
19//! ordinary GC-able section that survives the link only if the table reader
20//! behind [`implements!`](crate::implements) was instantiated. The lowerer copies the table into VM memory only when
21//! the anchor section is present. A contract with no ERC-165 impl therefore
22//! ships an image byte-identical to one built without this module; one with
23//! an impl pays four bytes of anchor plus 8 per impl block, plus the scan.
24//!
25//! # What counts as "this contract"
26//!
27//! The unit is the contract image, not the storage struct. A crate with
28//! `impl X for A` and `impl Y for B` links both into one image whose
29//! dispatch table answers to both interfaces at one address, so its table
30//! holds both ids and `supports_interface` says yes to both — which is the
31//! truth ERC-165 asks about. On the guest the table is simply the whole
32//! image. On the host, where one test process holds many contracts, the
33//! records go through a `linkme` slice keyed by the package that expanded
34//! them, name and version both, so [`implements!`](crate::implements) from
35//! inside a contract crate sees every impl block of that crate — `A`'s and
36//! `B`'s alike, the same as the guest — while another contract crate's
37//! records, or another version of this one (a migration test may link two),
38//! stay apart. That is the scope the entrypoint guards and the mock
39//! registry already use for "one contract".
40
41use crate::InterfaceId;
42
43/// One host-side record: the package (name and version) whose `#[contract]`
44/// impl block expanded it, and the interface that block implements.
45#[cfg(not(target_arch = "riscv32"))]
46#[doc(hidden)]
47pub struct HostRecord {
48    pub package: &'static str,
49    pub version: &'static str,
50    pub id: InterfaceId,
51}
52
53#[cfg(not(target_arch = "riscv32"))]
54#[doc(hidden)]
55#[crate::__linkme::distributed_slice]
56#[linkme(crate = crate::__linkme)]
57pub static HOST_RECORDS: [HostRecord];
58
59/// True if the contract identified by `package`/`version` carries a
60/// `#[contract]` impl of the interface identified by `interface_id`.
61///
62/// [`implements!`](crate::implements) supplies the caller crate's own
63/// package name and version, which is the form contracts use. On the guest
64/// there is one contract per image, so the scope is not consulted and the
65/// table is scanned whole; on the host the records are filtered to that
66/// package. Neither touches storage.
67#[cfg(target_arch = "riscv32")]
68pub fn implements_in(
69    _package: &str,
70    _version: &str,
71    interface_id: InterfaceId,
72) -> bool {
73    rv32::contains(interface_id)
74}
75
76/// True if the contract identified by `package`/`version` carries a
77/// `#[contract]` impl of the interface identified by `interface_id`.
78///
79/// [`implements!`](crate::implements) supplies the caller crate's own
80/// package name and version, which is the form contracts use. On the guest
81/// there is one contract per image, so the scope is not consulted and the
82/// table is scanned whole; on the host the records are filtered to that
83/// package. Neither touches storage.
84#[cfg(not(target_arch = "riscv32"))]
85pub fn implements_in(
86    package: &str,
87    version: &str,
88    interface_id: InterfaceId,
89) -> bool {
90    HOST_RECORDS.iter().any(|r| {
91        r.id == interface_id && r.package == package && r.version == version
92    })
93}
94
95/// True if this contract carries a `#[contract]` impl of the interface
96/// identified by the given [`InterfaceId`].
97///
98/// Expands to [`interface_table::implements_in`](crate::interface_table::implements_in)
99/// scoped to the invoking crate's package name and version, so it must be
100/// invoked from the contract crate itself — which is where a
101/// `supports_interface` body lives:
102///
103/// ```ignore
104/// fn supports_interface(id: InterfaceId) -> bool {
105///     thruput_sdk::implements!(id)
106/// }
107/// ```
108#[macro_export]
109macro_rules! implements {
110    ($id:expr) => {
111        $crate::interface_table::implements_in(
112            env!("CARGO_PKG_NAME"),
113            env!("CARGO_PKG_VERSION"),
114            $id,
115        )
116    };
117}
118
119#[cfg(target_arch = "riscv32")]
120mod rv32 {
121    // Bracket symbols for the `thruput_rv32_interfaces` output section,
122    // defined by link/thruput-rv32.ld in the rv32-runtime crate. Declared
123    // as records so `&raw const` yields correctly-typed pointers; only their
124    // addresses are read.
125    unsafe extern "C" {
126        static __thruput_rv32_interfaces_start: super::InterfaceId;
127        static __thruput_rv32_interfaces_end: super::InterfaceId;
128    }
129
130    // The lowerer's signal that the table has a reader. Deliberately not
131    // `#[used]`: the linker keeps this static only because `contains` reads
132    // it, so its section exists in the ELF exactly when `implements` was
133    // instantiated, and the lowerer keys the table's presence in the image
134    // off that. Section name shared with the linker script and the
135    // CLI's rv32 lowerer (`INTERFACE_TABLE_ANCHOR_SECTION`).
136    #[unsafe(link_section = "thruput_rv32_interfaces_anchor")]
137    static ANCHOR: u32 = 1;
138
139    /// Scan the table for `wanted`. Out of line and cold so LLVM keeps it
140    /// a plain eight-byte-stride loop: inlined at -O3 it unrolls the scan
141    /// eightfold, ~250 bytes of code for a table of a handful of records.
142    #[cold]
143    #[inline(never)]
144    pub(super) fn contains(wanted: super::InterfaceId) -> bool {
145        // SAFETY: the linker script ALIGN(8)s the section start and brackets
146        // exactly the KEEP'd `thruput_rv32_interfaces` input section, whose
147        // sole writer is `__interface_record!` (each entry a `static [u8;
148        // 8]`, so the section is a well-formed array of stride 8). An empty
149        // table gives start == end and no reads. The bracket symbols are
150        // distinct declarations, so they are compared as addresses rather
151        // than through `offset_from`, which needs shared provenance.
152        unsafe {
153            core::ptr::read_volatile(&raw const ANCHOR);
154            let mut p = &raw const __thruput_rv32_interfaces_start;
155            let end = &raw const __thruput_rv32_interfaces_end;
156            while (p as usize) < (end as usize) {
157                if core::ptr::read_volatile(p) == wanted {
158                    return true;
159                }
160                p = p.add(1);
161            }
162            false
163        }
164    }
165}
166
167/// Register one interface-table record for the invoking crate. Emitted by
168/// `#[contract]` for every impl block; not for hand-written use.
169#[doc(hidden)]
170#[macro_export]
171macro_rules! __interface_record {
172    ($id:expr) => {
173        const _: () = {
174            #[cfg(target_arch = "riscv32")]
175            #[used]
176            #[unsafe(link_section = "thruput_rv32_interfaces")]
177            static __THRUPUT_INTERFACE_RECORD: $crate::InterfaceId = $id;
178
179            #[cfg(not(target_arch = "riscv32"))]
180            #[$crate::__linkme::distributed_slice(
181                $crate::interface_table::HOST_RECORDS
182            )]
183            #[linkme(crate = $crate::__linkme)]
184            static __THRUPUT_INTERFACE_RECORD:
185                $crate::interface_table::HostRecord =
186                $crate::interface_table::HostRecord {
187                    package: env!("CARGO_PKG_NAME"),
188                    version: env!("CARGO_PKG_VERSION"),
189                    id: $id,
190                };
191        };
192    };
193}