Skip to main content

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}