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
pub mod result;

pub use holochain_serialized_bytes::prelude::*;
pub use result::*;
pub use serde_bytes;

/// Something like `usize` for wasm.
/// Wasm has a memory limit of 4GB so offsets and lengths fit in `u32`.
///
/// When rust compiles to the `wasm32-unknown-unknown` target, `usize` will be `u32` in
/// wasm, but the host could interpret `usize` as either `u32` or `u64`. For that reason
/// we specify `u32` everywhere we need the host and the guest to have a shared agreement
/// on the size of an offset/length, or the host will not be able to directly
/// manipulate the guest memory as it needs to.
///
/// Wasmer itself uses `u32` in the `WasmPtr` abstraction etc.
/// @see https://docs.rs/wasmer-runtime/0.17.0/wasmer_runtime/struct.WasmPtr.html
pub type WasmSize = u32;

/// A `WasmSize` that points to a position in wasm linear memory that the host
/// and guest are sharing to communicate across function calls.
pub type GuestPtr = WasmSize;

/// A `WasmSize` integer that represents the size of bytes to read/write to memory.
pub type Len = WasmSize;

/// Enough bits to fit a pointer and length into so we can return it. The externs
/// defined as "C" don't support multiple return values (unlike wasm). The native
/// Rust support for wasm externs is not stable at the time of writing.
pub type GuestPtrLen = u64;

/// Given a pointer and a length, return a `u64` merged `GuestPtrLen`.
/// Works via a simple bitwise shift to move the pointer to high bits then OR
/// the length into the low bits.
pub fn merge_u64(guest_ptr: GuestPtr, len: Len) -> GuestPtrLen {
    // It should be impossible to hit these unwrap/panic conditions but it's more
    // conservative to define them than rely on an `as uX` cast.
    (u64::try_from(guest_ptr).unwrap() << 32) | u64::try_from(len).unwrap()
}

/// Given a merged `GuestPtrLen`, split out a `u32` pointer and length.
/// Performs the inverse of `merge_u64`. Takes the low `u32` bits as the length
/// then shifts the 32 high bits down and takes those as the pointer.
pub fn split_u64(u: GuestPtrLen) -> (GuestPtr, Len) {
    // It should be impossible to hit these verbose unwrap/panic conditions but it's more
    // conservative to define them than rely on an `as uX` cast that could silently truncate bits.
    (
        u32::try_from(u >> 32).unwrap(),
        u32::try_from(u & u64::try_from(u32::MAX).unwrap()).unwrap(),
    )
}

#[cfg(test)]
pub mod tests {
    use super::*;

    #[test]
    fn round_trip() {
        let guest_ptr = 9000000;
        let len = 1000;

        let (out_guest_ptr, out_len) = split_u64(merge_u64(guest_ptr, len));

        assert_eq!(guest_ptr, out_guest_ptr,);
        assert_eq!(len, out_len,);
    }
}