Skip to content

Commit a70d85c

Browse files
authored
Merge pull request #62 from freenet/feat/improve-state-copying
feat: add std::io::Write impl for BufferMut
1 parent 7b60072 commit a70d85c

2 files changed

Lines changed: 119 additions & 8 deletions

File tree

rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "freenet-stdlib"
3-
version = "0.3.2"
3+
version = "0.3.3"
44
edition = "2021"
55
rust-version = "1.80"
66
publish = true

rust/src/memory/buf.rs

Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,27 @@ impl<'instance> BufferMut<'instance> {
184184
}
185185
}
186186

187+
impl std::io::Write for BufferMut<'_> {
188+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
189+
let last_write = (*self.write_ptr) as usize;
190+
let free = self.buffer.len() - last_write;
191+
let n = buf.len().min(free);
192+
if n == 0 && !buf.is_empty() {
193+
return Err(std::io::Error::new(
194+
std::io::ErrorKind::WriteZero,
195+
"buffer full",
196+
));
197+
}
198+
self.buffer[last_write..last_write + n].copy_from_slice(&buf[..n]);
199+
*self.write_ptr = (last_write + n) as u32;
200+
Ok(n)
201+
}
202+
203+
fn flush(&mut self) -> std::io::Result<()> {
204+
Ok(())
205+
}
206+
}
207+
187208
#[inline(always)]
188209
pub(crate) fn compute_ptr<T>(ptr: *mut T, linear_mem_space: &WasmLinearMem) -> *mut T {
189210
let mem_start_ptr = linear_mem_space.start_ptr;
@@ -200,13 +221,15 @@ fn from_raw_builder<'a>(builder_ptr: *mut BufferBuilder, mem: WasmLinearMem) ->
200221
unsafe {
201222
#[cfg(feature = "trace")]
202223
{
203-
let contract_mem = std::slice::from_raw_parts(mem.start_ptr, mem.size as usize);
204-
tracing::trace!(
205-
"*mut BufferBuilder <- offset: {}; in mem: {:?}",
206-
builder_ptr as usize,
207-
&contract_mem[builder_ptr as usize
208-
..builder_ptr as usize + std::mem::size_of::<BufferBuilder>()]
209-
);
224+
if !mem.start_ptr.is_null() && mem.size > 0 {
225+
let contract_mem = std::slice::from_raw_parts(mem.start_ptr, mem.size as usize);
226+
tracing::trace!(
227+
"*mut BufferBuilder <- offset: {}; in mem: {:?}",
228+
builder_ptr as usize,
229+
&contract_mem[builder_ptr as usize
230+
..builder_ptr as usize + std::mem::size_of::<BufferBuilder>()]
231+
);
232+
}
210233
// use std::{fs::File, io::Write};
211234
// let mut f = File::create(std::env::temp_dir().join("dump.mem")).unwrap();
212235
// f.write_all(contract_mem).unwrap();
@@ -340,6 +363,94 @@ fn __frnt__initiate_buffer(capacity: u32) -> i64 {
340363
buffer as i64
341364
}
342365

366+
#[cfg(test)]
367+
mod test_io_write {
368+
use super::*;
369+
use std::io::Write;
370+
371+
/// Create a BufferMut backed by host memory (no WASM runtime needed).
372+
/// Uses `__frnt__initiate_buffer` which allocates in host memory during tests,
373+
/// and a null-base WasmLinearMem so compute_ptr is a no-op on absolute pointers.
374+
unsafe fn host_buffer_mut(capacity: u32) -> BufferMut<'static> {
375+
let builder_ptr = __frnt__initiate_buffer(capacity) as *mut BufferBuilder;
376+
let linear_mem = WasmLinearMem {
377+
start_ptr: std::ptr::null(),
378+
size: 0,
379+
};
380+
BufferMut::from_ptr(builder_ptr, linear_mem)
381+
}
382+
383+
/// Call std::io::Write::write (not BufferMut::write which has different signature)
384+
fn io_write(buf: &mut BufferMut<'_>, data: &[u8]) -> std::io::Result<usize> {
385+
Write::write(buf, data)
386+
}
387+
388+
#[test]
389+
fn write_trait_basic() {
390+
let mut buf = unsafe { host_buffer_mut(32) };
391+
let n = io_write(&mut buf, b"hello").unwrap();
392+
assert_eq!(n, 5);
393+
assert_eq!(buf.read_bytes(5), b"hello");
394+
}
395+
396+
#[test]
397+
fn write_trait_fills_exactly() {
398+
let mut buf = unsafe { host_buffer_mut(4) };
399+
let n = io_write(&mut buf, b"abcd").unwrap();
400+
assert_eq!(n, 4);
401+
assert_eq!(buf.read_bytes(4), b"abcd");
402+
}
403+
404+
#[test]
405+
fn write_trait_partial_when_near_full() {
406+
let mut buf = unsafe { host_buffer_mut(4) };
407+
io_write(&mut buf, b"ab").unwrap();
408+
// Only 2 bytes free, writing 3 should write 2
409+
let n = io_write(&mut buf, b"xyz").unwrap();
410+
assert_eq!(n, 2);
411+
assert_eq!(buf.read_bytes(4), b"abxy");
412+
}
413+
414+
#[test]
415+
fn write_trait_error_when_full() {
416+
let mut buf = unsafe { host_buffer_mut(2) };
417+
io_write(&mut buf, b"ab").unwrap();
418+
let err = io_write(&mut buf, b"c").unwrap_err();
419+
assert_eq!(err.kind(), std::io::ErrorKind::WriteZero);
420+
}
421+
422+
#[test]
423+
fn write_trait_empty_slice_ok() {
424+
let mut buf = unsafe { host_buffer_mut(4) };
425+
let n = io_write(&mut buf, b"").unwrap();
426+
assert_eq!(n, 0);
427+
}
428+
429+
#[test]
430+
fn write_all_trait() {
431+
let mut buf = unsafe { host_buffer_mut(16) };
432+
buf.write_all(b"hello world").unwrap();
433+
assert_eq!(buf.read_bytes(11), b"hello world");
434+
}
435+
436+
#[test]
437+
fn write_all_insufficient_space() {
438+
let mut buf = unsafe { host_buffer_mut(4) };
439+
let err = buf.write_all(b"hello").unwrap_err();
440+
assert_eq!(err.kind(), std::io::ErrorKind::WriteZero);
441+
}
442+
443+
#[test]
444+
fn bincode_serialize_into() {
445+
let data: Vec<u32> = vec![1, 2, 3, 4, 5];
446+
let size = bincode::serialized_size(&data).unwrap() as usize;
447+
let mut buf = unsafe { host_buffer_mut(size as u32) };
448+
bincode::serialize_into(&mut buf, &data).unwrap();
449+
let result: Vec<u32> = bincode::deserialize(buf.read_bytes(size)).unwrap();
450+
assert_eq!(result, data);
451+
}
452+
}
453+
343454
#[cfg(all(test, any(unix, windows), feature = "wasmer-tests"))]
344455
mod test {
345456
use super::*;

0 commit comments

Comments
 (0)