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
use holochain_core_types::error::RibosomeErrorCode;
use holochain_wasm_utils::memory_allocation::{SinglePageAllocation, SinglePageStack, U16_MAX};

use wasmi::{MemoryRef, ModuleRef};

//--------------------------------------------------------------------------------------------------
// WASM Memory Manager
//--------------------------------------------------------------------------------------------------

#[derive(Clone, Debug)]
/// Struct for managing a WASM Memory Instance as a single page memory stack
pub struct SinglePageManager {
    stack: SinglePageStack,
    wasm_memory: MemoryRef,
}

/// A Memory Manager limited to one wasm memory page that works like a stack.
/// With this Memory Manager, the WASM host (i.e. the Ribosome) and WASM module (i.e. the Zome)
/// only need to pass around an i32 to communicate any data.
/// That i32 is the last memory allocation on the stack:
/// it is split in an i16 'offset' in the upper bits and an i16 'length' in the lower bits.
/// This fits with the 64KiB sized of a memory Page.
/// Complex input arguments should be stored on the latest allocation on the stack.
/// Complex output arguments can be stored anywhere on stack.
/// Since zero sized allocations are not allowed,
/// it is possible to pass around a return and/or error code with the following convention:
/// using the i16 'offset' as return code and i16 'length' set to zero
/// to indicate its a return code.
/// Return code of 0 means success, while any other value means a failure and gives the error code.
/// In the future, to handle bigger memory needs, we could do same with an i64 instead
/// and handle multiple memory Pages.
#[allow(unknown_lints)]
#[allow(cast_lossless)]
impl SinglePageManager {
    pub fn new(wasm_instance: &ModuleRef) -> Self {
        // get wasm memory reference from module
        let wasm_memory = wasm_instance
            .export_by_name("memory")
            .expect("all modules compiled with rustc should have an export named 'memory'; qed")
            .as_memory()
            .expect("in module generated by rustc export named 'memory' should be a memory; qed")
            .clone();

        return SinglePageManager {
            stack: SinglePageStack::default(),
            wasm_memory: wasm_memory.clone(),
        };
    }

    /// Allocate on stack without writing in it
    pub fn allocate(&mut self, length: u16) -> Result<SinglePageAllocation, RibosomeErrorCode> {
        if self.stack.top() as u32 + length as u32 > U16_MAX {
            return Err(RibosomeErrorCode::OutOfMemory);
        }
        let offset = self.stack.allocate(length);
        SinglePageAllocation::new(offset, length)
    }

    /// Write data on top of stack
    pub fn write(&mut self, data: &[u8]) -> Result<SinglePageAllocation, RibosomeErrorCode> {
        let data_len = data.len();
        if data_len > <u16>::max_value() as usize {
            return Err(RibosomeErrorCode::OutOfMemory);
        }
        if data_len == 0 {
            return Err(RibosomeErrorCode::ZeroSizedAllocation);
        }
        // scope for mutable borrow of self
        let mem_buf: SinglePageAllocation;
        {
            let res = self.allocate(data_len as u16);
            if let Err(err_code) = res {
                return Err(err_code);
            }
            mem_buf = res.unwrap();
        }

        self.wasm_memory
            .set(mem_buf.offset() as u32, &data)
            .expect("memory should be writable");
        Ok(mem_buf)
    }

    /// Read data somewhere in stack
    pub fn read(&self, allocation: SinglePageAllocation) -> Vec<u8> {
        return self
            .wasm_memory
            .get(allocation.offset() as u32, allocation.length() as usize)
            .expect("Successfully retrieve the result");
    }
}