Skip to main content

runmat_runtime/native/
host.rs

1use std::ffi::c_void;
2
3use super::{
4    NativeCall, NativeExecuteSiteFn, NativeExit, NativeSafepoint, NativeSourceMapEntry,
5    NativeValueRef, NATIVE_ABI_VERSION,
6};
7
8/// Uniform generated-function entrypoint.
9///
10/// The caller owns `exit`; writing through an output pointer avoids platform-
11/// specific aggregate return conventions. `NativeHostStatus::OK` means `exit`
12/// was initialized and must then pass `NativeCall::validate_exit`. Panics,
13/// exceptions, and platform unwinding must never cross this boundary; semantic
14/// control transfer is represented exclusively by `NativeExit`.
15pub type NativeEntryPoint =
16    unsafe extern "C" fn(call: *mut NativeCall, exit: *mut NativeExit) -> NativeHostStatus;
17
18#[repr(transparent)]
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub struct NativeHostStatus(pub u32);
21
22impl NativeHostStatus {
23    pub const OK: Self = Self(0);
24    pub const INVALID_ARGUMENT: Self = Self(1);
25    pub const STALE_VALUE: Self = Self(2);
26    pub const HOST_FAILURE: Self = Self(3);
27
28    pub const fn is_known(self) -> bool {
29        self.0 <= Self::HOST_FAILURE.0
30    }
31}
32
33pub type NativeRetainValueFn =
34    unsafe extern "C" fn(context: *mut c_void, value: NativeValueRef) -> NativeHostStatus;
35pub type NativeReleaseValueFn =
36    unsafe extern "C" fn(context: *mut c_void, value: NativeValueRef) -> NativeHostStatus;
37pub type NativeSafepointFn = unsafe extern "C" fn(
38    context: *mut c_void,
39    safepoint: *const NativeSafepoint,
40    exit: *mut NativeExit,
41) -> NativeHostStatus;
42pub type NativeSourceLookupFn = unsafe extern "C" fn(
43    context: *mut c_void,
44    source: u32,
45    output: *mut NativeSourceMapEntry,
46) -> NativeHostStatus;
47
48/// Runtime helper table supplied to generated code.
49///
50/// This schema revision requires the exact table size and every callback.
51/// Later append-only revisions must negotiate a new ABI version rather than
52/// silently interpreting a truncated prefix.
53#[repr(C)]
54#[derive(Clone, Copy, Debug)]
55pub struct NativeHostVTable {
56    pub abi_version: u32,
57    pub struct_size: u32,
58    pub context: *mut c_void,
59    pub retain_value: Option<NativeRetainValueFn>,
60    pub release_value: Option<NativeReleaseValueFn>,
61    pub poll_safepoint: Option<NativeSafepointFn>,
62    pub source_lookup: Option<NativeSourceLookupFn>,
63    pub execute_site: Option<NativeExecuteSiteFn>,
64}
65
66impl NativeHostVTable {
67    pub fn validate(&self) -> Result<(), super::NativeAbiError> {
68        if self.abi_version != NATIVE_ABI_VERSION.encoded() {
69            return Err(super::NativeAbiError::new(
70                "native.host.abi_version",
71                "unsupported native ABI version",
72            ));
73        }
74        if self.struct_size as usize != std::mem::size_of::<Self>() {
75            return Err(super::NativeAbiError::new(
76                "native.host.struct_size",
77                "host table size does not match this ABI revision",
78            ));
79        }
80        if self.retain_value.is_none()
81            || self.release_value.is_none()
82            || self.poll_safepoint.is_none()
83            || self.source_lookup.is_none()
84            || self.execute_site.is_none()
85        {
86            return Err(super::NativeAbiError::new(
87                "native.host.callbacks",
88                "all native host callbacks are required",
89            ));
90        }
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    unsafe extern "C" fn retain(_: *mut c_void, _: NativeValueRef) -> NativeHostStatus {
100        NativeHostStatus::OK
101    }
102
103    unsafe extern "C" fn release(_: *mut c_void, _: NativeValueRef) -> NativeHostStatus {
104        NativeHostStatus::OK
105    }
106
107    unsafe extern "C" fn safepoint(
108        _: *mut c_void,
109        _: *const NativeSafepoint,
110        _: *mut NativeExit,
111    ) -> NativeHostStatus {
112        NativeHostStatus::OK
113    }
114
115    unsafe extern "C" fn source(
116        _: *mut c_void,
117        _: u32,
118        _: *mut NativeSourceMapEntry,
119    ) -> NativeHostStatus {
120        NativeHostStatus::OK
121    }
122
123    unsafe extern "C" fn site(
124        _: *mut c_void,
125        _: *mut NativeCall,
126        _: *const crate::native::NativeSiteRequest,
127        _: *mut crate::native::NativeSiteOutcome,
128        _: *mut NativeExit,
129    ) -> NativeHostStatus {
130        NativeHostStatus::OK
131    }
132
133    fn table() -> NativeHostVTable {
134        NativeHostVTable {
135            abi_version: NATIVE_ABI_VERSION.encoded(),
136            struct_size: std::mem::size_of::<NativeHostVTable>() as u32,
137            context: std::ptr::null_mut(),
138            retain_value: Some(retain),
139            release_value: Some(release),
140            poll_safepoint: Some(safepoint),
141            source_lookup: Some(source),
142            execute_site: Some(site),
143        }
144    }
145
146    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
147    #[cfg_attr(not(target_arch = "wasm32"), test)]
148    fn host_table_requires_version_size_and_every_service() {
149        assert_eq!(table().validate(), Ok(()));
150        assert!(NativeHostStatus::OK.is_known());
151        assert!(!NativeHostStatus(u32::MAX).is_known());
152
153        let mut missing = table();
154        missing.execute_site = None;
155        assert_eq!(
156            missing.validate().unwrap_err().field,
157            "native.host.callbacks"
158        );
159
160        let mut future = table();
161        future.abi_version += 1;
162        assert_eq!(
163            future.validate().unwrap_err().field,
164            "native.host.abi_version"
165        );
166    }
167}