1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use super::Host;

// FIXME: These should both be `u16`, and probably generated from the wasi
// snapshot_0 `witx` definitions.
const __WASI_ESUCCESS: u16 = 0;
const __WASI_EIO: u16 = 29; // Not sure I counted right. Might be some other error.

const __WASI_STDOUT: u32 = 1;

fn get_element_ptr(base: u32, offset: u32, scale: u32) -> Result<u32, wasmi::Trap> {
    let byte_offset = offset
        .checked_mul(scale)
        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?;
    Ok(base
        .checked_add(byte_offset)
        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?)
}

/// Loads the value stored at `base + (offset * scale)`.
fn mem_read<T: wasmi::LittleEndianConvert>(
    mem: &[u8],
    base: u32,
    offset: u32,
    scale: u32,
) -> Result<T, wasmi::Trap> {
    let addr = get_element_ptr(base, offset, scale)?;
    let slice = mem
        .get(addr as usize..)
        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?;
    Ok(T::from_little_endian(slice).map_err(|_| wasmi::TrapKind::MemoryAccessOutOfBounds)?)
}

/// Stores a value at `base + (offset * scale)`.
fn mem_write<T: wasmi::LittleEndianConvert>(
    mem: &mut [u8],
    value: T,
    base: u32,
    offset: u32,
    scale: u32,
) -> Result<(), wasmi::Trap> {
    let addr = get_element_ptr(base, offset, scale)?;

    // NOTE: while `from_little_endian` will trim the input slice to be the
    // correct length, `into_little_endian` does not, and requires that `slice`
    // be the correct length for our data type.
    //
    // Assume that the size in wasm matches our host size for the given type.
    let addr_after = get_element_ptr(addr, core::mem::size_of::<T>() as u32, 1)?;
    let slice = mem
        .get_mut(addr as usize..addr_after as usize)
        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?;
    T::into_little_endian(value, slice);
    Ok(())
}

/// Reference to a subslice of memory.
fn mem_slice(mem: &[u8], addr: u32, len: u32) -> Result<&[u8], wasmi::Trap> {
    let end = get_element_ptr(addr, len, 1)?;
    Ok(mem
        .get(addr as usize..end as usize)
        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?)
}

#[allow(dead_code)] // unused
fn mem_slice_mut(mem: &mut [u8], addr: u32, len: u32) -> Result<&mut [u8], wasmi::Trap> {
    let end = get_element_ptr(addr, len, 1)?;
    Ok(mem
        .get_mut(addr as usize..end as usize)
        .ok_or(wasmi::TrapKind::MemoryAccessOutOfBounds)?)
}

#[tracing::instrument(skip(host))]
pub fn fd_write(
    host: &mut Host,
    fd: u32,
    iovs: u32,
    iovs_len: u32,
    nwritten: u32,
) -> Result<u16, wasmi::Trap> {
    if fd != __WASI_STDOUT {
        return Ok(__WASI_EIO);
    }

    host.memory.with_direct_access_mut(|mem| {
        let mut bytes_written = 0u32;
        for idx in 0..iovs_len {
            // read iov
            let iov = get_element_ptr(iovs, 8, idx)?;
            let buf = mem_read::<u32>(mem, iov, 0, 4)?;
            let buf_len = mem_read::<u32>(mem, iov, 1, 4)?;

            // TODO: Actually write out our slice.
            let buf_slice = mem_slice(mem, buf, buf_len)?;
            let buf_str = core::str::from_utf8(buf_slice);
            tracing::info!(?buf_slice, ?buf_str, "fd_write");

            bytes_written = bytes_written.saturating_add(buf_len);
        }

        // Write number of bytes written to memory.
        mem_write::<u32>(mem, bytes_written, nwritten, 0, 0)?;
        Ok(__WASI_ESUCCESS)
    })
}