sim_kernel/native_abi.rs
1//! The native ABI: byte-frame and manifest transport types for dynamic libs.
2//!
3//! The kernel defines the versioned entrypoint, header, and call/response
4//! frame contracts that native libraries are loaded across; it defines no
5//! guest semantics beyond this transport.
6
7use std::ffi::{CString, c_char, c_void};
8
9/// Exported symbol name a v1 native dynamic library must expose.
10pub const NATIVE_DYLIB_ENTRYPOINT_V1: &str = "sim_native_abi_v1";
11/// Major ABI version of the v1 native library transport.
12pub const NATIVE_LIB_ABI_V1_MAJOR: u16 = 1;
13/// Minor ABI version of the v1 native library transport.
14pub const NATIVE_LIB_ABI_V1_MINOR: u16 = 0;
15
16/// Entry that creates a fresh guest instance, returning an opaque handle.
17pub type NativeAbiInstantiate = unsafe extern "C" fn() -> *mut c_void;
18/// Entry that destroys a guest instance handle.
19pub type NativeAbiDestroyInstance = unsafe extern "C" fn(instance: *mut c_void);
20/// Entry that returns the guest's encoded manifest as a call response.
21pub type NativeAbiManifest = unsafe extern "C" fn(instance: *mut c_void) -> NativeAbiCallResponse;
22/// Entry that invokes a named guest function with borrowed argument bytes.
23pub type NativeAbiCall = unsafe extern "C" fn(
24 instance: *mut c_void,
25 function: *const c_char,
26 args: NativeAbiBorrowedBytes,
27) -> NativeAbiCallResponse;
28/// Entry that frees bytes the guest previously handed back to the host.
29pub type NativeAbiDestroyBytes = unsafe extern "C" fn(bytes: NativeAbiOwnedBytes);
30/// Entry that frees an error the guest previously handed back to the host.
31pub type NativeAbiDestroyError = unsafe extern "C" fn(error: *mut NativeAbiError);
32
33/// Version header prefix shared by every native library ABI struct.
34#[repr(C)]
35#[derive(Clone, Copy)]
36pub struct NativeLibAbiHeaderV1 {
37 /// Byte size of the full ABI struct, for forward-compatible probing.
38 pub struct_size: usize,
39 /// Major ABI version the library was built against.
40 pub abi_major: u16,
41 /// Minor ABI version the library was built against.
42 pub abi_minor: u16,
43}
44
45impl NativeLibAbiHeaderV1 {
46 /// Builds a header from an explicit size and version pair.
47 pub const fn new(struct_size: usize, abi_major: u16, abi_minor: u16) -> Self {
48 Self {
49 struct_size,
50 abi_major,
51 abi_minor,
52 }
53 }
54}
55
56/// The v1 native library vtable: header plus the entrypoint function table.
57#[repr(C)]
58#[derive(Clone, Copy)]
59pub struct NativeLibAbiV1 {
60 /// Byte size of this struct, matching [`NativeLibAbiHeaderV1::struct_size`].
61 pub struct_size: usize,
62 /// Major ABI version the library was built against.
63 pub abi_major: u16,
64 /// Minor ABI version the library was built against.
65 pub abi_minor: u16,
66 /// Instance-creation entry.
67 pub instantiate: NativeAbiInstantiate,
68 /// Instance-destruction entry.
69 pub destroy_instance: NativeAbiDestroyInstance,
70 /// Manifest-fetch entry.
71 pub manifest: NativeAbiManifest,
72 /// Function-call entry.
73 pub call: NativeAbiCall,
74 /// Byte-buffer free entry.
75 pub destroy_bytes: NativeAbiDestroyBytes,
76 /// Error free entry.
77 pub destroy_error: NativeAbiDestroyError,
78}
79
80impl NativeLibAbiV1 {
81 /// Byte size of the leading [`NativeLibAbiHeaderV1`] prefix.
82 pub const HEADER_SIZE: usize = std::mem::size_of::<NativeLibAbiHeaderV1>();
83
84 /// Builds a v1 vtable, stamping in the current size and version.
85 pub const fn new(
86 instantiate: NativeAbiInstantiate,
87 destroy_instance: NativeAbiDestroyInstance,
88 manifest: NativeAbiManifest,
89 call: NativeAbiCall,
90 destroy_bytes: NativeAbiDestroyBytes,
91 destroy_error: NativeAbiDestroyError,
92 ) -> Self {
93 Self {
94 struct_size: std::mem::size_of::<Self>(),
95 abi_major: NATIVE_LIB_ABI_V1_MAJOR,
96 abi_minor: NATIVE_LIB_ABI_V1_MINOR,
97 instantiate,
98 destroy_instance,
99 manifest,
100 call,
101 destroy_bytes,
102 destroy_error,
103 }
104 }
105
106 /// Extracts the version header prefix of this vtable.
107 pub const fn header(&self) -> NativeLibAbiHeaderV1 {
108 NativeLibAbiHeaderV1::new(self.struct_size, self.abi_major, self.abi_minor)
109 }
110}
111
112/// A borrowed byte slice passed by raw pointer across the ABI boundary.
113#[repr(C)]
114#[derive(Clone, Copy)]
115pub struct NativeAbiBorrowedBytes {
116 /// Pointer to the first byte, or null when empty.
117 pub ptr: *const u8,
118 /// Number of bytes addressed by `ptr`.
119 pub len: usize,
120}
121
122impl NativeAbiBorrowedBytes {
123 /// The empty slice (null pointer, zero length).
124 pub const fn empty() -> Self {
125 Self {
126 ptr: std::ptr::null(),
127 len: 0,
128 }
129 }
130
131 /// Borrows an existing slice for the duration of a single ABI call.
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// use sim_kernel::NativeAbiBorrowedBytes;
137 ///
138 /// let payload = [1u8, 2, 3];
139 /// let borrowed = NativeAbiBorrowedBytes::borrow(&payload);
140 /// assert_eq!(borrowed.len, 3);
141 /// assert!(!borrowed.ptr.is_null());
142 ///
143 /// let empty = NativeAbiBorrowedBytes::empty();
144 /// assert_eq!(empty.len, 0);
145 /// assert!(empty.ptr.is_null());
146 /// ```
147 pub fn borrow(bytes: &[u8]) -> Self {
148 Self {
149 ptr: bytes.as_ptr(),
150 len: bytes.len(),
151 }
152 }
153}
154
155/// An owned byte buffer transferred across the ABI, freed by its allocator.
156#[repr(C)]
157#[derive(Clone, Copy)]
158pub struct NativeAbiOwnedBytes {
159 /// Pointer to the first byte, or null when empty.
160 pub ptr: *mut u8,
161 /// Number of valid bytes addressed by `ptr`.
162 pub len: usize,
163 /// Allocated capacity, needed to reconstruct the owning `Vec`.
164 pub cap: usize,
165}
166
167impl NativeAbiOwnedBytes {
168 /// The empty buffer (null pointer, zero length and capacity).
169 pub const fn empty() -> Self {
170 Self {
171 ptr: std::ptr::null_mut(),
172 len: 0,
173 cap: 0,
174 }
175 }
176}
177
178/// Result of a manifest or call entry: either owned bytes or an error.
179#[repr(C)]
180pub struct NativeAbiCallResponse {
181 /// Success payload bytes; empty when `error` is set.
182 pub bytes: NativeAbiOwnedBytes,
183 /// Failure pointer, or null on success.
184 pub error: *mut NativeAbiError,
185}
186
187impl NativeAbiCallResponse {
188 /// Builds a successful response carrying the given bytes.
189 pub const fn success(bytes: NativeAbiOwnedBytes) -> Self {
190 Self {
191 bytes,
192 error: std::ptr::null_mut(),
193 }
194 }
195
196 /// Builds a failed response carrying the given error pointer.
197 pub const fn failure(error: *mut NativeAbiError) -> Self {
198 Self {
199 bytes: NativeAbiOwnedBytes::empty(),
200 error,
201 }
202 }
203}
204
205/// An error returned across the ABI as a heap-allocated C string holder.
206#[repr(C)]
207pub struct NativeAbiError {
208 /// NUL-terminated message owned by the allocating side.
209 pub message: *mut c_char,
210}
211
212impl NativeAbiError {
213 /// Heap-allocates an error, sanitizing interior NULs out of the message.
214 pub fn boxed(message: impl Into<String>) -> *mut Self {
215 let sanitized = message.into().replace('\0', " ");
216 let message = CString::new(sanitized)
217 .expect("sanitized native ABI error strings must not contain interior NULs");
218 Box::into_raw(Box::new(Self {
219 message: message.into_raw(),
220 }))
221 }
222}
223
224/// Transfers ownership of a `Vec<u8>` into an ABI owned-bytes descriptor.
225///
226/// # Examples
227///
228/// ```
229/// use sim_kernel::native_abi_owned_bytes;
230///
231/// let owned = native_abi_owned_bytes(vec![7u8, 8, 9]);
232/// assert_eq!(owned.len, 3);
233/// assert!(owned.cap >= owned.len);
234///
235/// // The descriptor owns the allocation; reclaim it to avoid a leak.
236/// let reclaimed = unsafe { Vec::from_raw_parts(owned.ptr, owned.len, owned.cap) };
237/// assert_eq!(reclaimed, vec![7u8, 8, 9]);
238/// ```
239pub fn native_abi_owned_bytes(mut bytes: Vec<u8>) -> NativeAbiOwnedBytes {
240 let result = NativeAbiOwnedBytes {
241 ptr: bytes.as_mut_ptr(),
242 len: bytes.len(),
243 cap: bytes.capacity(),
244 };
245 std::mem::forget(bytes);
246 result
247}