dpp_plugin_sdk/abi.rs
1//! Low-level linear-memory ABI: allocation, deallocation, and buffer packing
2//! across the Wasm host/guest boundary.
3
4use std::alloc::{Layout, alloc as mem_alloc, dealloc as mem_dealloc};
5
6/// Allocate `len` bytes in the module's linear memory and return the
7/// pointer as a `u32`. Returns `0` for a zero-length request.
8///
9/// Returns `0` (allocation failure) rather than panicking on an oversized
10/// request (`len ≥ isize::MAX`), and never hands out a *truncated* pointer: on
11/// a 64-bit host (e.g. a plugin's own native test build) a real heap address
12/// does not fit in `u32`, so such an allocation is freed and `0` returned. On
13/// wasm32 every address fits, so that guard is inert.
14#[must_use]
15pub fn host_alloc(len: u32) -> u32 {
16 if len == 0 {
17 return 0;
18 }
19 let Ok(layout) = Layout::from_size_align(len as usize, 1) else {
20 return 0;
21 };
22 // SAFETY: `layout` has non-zero size; a null return is handled by the host.
23 let ptr = unsafe { mem_alloc(layout) } as usize;
24 if ptr > u32::MAX as usize {
25 // SAFETY: `ptr`/`layout` are exactly the allocation just returned.
26 unsafe { mem_dealloc(ptr as *mut u8, layout) };
27 return 0;
28 }
29 ptr as u32
30}
31
32/// Free a buffer previously returned by [`host_alloc`] (or packed into a
33/// `-> u64` ABI return). No-op for null pointers, zero length, or an
34/// oversized length that could not have been allocated.
35pub fn host_dealloc(ptr: u32, len: u32) {
36 if ptr == 0 || len == 0 {
37 return;
38 }
39 let Ok(layout) = Layout::from_size_align(len as usize, 1) else {
40 return;
41 };
42 // SAFETY: `ptr`/`len` must describe a buffer from `host_alloc`.
43 unsafe { mem_dealloc(ptr as *mut u8, layout) }
44}
45
46/// View the host-written input buffer as a byte slice.
47///
48/// # Safety
49///
50/// `ptr` and `len` must describe a single allocation written by the host
51/// (via `alloc`) that lives for the duration of the returned borrow.
52#[must_use]
53pub unsafe fn read_input<'a>(ptr: u32, len: u32) -> &'a [u8] {
54 // A 32-bit ABI pointer can only address real memory on a 32-bit target
55 // (wasm32). On a 64-bit host the value is a truncated address, so never
56 // dereference it — return empty for anything but the len==0 case.
57 if len == 0 || !cfg!(target_pointer_width = "32") {
58 return &[];
59 }
60 // SAFETY: on wasm32 `ptr`/`len` describe a host-written allocation.
61 unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) }
62}
63
64/// Leak `bytes` into linear memory and return the packed
65/// `(ptr << 32) | len` the host uses to read and later free it.
66///
67/// The buffer is shrunk to an exact-size allocation (`capacity == len`,
68/// align 1) so that the host's `dealloc(ptr, len)` frees precisely the
69/// allocation it was given. Returning a `Vec` directly would leak its
70/// (possibly larger) capacity and make `dealloc` a size-mismatched free.
71#[must_use]
72pub fn write_output(bytes: Vec<u8>) -> u64 {
73 let mut boxed = bytes.into_boxed_slice();
74 let out_len = boxed.len() as u32;
75 let out_ptr = boxed.as_mut_ptr() as usize;
76 if out_ptr > u32::MAX as usize {
77 // 64-bit host: the address can't be represented in the 32-bit ABI. Drop
78 // the buffer (no leak) and return a null pointer with the exact length.
79 // On wasm32 every address fits, so this branch is inert.
80 return u64::from(out_len);
81 }
82 std::mem::forget(boxed);
83 ((out_ptr as u64) << 32) | u64::from(out_len)
84}