weaveffi/abi/marshal.rs
1//! The glue every generated thunk calls: per-family parameter lifting,
2//! return lowering, and the synchronous call driver.
3//!
4//! The `#[weaveffi::module]` expansion emits one short call per parameter
5//! and per return into this module instead of inlining the marshalling, so
6//! sync thunks, async launchers, iterator steps, and callback-interface
7//! wrappers share one implementation of each family's rules:
8//!
9//! * `lift_*` functions turn a parameter's C slots into the Rust value the
10//! producer's function takes, or a [`MARSHAL_ERROR_CODE`] error naming the
11//! parameter. A thunk lifts **every** parameter before it looks at any
12//! result, so a callback context or the object tokens in a buffer are
13//! always adopted (and released again) even when another parameter fails.
14//! * `lower_*` functions (and [`CEnum::to_i32`]) turn a returned value into
15//! its C return and out slots.
16//! * [`call_sync`] runs a synchronous thunk's body under `catch_unwind` and
17//! reports its outcome through `out_err`.
18
19use std::ffi::c_void;
20use std::sync::Arc;
21
22use crate::abi::callback::{lift_callback, lift_callback_opt, CallbackInterface};
23use crate::abi::error::{error_clear, error_store, FfiError, MARSHAL_ERROR_CODE};
24use crate::abi::BufferValue;
25
26/// A C-style enum: an `i32` discriminant at the C ABI and inside value
27/// buffers. The `#[weaveffi::module]` expansion implements it for every
28/// `#[repr(i32)]` `#[weaveffi::enumeration]`.
29pub trait CEnum: Sized {
30 /// The variant whose discriminant is `value`, or `None` when no variant
31 /// has it.
32 fn from_i32(value: i32) -> Option<Self>;
33
34 /// This variant's discriminant.
35 fn to_i32(&self) -> i32;
36}
37
38/// Write a C-style enum into a value buffer (the body of its
39/// `BufferValue::write_value`).
40pub fn write_enum<E: CEnum>(value: &E, w: &mut crate::abi::BufferWriter) {
41 w.write_i32(value.to_i32());
42}
43
44/// Read a C-style enum from a value buffer (the body of its
45/// `BufferValue::read_value`).
46///
47/// # Errors
48///
49/// Returns an error when the buffer is exhausted or the discriminant isn't
50/// one of `E`'s.
51pub fn read_enum<E: CEnum>(
52 r: &mut crate::abi::BufferReader<'_>,
53) -> Result<E, crate::abi::BufferDecodeError> {
54 E::from_i32(r.read_i32()?).ok_or(crate::abi::BufferDecodeError {
55 context: "enum discriminant out of range",
56 })
57}
58
59/// The zero value a C return slot carries when a call fails: `0`, `false`,
60/// `0.0`, or null. Implemented for every C return type a thunk can have,
61/// `()` for `void`, and pairs and tuples of them (an async completion's
62/// result slots).
63pub trait Sentinel {
64 /// The zero value.
65 fn sentinel() -> Self;
66}
67
68macro_rules! zero_sentinel {
69 ($($t:ty => $v:expr),* $(,)?) => {
70 $(impl Sentinel for $t {
71 fn sentinel() -> Self {
72 $v
73 }
74 })*
75 };
76}
77
78zero_sentinel! {
79 () => (),
80 bool => false,
81 i8 => 0, i16 => 0, i32 => 0, i64 => 0,
82 u8 => 0, u16 => 0, u32 => 0, u64 => 0, usize => 0,
83 f32 => 0.0, f64 => 0.0,
84}
85
86impl<T> Sentinel for *const T {
87 fn sentinel() -> Self {
88 std::ptr::null()
89 }
90}
91
92impl<T> Sentinel for *mut T {
93 fn sentinel() -> Self {
94 std::ptr::null_mut()
95 }
96}
97
98impl<A: Sentinel> Sentinel for (A,) {
99 fn sentinel() -> Self {
100 (A::sentinel(),)
101 }
102}
103
104impl<A: Sentinel, B: Sentinel> Sentinel for (A, B) {
105 fn sentinel() -> Self {
106 (A::sentinel(), B::sentinel())
107 }
108}
109
110/// Run a synchronous thunk's body and report its outcome: `Ok` clears
111/// `out_err` and returns the value; `Err` stores the error and returns the
112/// [`Sentinel`]; a panic is caught and reported with the panic code, so it
113/// never unwinds into C.
114///
115/// # Safety
116///
117/// `out_err` must be null or point to a valid, writable error slot whose
118/// pointers are null or owned by this runtime.
119pub unsafe fn call_sync<R: Sentinel>(
120 out_err: *mut FfiError,
121 body: impl FnOnce() -> Result<R, FfiError>,
122) -> R {
123 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(body))
124 .unwrap_or_else(|payload| Err(FfiError::from_panic(&*payload)));
125 match outcome {
126 Ok(value) => {
127 // SAFETY: forwarded from the caller.
128 unsafe { error_clear(out_err) };
129 value
130 }
131 Err(e) => {
132 // SAFETY: forwarded from the caller.
133 unsafe { error_store(out_err, e) };
134 R::sentinel()
135 }
136 }
137}
138
139fn invalid(name: &str) -> FfiError {
140 FfiError::new(MARSHAL_ERROR_CODE, &format!("{name} is null or invalid"))
141}
142
143/// Lift a C-style enum parameter.
144///
145/// # Errors
146///
147/// Returns a marshalling error when `value` isn't one of `E`'s
148/// discriminants.
149pub fn lift_enum<E: CEnum>(value: i32, name: &str) -> Result<E, FfiError> {
150 E::from_i32(value).ok_or_else(|| {
151 FfiError::new(
152 MARSHAL_ERROR_CODE,
153 &format!("{name}: {value} is not a valid enum value"),
154 )
155 })
156}
157
158/// Borrow a string parameter's UTF-8 `(ptr, len)` run as a `&str`.
159///
160/// # Errors
161///
162/// Returns a marshalling error for null with a non-zero length or bytes that
163/// aren't UTF-8.
164///
165/// # Safety
166///
167/// Same contract as [`lift_byte_slice`](crate::abi::lift_byte_slice).
168pub unsafe fn lift_str_param<'a>(
169 ptr: *const u8,
170 len: usize,
171 name: &str,
172) -> Result<&'a str, FfiError> {
173 // SAFETY: forwarded from the caller.
174 unsafe { crate::abi::lift_str(ptr, len) }.ok_or_else(|| invalid(name))
175}
176
177/// Copy a string parameter into an owned `String`.
178///
179/// # Errors
180///
181/// Same as [`lift_str_param`].
182///
183/// # Safety
184///
185/// Same contract as [`lift_str_param`].
186pub unsafe fn lift_string_param(
187 ptr: *const u8,
188 len: usize,
189 name: &str,
190) -> Result<String, FfiError> {
191 // SAFETY: forwarded from the caller.
192 unsafe { lift_str_param(ptr, len, name) }.map(str::to_owned)
193}
194
195/// Borrow a bytes parameter's `(ptr, len)` run as a slice.
196///
197/// # Errors
198///
199/// Returns a marshalling error for null with a non-zero length.
200///
201/// # Safety
202///
203/// Same contract as [`lift_byte_slice`](crate::abi::lift_byte_slice).
204pub unsafe fn lift_slice_param<'a>(
205 ptr: *const u8,
206 len: usize,
207 name: &str,
208) -> Result<&'a [u8], FfiError> {
209 // SAFETY: forwarded from the caller.
210 unsafe { crate::abi::lift_byte_slice(ptr, len) }.ok_or_else(|| invalid(name))
211}
212
213/// Copy a bytes parameter into an owned `Vec<u8>`.
214///
215/// # Errors
216///
217/// Same as [`lift_slice_param`].
218///
219/// # Safety
220///
221/// Same contract as [`lift_slice_param`].
222pub unsafe fn lift_bytes_param(
223 ptr: *const u8,
224 len: usize,
225 name: &str,
226) -> Result<Vec<u8>, FfiError> {
227 // SAFETY: forwarded from the caller.
228 unsafe { lift_slice_param(ptr, len, name) }.map(<[u8]>::to_vec)
229}
230
231/// Decode a value-buffer parameter (a record, rich enum, optional, list, or
232/// map), adopting any object tokens it carries.
233///
234/// # Errors
235///
236/// Returns a marshalling error for null with a non-zero length or a
237/// malformed buffer.
238///
239/// # Safety
240///
241/// Same contract as [`lift_slice_param`], plus that of
242/// [`decode_value`](crate::abi::decode_value) for the buffer's object tokens.
243pub unsafe fn lift_buffer_param<T: BufferValue>(
244 ptr: *const u8,
245 len: usize,
246 name: &str,
247) -> Result<T, FfiError> {
248 // SAFETY: forwarded from the caller.
249 let bytes = unsafe { lift_slice_param(ptr, len, name) }?;
250 // SAFETY: forwarded from the caller; the thunk decodes each buffer once.
251 unsafe { crate::abi::decode_value(bytes) }
252 .map_err(|e| FfiError::new(MARSHAL_ERROR_CODE, &format!("{name}: {e}")))
253}
254
255/// Borrow an object parameter for the call.
256///
257/// # Errors
258///
259/// Returns a marshalling error for null.
260///
261/// # Safety
262///
263/// Same contract as [`object_ref`](crate::abi::object_ref).
264pub unsafe fn lift_object_param<'a, T>(ptr: *const T, name: &str) -> Result<&'a T, FfiError> {
265 // SAFETY: forwarded from the caller.
266 unsafe { crate::abi::object_ref(ptr) }.ok_or_else(|| invalid(name))
267}
268
269/// Take a new strong reference to an object parameter, so the producer can
270/// retain it.
271///
272/// # Errors
273///
274/// Returns a marshalling error for null.
275///
276/// # Safety
277///
278/// Same contract as [`object_arc`](crate::abi::object_arc).
279pub unsafe fn lift_object_arc_param<T>(ptr: *const T, name: &str) -> Result<Arc<T>, FfiError> {
280 // SAFETY: forwarded from the caller.
281 unsafe { crate::abi::object_arc(ptr) }.ok_or_else(|| invalid(name))
282}
283
284/// Borrow an optional object parameter (`I?`); null is `None`.
285///
286/// # Errors
287///
288/// Never fails; the `Result` keeps every lift the same shape.
289///
290/// # Safety
291///
292/// Same contract as [`object_ref`](crate::abi::object_ref).
293pub unsafe fn lift_object_opt_param<'a, T>(ptr: *const T) -> Result<Option<&'a T>, FfiError> {
294 // SAFETY: forwarded from the caller.
295 Ok(unsafe { crate::abi::object_ref(ptr) })
296}
297
298/// Retain an optional object parameter (`I?`); null is `None`.
299///
300/// # Errors
301///
302/// Never fails; the `Result` keeps every lift the same shape.
303///
304/// # Safety
305///
306/// Same contract as [`object_arc`](crate::abi::object_arc).
307pub unsafe fn lift_object_arc_opt_param<T>(ptr: *const T) -> Result<Option<Arc<T>>, FfiError> {
308 // SAFETY: forwarded from the caller.
309 Ok(unsafe { crate::abi::object_arc(ptr) })
310}
311
312/// Adopt a callback-interface parameter's `(ctx, vtable)` pair.
313///
314/// # Errors
315///
316/// Returns a marshalling error for a null or too-small vtable.
317///
318/// # Safety
319///
320/// Same contract as
321/// [`ForeignCallback::from_raw`](crate::abi::ForeignCallback::from_raw).
322pub unsafe fn lift_callback_param<C: CallbackInterface + ?Sized>(
323 ctx: *mut c_void,
324 vtable: *const C::Vtable,
325 name: &str,
326) -> Result<Arc<C>, FfiError> {
327 // SAFETY: forwarded from the caller.
328 unsafe { lift_callback::<C>(ctx, vtable, name) }
329}
330
331/// Adopt an optional callback-interface parameter (`Cb?`); a null vtable is
332/// `None`.
333///
334/// # Errors
335///
336/// Returns a marshalling error for a too-small vtable.
337///
338/// # Safety
339///
340/// Same contract as [`lift_callback_param`].
341pub unsafe fn lift_callback_opt_param<C: CallbackInterface + ?Sized>(
342 ctx: *mut c_void,
343 vtable: *const C::Vtable,
344 name: &str,
345) -> Result<Option<Arc<C>>, FfiError> {
346 // SAFETY: forwarded from the caller.
347 unsafe { lift_callback_opt::<C>(ctx, vtable, name) }
348}
349
350/// Lift a method's receiver (`self`), borrowed for the call.
351///
352/// # Errors
353///
354/// Returns a marshalling error for null.
355///
356/// # Safety
357///
358/// Same contract as [`lift_object_param`].
359pub unsafe fn lift_self<'a, T>(ptr: *const T) -> Result<&'a T, FfiError> {
360 // SAFETY: forwarded from the caller.
361 unsafe { lift_object_param(ptr, "self") }
362}
363
364/// Lift a method's receiver as a retained reference (`self: Arc<Self>`, and
365/// every async method, whose future outlives the launcher).
366///
367/// # Errors
368///
369/// Returns a marshalling error for null.
370///
371/// # Safety
372///
373/// Same contract as [`lift_object_arc_param`].
374pub unsafe fn lift_self_arc<T>(ptr: *const T) -> Result<Arc<T>, FfiError> {
375 // SAFETY: forwarded from the caller.
376 unsafe { lift_object_arc_param(ptr, "self") }
377}
378
379/// Lower a returned string as a producer-allocated run plus `*out_len`.
380///
381/// # Safety
382///
383/// `out_len` must be null or point to a writable `usize`.
384pub unsafe fn lower_string_ret(value: impl Into<String>, out_len: *mut usize) -> *const u8 {
385 // SAFETY: forwarded from the caller.
386 unsafe { crate::abi::lower_string(value.into(), out_len) }
387}
388
389/// Lower returned bytes as a producer-allocated run plus `*out_len`.
390///
391/// # Safety
392///
393/// Same contract as [`lower_string_ret`].
394pub unsafe fn lower_bytes_ret(value: impl Into<Vec<u8>>, out_len: *mut usize) -> *const u8 {
395 // SAFETY: forwarded from the caller.
396 unsafe { crate::abi::lower_bytes(value.into(), out_len) }
397}
398
399/// Encode a returned value buffer as a producer-allocated run plus
400/// `*out_len`.
401///
402/// # Safety
403///
404/// Same contract as [`lower_string_ret`].
405pub unsafe fn lower_buffer_ret<T: BufferValue>(value: &T, out_len: *mut usize) -> *const u8 {
406 // SAFETY: forwarded from the caller.
407 unsafe { crate::abi::lower_bytes(crate::abi::encode_value(value), out_len) }
408}
409
410/// A string as an async completion's owned `(result_ptr, result_len)` run.
411#[must_use]
412pub fn string_run(value: impl Into<String>) -> (*const u8, usize) {
413 crate::abi::bytes_into_raw(value.into().into_bytes())
414}
415
416/// Bytes as an async completion's owned `(result_ptr, result_len)` run.
417#[must_use]
418pub fn bytes_run(value: impl Into<Vec<u8>>) -> (*const u8, usize) {
419 crate::abi::bytes_into_raw(value.into())
420}
421
422/// A value buffer as an async completion's owned `(result_ptr, result_len)`
423/// run.
424#[must_use]
425pub fn buffer_run<T: BufferValue>(value: &T) -> (*const u8, usize) {
426 crate::abi::bytes_into_raw(crate::abi::encode_value(value))
427}
428
429/// Borrow a string-like argument as the `(ptr, len)` slots of a callback
430/// method call.
431pub fn str_slots<S: AsRef<str> + ?Sized>(value: &S) -> (*const u8, usize) {
432 let s = value.as_ref();
433 (s.as_ptr(), s.len())
434}
435
436/// Borrow a bytes-like argument as the `(ptr, len)` slots of a callback
437/// method call.
438pub fn byte_slots<B: AsRef<[u8]> + ?Sized>(value: &B) -> (*const u8, usize) {
439 let b = value.as_ref();
440 (b.as_ptr(), b.len())
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[derive(Debug, PartialEq)]
448 enum Color {
449 Red,
450 Blue,
451 }
452
453 impl CEnum for Color {
454 fn from_i32(value: i32) -> Option<Self> {
455 match value {
456 0 => Some(Self::Red),
457 2 => Some(Self::Blue),
458 _ => None,
459 }
460 }
461 fn to_i32(&self) -> i32 {
462 match self {
463 Self::Red => 0,
464 Self::Blue => 2,
465 }
466 }
467 }
468
469 #[test]
470 fn enums_lift_and_round_trip_through_buffers() {
471 assert_eq!(lift_enum::<Color>(2, "c").unwrap(), Color::Blue);
472 let err = lift_enum::<Color>(1, "c").unwrap_err();
473 assert_eq!(err.code, MARSHAL_ERROR_CODE);
474 let mut w = crate::abi::BufferWriter::new();
475 write_enum(&Color::Blue, &mut w);
476 let bytes = w.finish();
477 let mut r = crate::abi::BufferReader::new(&bytes);
478 assert_eq!(read_enum::<Color>(&mut r).unwrap(), Color::Blue);
479 }
480
481 #[test]
482 fn call_sync_reports_errors_and_panics() {
483 let mut err = FfiError::new(1, "stale");
484 assert_eq!(unsafe { call_sync(&mut err, || Ok(5i32)) }, 5);
485 assert_eq!(err.code, 0);
486 let p: *const u8 =
487 unsafe { call_sync(&mut err, || Err(FfiError::new(MARSHAL_ERROR_CODE, "bad"))) };
488 assert!(p.is_null());
489 assert_eq!(err.code, MARSHAL_ERROR_CODE);
490 let v: f64 = unsafe {
491 call_sync(&mut err, || {
492 if true {
493 panic!("boom");
494 }
495 Ok(1.0)
496 })
497 };
498 assert_eq!(v, 0.0);
499 assert_eq!(err.code, crate::abi::PANIC_ERROR_CODE);
500 }
501
502 #[test]
503 fn param_lifts_name_the_parameter() {
504 let err = unsafe { lift_str_param(std::ptr::null(), 3, "title") }.unwrap_err();
505 assert_eq!(
506 unsafe { err.message_str() },
507 Some("title is null or invalid")
508 );
509 let bad = [0xffu8];
510 assert!(unsafe { lift_string_param(bad.as_ptr(), 1, "s") }.is_err());
511 let err = unsafe { lift_buffer_param::<Vec<i32>>(bad.as_ptr(), 1, "xs") }.unwrap_err();
512 assert!(unsafe { err.message_str() }
513 .unwrap()
514 .starts_with("xs: malformed"));
515 assert!(unsafe { lift_object_param::<u8>(std::ptr::null(), "o") }.is_err());
516 assert!(unsafe { lift_object_opt_param::<u8>(std::ptr::null()) }
517 .unwrap()
518 .is_none());
519 }
520}