mycelium_kernel/wasm/
wasi.rs

1use super::Host;
2
3// FIXME: These should both be `u16`, and probably generated from the wasi
4// snapshot_0 `witx` definitions.
5const __WASI_ESUCCESS: u16 = 0;
6const __WASI_EIO: u16 = 29; // Not sure I counted right. Might be some other error.
7
8const __WASI_STDOUT: u32 = 1;
9
10fn get_element_ptr(base: u32, offset: u32, scale: u32) -> Result<u32, wasmi::Trap> {
11    let byte_offset = offset
12        .checked_mul(scale)
13        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?;
14    Ok(base
15        .checked_add(byte_offset)
16        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?)
17}
18
19/// Loads the value stored at `base + (offset * scale)`.
20fn mem_read<T: wasmi::LittleEndianConvert>(
21    mem: &[u8],
22    base: u32,
23    offset: u32,
24    scale: u32,
25) -> Result<T, wasmi::Trap> {
26    let addr = get_element_ptr(base, offset, scale)?;
27    let slice = mem
28        .get(addr as usize..)
29        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?;
30    Ok(T::from_little_endian(slice).map_err(|_| wasmi::TrapKind::MemoryAccessOutOfBounds)?)
31}
32
33/// Stores a value at `base + (offset * scale)`.
34fn mem_write<T: wasmi::LittleEndianConvert>(
35    mem: &mut [u8],
36    value: T,
37    base: u32,
38    offset: u32,
39    scale: u32,
40) -> Result<(), wasmi::Trap> {
41    let addr = get_element_ptr(base, offset, scale)?;
42
43    // NOTE: while `from_little_endian` will trim the input slice to be the
44    // correct length, `into_little_endian` does not, and requires that `slice`
45    // be the correct length for our data type.
46    //
47    // Assume that the size in wasm matches our host size for the given type.
48    let addr_after = get_element_ptr(addr, core::mem::size_of::<T>() as u32, 1)?;
49    let slice = mem
50        .get_mut(addr as usize..addr_after as usize)
51        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?;
52    T::into_little_endian(value, slice);
53    Ok(())
54}
55
56/// Reference to a subslice of memory.
57fn mem_slice(mem: &[u8], addr: u32, len: u32) -> Result<&[u8], wasmi::Trap> {
58    let end = get_element_ptr(addr, len, 1)?;
59    Ok(mem
60        .get(addr as usize..end as usize)
61        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?)
62}
63
64#[allow(dead_code)] // unused
65fn mem_slice_mut(mem: &mut [u8], addr: u32, len: u32) -> Result<&mut [u8], wasmi::Trap> {
66    let end = get_element_ptr(addr, len, 1)?;
67    Ok(mem
68        .get_mut(addr as usize..end as usize)
69        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?)
70}
71
72#[tracing::instrument(skip(host))]
73pub fn fd_write(
74    host: &mut Host,
75    fd: u32,
76    iovs: u32,
77    iovs_len: u32,
78    nwritten: u32,
79) -> Result<u16, wasmi::Trap> {
80    if fd != __WASI_STDOUT {
81        return Ok(__WASI_EIO);
82    }
83
84    host.memory.with_direct_access_mut(|mem| {
85        let mut bytes_written = 0u32;
86        for idx in 0..iovs_len {
87            // read iov
88            let iov = get_element_ptr(iovs, 8, idx)?;
89            let buf = mem_read::<u32>(mem, iov, 0, 4)?;
90            let buf_len = mem_read::<u32>(mem, iov, 1, 4)?;
91
92            // TODO: Actually write out our slice.
93            let buf_slice = mem_slice(mem, buf, buf_len)?;
94            let buf_str = core::str::from_utf8(buf_slice);
95            tracing::info!(?buf_slice, ?buf_str, "fd_write");
96
97            bytes_written = bytes_written.saturating_add(buf_len);
98        }
99
100        // Write number of bytes written to memory.
101        mem_write::<u32>(mem, bytes_written, nwritten, 0, 0)?;
102        Ok(__WASI_ESUCCESS)
103    })
104}