locutus_stdlib/
buf.rs

1//! Memory buffers to interact with the WASM contracts.
2
3use crate::prelude::WasmLinearMem;
4
5#[doc(hidden)]
6#[derive(Clone, Copy, Debug)]
7#[repr(C)]
8pub struct BufferBuilder {
9    start: i64,
10    capacity: u32,
11    last_read: i64,
12    last_write: i64,
13}
14
15impl BufferBuilder {
16    /// Return the buffer capacity.
17    pub fn capacity(&self) -> usize {
18        self.capacity as _
19    }
20
21    pub fn written(&self, mem: Option<&WasmLinearMem>) -> usize {
22        unsafe {
23            let ptr = if let Some(mem) = mem {
24                compute_ptr(self.last_write as *mut u32, mem)
25            } else {
26                self.last_write as *mut u32
27            };
28            *ptr as usize
29        }
30    }
31
32    /// Returns the first byte of buffer.
33    pub fn start(&self) -> *mut u8 {
34        self.start as _
35    }
36
37    /// # Safety
38    /// Requires that there are no living references to the current
39    /// underlying buffer or will trigger UB
40    pub unsafe fn update_buffer(&mut self, data: Vec<u8>) {
41        let read_ptr = Box::leak(Box::from_raw(self.last_read as *mut u32));
42        let write_ptr = Box::leak(Box::from_raw(self.last_write as *mut u32));
43
44        // drop previous buffer
45        let prev = Vec::from_raw_parts(self.start as *mut u8, *write_ptr as usize, self.capacity());
46        std::mem::drop(prev);
47
48        // write the new buffer information
49        let new_ptr = data.as_ptr();
50        self.start = new_ptr as i64;
51        self.capacity = data.capacity() as _;
52        *read_ptr = 0;
53        *write_ptr = data.len().saturating_sub(1) as _; // []
54        std::mem::forget(data);
55    }
56
57    /// Returns a wrapped raw pointer to the buffer builder.
58    pub fn to_ptr(self) -> *mut BufferBuilder {
59        Box::into_raw(Box::new(self))
60    }
61}
62
63/// Type of buffer errors.
64#[derive(thiserror::Error, Debug)]
65pub enum Error {
66    /// Insufficient memory while trying to write to the buffer.
67    #[error("insufficient memory, needed {req} bytes but had {free} bytes")]
68    InsufficientMemory {
69        /// Required memory available
70        req: usize,
71        /// Available memory.
72        free: usize,
73    },
74}
75
76/// A live mutable buffer in the WASM linear memory.
77#[derive(Debug)]
78pub struct BufferMut<'instance> {
79    buffer: &'instance mut [u8],
80    /// stores the last read in the buffer
81    read_ptr: &'instance u32,
82    /// stores the last write in the buffer
83    write_ptr: &'instance mut u32,
84    /// A pointer to the underlying builder
85    builder_ptr: *mut BufferBuilder,
86    /// Linear memory pointer and size in bytes
87    mem: WasmLinearMem,
88}
89
90impl<'instance> BufferMut<'instance> {
91    /// Tries to write data into the buffer, after any unread bytes.
92    ///
93    /// Will return an error if there is insufficient space.
94    pub fn write<T>(&mut self, obj: T) -> Result<(), Error>
95    where
96        T: AsRef<[u8]>,
97    {
98        let obj = obj.as_ref();
99        if obj.len() > self.buffer.len() {
100            return Err(Error::InsufficientMemory {
101                req: obj.len(),
102                free: self.buffer.len(),
103            });
104        }
105        let mut last_write = (*self.write_ptr) as usize;
106        let free_right = self.buffer.len() - last_write;
107        if obj.len() <= free_right {
108            let copy_to = &mut self.buffer[last_write..last_write + obj.len()];
109            copy_to.copy_from_slice(obj);
110            last_write += obj.len();
111            *self.write_ptr = last_write as u32;
112            Ok(())
113        } else {
114            Err(Error::InsufficientMemory {
115                req: obj.len(),
116                free: free_right,
117            })
118        }
119    }
120
121    /// Read bytes specified number of bytes from the buffer.
122    ///
123    /// Always reads from the beginning.
124    pub fn read_bytes(&self, len: usize) -> &[u8] {
125        let next_offset = *self.read_ptr as usize;
126        // don't update the read ptr
127        &self.buffer[next_offset..next_offset + len]
128    }
129
130    /// Give ownership of the buffer back to the guest.
131    pub fn shared(self) -> Buffer<'instance> {
132        let BufferMut {
133            builder_ptr, mem, ..
134        } = self;
135        let BuilderInfo {
136            buffer,
137            read_ptr,
138            write_ptr,
139            ..
140        } = from_raw_builder(builder_ptr, mem);
141        Buffer {
142            buffer,
143            read_ptr,
144            write_ptr,
145            builder_ptr,
146            mem,
147        }
148    }
149
150    /// Return the buffer capacity.
151    pub fn capacity(&self) -> usize {
152        unsafe {
153            let p = &*compute_ptr(self.builder_ptr, &self.mem);
154            p.capacity as _
155        }
156    }
157
158    #[doc(hidden)]
159    /// # Safety
160    /// The pointer passed come from a previous call to `initiate_buffer` exported function from the contract.
161    pub unsafe fn from_ptr(
162        builder_ptr: *mut BufferBuilder,
163        linear_mem_space: WasmLinearMem,
164    ) -> Self {
165        let BuilderInfo {
166            buffer,
167            read_ptr,
168            write_ptr,
169        } = from_raw_builder(builder_ptr, linear_mem_space);
170        BufferMut {
171            buffer,
172            read_ptr,
173            write_ptr,
174            builder_ptr,
175            mem: linear_mem_space,
176        }
177    }
178
179    /// A pointer to the linear memory address.
180    pub fn ptr(&self) -> *mut BufferBuilder {
181        self.builder_ptr
182    }
183}
184
185#[inline(always)]
186pub(crate) fn compute_ptr<T>(ptr: *mut T, linear_mem_space: &WasmLinearMem) -> *mut T {
187    let mem_start_ptr = linear_mem_space.start_ptr;
188    (mem_start_ptr as isize + ptr as isize) as _
189}
190
191struct BuilderInfo<'instance> {
192    buffer: &'instance mut [u8],
193    read_ptr: &'instance mut u32,
194    write_ptr: &'instance mut u32,
195}
196
197fn from_raw_builder<'a>(builder_ptr: *mut BufferBuilder, mem: WasmLinearMem) -> BuilderInfo<'a> {
198    unsafe {
199        #[cfg(feature = "trace")]
200        {
201            let contract_mem = std::slice::from_raw_parts(mem.start_ptr, mem.size as usize);
202            tracing::trace!(
203                "*mut BufferBuilder <- offset: {}; in mem: {:?}",
204                builder_ptr as usize,
205                &contract_mem[builder_ptr as usize
206                    ..builder_ptr as usize + std::mem::size_of::<BufferBuilder>()]
207            );
208            // use std::{fs::File, io::Write};
209            // let mut f = File::create(std::env::temp_dir().join("dump.mem")).unwrap();
210            // f.write_all(contract_mem).unwrap();
211        }
212
213        let builder_ptr = compute_ptr(builder_ptr, &mem);
214        let buf_builder: &'static mut BufferBuilder = Box::leak(Box::from_raw(builder_ptr));
215        #[cfg(feature = "trace")]
216        {
217            tracing::trace!("buf builder from FFI: {buf_builder:?}");
218        }
219
220        let read_ptr = Box::leak(Box::from_raw(compute_ptr(
221            buf_builder.last_read as *mut u32,
222            &mem,
223        )));
224        let write_ptr = Box::leak(Box::from_raw(compute_ptr(
225            buf_builder.last_write as *mut u32,
226            &mem,
227        )));
228        let buffer_ptr = compute_ptr(buf_builder.start as *mut u8, &mem);
229        let buffer =
230            &mut *std::ptr::slice_from_raw_parts_mut(buffer_ptr, buf_builder.capacity as usize);
231        BuilderInfo {
232            buffer,
233            read_ptr,
234            write_ptr,
235        }
236    }
237}
238
239#[derive(Debug)]
240/// A live buffer in the WASM linear memory.
241pub struct Buffer<'instance> {
242    buffer: &'instance mut [u8],
243    /// stores the last read in the buffer
244    read_ptr: &'instance mut u32,
245    write_ptr: &'instance u32,
246    builder_ptr: *mut BufferBuilder,
247    mem: WasmLinearMem,
248}
249
250impl<'instance> Buffer<'instance> {
251    /// # Safety
252    /// In order for this to be a safe T must be properly aligned and cannot re-use the buffer
253    /// trying to read the same memory region again (that would create more than one copy to
254    /// the same underlying data and break aliasing rules).
255    pub unsafe fn read<T: Sized>(&mut self) -> T {
256        let next_offset = *self.read_ptr as usize;
257        let bytes = &self.buffer[next_offset..next_offset + std::mem::size_of::<T>()];
258        let t = std::ptr::read(bytes.as_ptr() as *const T);
259        *self.read_ptr += std::mem::size_of::<T>() as u32;
260        t
261    }
262
263    /// Read the specified number of bytes from the buffer.
264    pub fn read_bytes(&mut self, len: usize) -> &[u8] {
265        let next_offset = *self.read_ptr as usize;
266        *self.read_ptr += len as u32;
267        &self.buffer[next_offset..next_offset + len]
268    }
269
270    /// Reads all the bytes from the buffer.
271    pub fn read_all(&mut self) -> &[u8] {
272        let next_offset = *self.read_ptr as usize;
273        *self.read_ptr += self.buffer.len() as u32;
274        &self.buffer[next_offset..=*self.write_ptr as usize]
275    }
276
277    /// Give ownership of the buffer back to the guest.
278    ///
279    /// # Safety
280    /// Must guarantee that there are not underlying alive shared references.
281    #[doc(hidden)]
282    pub unsafe fn exclusive(self) -> BufferMut<'instance> {
283        let Buffer {
284            builder_ptr, mem, ..
285        } = self;
286        let BuilderInfo {
287            buffer,
288            read_ptr,
289            write_ptr,
290        } = from_raw_builder(builder_ptr, mem);
291        BufferMut {
292            buffer,
293            read_ptr,
294            write_ptr,
295            builder_ptr,
296            mem,
297        }
298    }
299}
300
301/// Returns the pointer to a new BufferBuilder.
302///
303/// This buffer leaks it's own memory and will only be freed by the runtime when a contract instance is dropped.
304#[doc(hidden)]
305#[no_mangle]
306pub fn initiate_buffer(capacity: u32) -> i64 {
307    let buf: Vec<u8> = Vec::with_capacity(capacity as usize);
308    let start = buf.as_ptr() as i64;
309
310    let last_read = Box::into_raw(Box::new(0u32));
311    let last_write = Box::into_raw(Box::new(0u32));
312    let buffer = Box::into_raw(Box::new(BufferBuilder {
313        start,
314        capacity,
315        last_read: last_read as _,
316        last_write: last_write as _,
317    }));
318    #[cfg(feature = "trace")]
319    {
320        tracing::trace!(
321            "new buffer ptr: {:p} -> {} as i64 w/ cap: {capacity}",
322            buf.as_ptr(),
323            start
324        );
325        tracing::trace!(
326            "last read ptr: {last_read:p} -> {} as i64",
327            last_read as i64
328        );
329        tracing::trace!(
330            "last write ptr: {last_write:p} -> {} as i64",
331            last_write as i64
332        );
333        tracing::trace!("buffer ptr: {buffer:p} -> {} as i64", buffer as i64);
334    }
335    std::mem::forget(buf);
336    buffer as i64
337}
338
339#[cfg(all(test, target_family = "unix"))]
340mod test {
341    use super::*;
342    use wasmer::{
343        imports, namespace, wat2wasm, AsStoreMut, Cranelift, Function, Instance, Module, Store,
344        TypedFunction,
345    };
346    use wasmer_wasi::WasiState;
347
348    const TEST_MODULE: &str = r#"
349        (module
350            (func $initiate_buffer (import "locutus" "initiate_buffer") (param i32) (result i64))
351            (memory $locutus_mem (export "memory") 20)
352            (export "initiate_buffer" (func $initiate_buffer))
353        )"#;
354
355    fn build_test_mod() -> Result<(Store, Instance), Box<dyn std::error::Error>> {
356        let wasm_bytes = wat2wasm(TEST_MODULE.as_bytes())?;
357        let mut store = Store::new(Cranelift::new());
358        let module = Module::new(&store, wasm_bytes)?;
359
360        let init_buf_fn = Function::new_typed(&mut store, initiate_buffer);
361        let imports = imports! {
362            "locutus" => { "initiate_buffer" => init_buf_fn }
363        };
364        let instance = Instance::new(&mut store, &module, &imports).unwrap();
365        Ok((store, instance))
366    }
367
368    // FIXME
369    #[allow(dead_code)]
370    fn build_test_mod_with_wasi() -> Result<(Store, Instance), Box<dyn std::error::Error>> {
371        let wasm_bytes = wat2wasm(TEST_MODULE.as_bytes())?;
372        let mut store = Store::new(Cranelift::new());
373        let module = Module::new(&store, wasm_bytes)?;
374
375        let init_buf_fn = Function::new_typed(&mut store, initiate_buffer);
376        let funcs = namespace!("initiate_buffer" => init_buf_fn );
377        let wasi_env = WasiState::new("locutus").finalize(&mut store)?;
378        let mut imports = wasi_env.import_object(&mut store, &module)?;
379        imports.register_namespace("locutus", funcs);
380
381        let instance = Instance::new(&mut store, &module, &imports).unwrap();
382        Ok((store, instance))
383    }
384
385    fn init_buf(store: &mut impl AsStoreMut, instance: &Instance, size: u32) -> *mut BufferBuilder {
386        let initiate_buffer: TypedFunction<u32, i64> = instance
387            .exports
388            .get_typed_function(&store, "initiate_buffer")
389            .unwrap();
390        initiate_buffer.call(store, size).unwrap() as *mut BufferBuilder
391    }
392
393    #[test]
394    #[ignore]
395    fn read_and_write() -> Result<(), Box<dyn std::error::Error>> {
396        let (mut store, instance) = build_test_mod()?;
397        let mem = instance.exports.get_memory("memory")?.view(&store);
398        let linear_mem = WasmLinearMem {
399            start_ptr: mem.data_ptr() as *const _,
400            size: mem.data_size(),
401        };
402
403        let mut writer =
404            unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
405        writer.write([1u8, 2])?;
406        let mut reader = writer.shared();
407        let r: [u8; 2] = unsafe { reader.read() };
408        assert_eq!(r, [1, 2]);
409
410        let mut writer = unsafe { reader.exclusive() };
411        writer.write([3u8, 4])?;
412        let mut reader = writer.shared();
413        let r: [u8; 2] = unsafe { reader.read() };
414        assert_eq!(r, [3, 4]);
415        Ok(())
416    }
417
418    #[test]
419    #[ignore]
420    fn read_and_write_bytes() -> Result<(), Box<dyn std::error::Error>> {
421        let (mut store, instance) = build_test_mod()?;
422        let mem = instance.exports.get_memory("memory")?.view(&store);
423        let linear_mem = WasmLinearMem {
424            start_ptr: mem.data_ptr() as *const _,
425            size: mem.data_size(),
426        };
427
428        let mut writer =
429            unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
430        writer.write([1u8, 2])?;
431        let mut reader = writer.shared();
432        let r = reader.read_bytes(2);
433        assert_eq!(r, &[1, 2]);
434
435        let mut writer = unsafe { reader.exclusive() };
436        writer.write([3u8, 4])?;
437        let mut reader = writer.shared();
438        let r = reader.read_bytes(2);
439        assert_eq!(r, &[3, 4]);
440        Ok(())
441    }
442
443    #[test]
444    #[ignore]
445    fn update() -> Result<(), Box<dyn std::error::Error>> {
446        let (mut store, instance) = build_test_mod()?;
447        let mem = instance.exports.get_memory("memory")?.view(&store);
448        let linear_mem = WasmLinearMem {
449            start_ptr: mem.data_ptr() as *const _,
450            size: mem.data_size(),
451        };
452
453        let ptr = {
454            let mut writer =
455                unsafe { BufferMut::from_ptr(init_buf(&mut store, &instance, 10), linear_mem) };
456            writer.write([1u8, 2])?;
457            writer.ptr()
458        };
459
460        let writer = unsafe {
461            let builder = &mut *ptr;
462            builder.update_buffer(vec![3, 5, 7]);
463            BufferMut::from_ptr(ptr, linear_mem)
464        };
465        let mut reader = writer.shared();
466        assert_eq!(reader.read_all(), &[3, 5, 7]);
467
468        Ok(())
469    }
470}