larvae_worm/abi.rs
1//! The raw ABI, for a worm that wants the exports without [`frontend!`](crate::frontend)
2
3use core::cell::UnsafeCell;
4
5/// The bytes cross without a type, so the alignment does not matter
6const ALIGN: usize = 1;
7
8/*
9The result header lives in a static and is not allocated. Thus the host frees
10exactly one item, the payload, instead of two. wasm32 is single threaded, and
11this fact makes the UnsafeCell sound here.
12*/
13struct Header(UnsafeCell<[u32; 3]>);
14
15// SAFETY: wasm32-unknown-unknown is single threaded, so no other code can observe this
16unsafe impl Sync for Header {}
17
18static HEADER: Header = Header(UnsafeCell::new([0; 3]));
19
20/// Allocate `len` bytes for the host to write into
21pub fn alloc(len: u32) -> *mut u8 {
22 if len == 0 {
23 return ALIGN as *mut u8; // dangling but aligned, and no code dereferences it
24 }
25
26 let layout = core::alloc::Layout::from_size_align(len as usize, ALIGN)
27 .expect("a byte layout is always valid");
28
29 // SAFETY: len is not zero, so the layout has a size that is not zero
30 unsafe { std::alloc::alloc(layout) }
31}
32
33/// Release a buffer that [`alloc`] returned before
34///
35/// # Safety
36/// `ptr` must come from [`alloc`] with this exact `len`, and no code has freed
37/// it before.
38pub unsafe fn dealloc(ptr: *mut u8, len: u32) {
39 if len == 0 {
40 return;
41 }
42
43 let layout = core::alloc::Layout::from_size_align(len as usize, ALIGN)
44 .expect("a byte layout is always valid");
45
46 // SAFETY: the caller guarantees that ptr came from alloc with this len
47 unsafe { std::alloc::dealloc(ptr, layout) }
48}
49
50/**
51Run `handler` over two byte spans from the host and publish the result.
52
53The function returns a pointer to `[out_ptr, out_len, ok]`. When the handler
54fails, or when a span is not UTF-8, `ok` is 0 and the payload is the message.
55
56# Safety
57Both `(ptr, len)` pairs must describe spans that this module allocated and
58that the host filled in.
59*/
60pub unsafe fn dispatch<F, E>(
61 src_ptr: *const u8,
62 src_len: u32,
63 cfg_ptr: *const u8,
64 cfg_len: u32,
65 handler: F,
66) -> *const u32
67where
68 F: FnOnce(&str, &str) -> Result<String, E>,
69 E: core::fmt::Display,
70{
71 // SAFETY: the caller guarantees that both spans are live and have the correct size
72 let src = unsafe { core::slice::from_raw_parts(src_ptr, src_len as usize) };
73 let cfg = unsafe { core::slice::from_raw_parts(cfg_ptr, cfg_len as usize) };
74
75 let (text, ok) = match (core::str::from_utf8(src), core::str::from_utf8(cfg)) {
76 (Ok(src), Ok(cfg)) => match handler(src, cfg) {
77 Ok(out) => (out, 1),
78
79 Err(e) => (e.to_string(), 0),
80 },
81
82 _ => ("source and config must both be utf-8".to_owned(), 0),
83 };
84
85 publish(text, ok)
86}
87
88/// Copy `text` into a new allocation and point the header at it
89fn publish(text: String, ok: u32) -> *const u32 {
90 let bytes = text.as_bytes();
91 let len = bytes.len() as u32;
92 let out = alloc(len);
93
94 if len > 0 {
95 // SAFETY: out is a new allocation of exactly len bytes, and the source
96 // is a live String that lives longer than the copy
97 unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), out, len as usize) };
98 }
99
100 let header = HEADER.0.get();
101
102 // SAFETY: the module is single threaded, and no code holds a reference across this write
103 unsafe { *header = [out as u32, len, ok] };
104
105 header.cast()
106}