summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/tinywasm/src/instance.rs5
-rw-r--r--crates/tinywasm/src/store/memory/lazy.rs178
-rw-r--r--crates/tinywasm/src/store/mod.rs13
-rw-r--r--crates/tinywasm/tests/memory_backends.rs9
4 files changed, 84 insertions, 121 deletions
diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs
index b509d0f..a8cf14a 100644
--- a/crates/tinywasm/src/instance.rs
+++ b/crates/tinywasm/src/instance.rs
@@ -174,7 +174,10 @@ impl ModuleInstance {
addrs.funcs.extend(store.init_funcs(&module.funcs, idx));
addrs.tables.extend(store.init_tables(&module.table_types)?);
match module.local_memory_allocation {
- LocalMemoryAllocation::Skip => {}
+ LocalMemoryAllocation::Skip => {
+ #[cfg(feature = "guest-debug")]
+ addrs.memories.extend(store.init_memories(&module.memory_types, MemoryInstance::new_lazy)?);
+ }
LocalMemoryAllocation::Lazy => {
addrs.memories.extend(store.init_memories(&module.memory_types, MemoryInstance::new_lazy)?)
}
diff --git a/crates/tinywasm/src/store/memory/lazy.rs b/crates/tinywasm/src/store/memory/lazy.rs
index 4b0fe56..2bcce09 100644
--- a/crates/tinywasm/src/store/memory/lazy.rs
+++ b/crates/tinywasm/src/store/memory/lazy.rs
@@ -1,6 +1,4 @@
use alloc::boxed::Box;
-use alloc::vec::Vec;
-use core::cell::RefCell;
use core::hint::cold_path;
use tinywasm_types::MemoryType;
@@ -9,19 +7,19 @@ use crate::{Error, MemoryBackend, Result};
use super::LinearMemory;
-/// A linear memory wrapper that materializes its inner backend on first access.
+/// A linear memory wrapper that allocates its backend on the first mutation.
///
-/// If the wrapped backend fails to create the inner memory during first access,
-/// this wrapper will panic.
+/// Before materialization, the memory is represented by its logical length and
+/// reads return the zeroes required by WebAssembly semantics.
pub struct LazyLinearMemory {
ty: MemoryType,
initial_len: usize,
backend: MemoryBackend,
- inner: RefCell<Option<Box<dyn LinearMemory>>>,
+ inner: Option<Box<dyn LinearMemory>>,
}
impl LazyLinearMemory {
- /// Creates a lazy memory for `ty` using `backend` for the eventual materialized storage.
+ /// Creates a lazy memory for `ty` using `backend` for eventual storage.
pub fn try_new(ty: MemoryType, backend: MemoryBackend) -> Result<Self> {
let initial_len = usize::try_from(ty.initial_size())
.map_err(|_| Error::UnsupportedFeature("memory size exceeds the host address space"))?;
@@ -29,147 +27,99 @@ impl LazyLinearMemory {
}
pub(crate) fn new_with_initial_len(ty: MemoryType, initial_len: usize, backend: MemoryBackend) -> Self {
- Self { ty, initial_len, backend, inner: RefCell::new(None) }
+ Self { ty, initial_len, backend, inner: None }
}
- fn with_inner<R>(&self, f: impl FnOnce(&dyn LinearMemory) -> R) -> R {
- self.ensure_materialized();
- let inner = self.inner.borrow();
- f(inner.as_deref().expect("lazy memory should be materialized"))
- }
-
- fn try_with_inner<R>(&self, f: impl FnOnce(&dyn LinearMemory) -> R) -> core::result::Result<R, crate::Trap> {
- self.try_ensure_materialized()?;
- let inner = self.inner.borrow();
- Ok(f(inner.as_deref().expect("lazy memory should be materialized")))
- }
-
- fn with_inner_mut<R>(&self, f: impl FnOnce(&mut dyn LinearMemory) -> R) -> R {
- self.ensure_materialized();
- let mut inner = self.inner.borrow_mut();
- f(inner.as_deref_mut().expect("lazy memory should be materialized"))
- }
-
- fn try_with_inner_mut<R>(
- &self,
- f: impl FnOnce(&mut dyn LinearMemory) -> R,
- ) -> core::result::Result<R, crate::Trap> {
- self.try_ensure_materialized()?;
- let mut inner = self.inner.borrow_mut();
- Ok(f(inner.as_deref_mut().expect("lazy memory should be materialized")))
- }
-
- fn ensure_materialized(&self) {
- if self.inner.borrow().is_some() {
- return;
+ fn materialize(&mut self) -> &mut dyn LinearMemory {
+ if self.inner.is_none() {
+ self.inner =
+ Some(self.backend.create(self.ty, self.initial_len).expect("lazy memory materialization failed").0);
}
-
- let storage = self.backend.create(self.ty, self.initial_len).expect("lazy memory materialization failed");
- *self.inner.borrow_mut() = Some(storage.0);
+ self.inner.as_deref_mut().expect("lazy memory should be materialized")
}
- fn try_ensure_materialized(&self) -> core::result::Result<(), crate::Trap> {
- if self.inner.borrow().is_some() {
- return Ok(());
+ fn try_materialize(&mut self) -> core::result::Result<&mut dyn LinearMemory, crate::Trap> {
+ if self.inner.is_none() {
+ let storage = match self.backend.create(self.ty, self.initial_len) {
+ Ok(storage) => storage,
+ Err(Error::Trap(trap)) => {
+ cold_path();
+ return Err(trap);
+ }
+ Err(err) => panic!("lazy memory materialization failed: {err}"),
+ };
+ self.inner = Some(storage.0);
}
-
- let storage = match self.backend.create(self.ty, self.initial_len) {
- Ok(storage) => storage,
- Err(Error::Trap(trap)) => {
- cold_path();
- return Err(trap);
- }
- Err(err) => panic!("lazy memory materialization failed: {err}"),
- };
- *self.inner.borrow_mut() = Some(storage.0);
- Ok(())
+ Ok(self.inner.as_deref_mut().expect("lazy memory should be materialized"))
}
}
impl LinearMemory for LazyLinearMemory {
fn len(&self) -> usize {
- self.with_inner(|inner| inner.len())
+ self.inner.as_deref().map_or(self.initial_len, LinearMemory::len)
}
fn grow_to(&mut self, new_len: usize) -> Result<(), crate::Trap> {
- self.try_with_inner_mut(|inner| inner.grow_to(new_len))?
+ self.try_materialize()?.grow_to(new_len)
}
fn read(&self, addr: usize, dst: &mut [u8]) -> usize {
- self.with_inner(|inner| inner.read(addr, dst))
+ if let Some(inner) = self.inner.as_deref() {
+ return inner.read(addr, dst);
+ }
+ if addr >= self.initial_len {
+ return 0;
+ }
+ let read_len = dst.len().min(self.initial_len - addr);
+ dst[..read_len].fill(0);
+ read_len
}
fn write(&mut self, addr: usize, src: &[u8]) -> usize {
- self.with_inner_mut(|inner| inner.write(addr, src))
+ if src.is_empty() || addr >= self.len() {
+ return 0;
+ }
+ self.materialize().write(addr, src)
}
fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> {
- self.with_inner_mut(|inner| inner.write_all(addr, src))
+ let end = addr.checked_add(src.len())?;
+ if end > self.len() {
+ return None;
+ }
+ if src.is_empty() {
+ return Some(());
+ }
+ self.materialize().write_all(addr, src)
}
fn fill(&mut self, addr: usize, len: usize, val: u8) -> Option<()> {
- self.with_inner_mut(|inner| inner.fill(addr, len, val))
+ let end = addr.checked_add(len)?;
+ if end > self.len() {
+ return None;
+ }
+ if len == 0 || val == 0 && self.inner.is_none() {
+ return Some(());
+ }
+ self.materialize().fill(addr, len, val)
}
fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Option<()> {
- self.with_inner_mut(|inner| inner.copy_within(dst, src, len))
- }
-
- fn read_exact(&self, addr: usize, dst: &mut [u8]) -> Option<()> {
- self.with_inner(|inner| inner.read_exact(addr, dst))
- }
-
- fn read_vec(&self, addr: usize, len: usize) -> Option<Vec<u8>> {
- self.with_inner(|inner| inner.read_vec(addr, len))
- }
-
- fn read_8(&self, base: u64, offset: u64) -> core::result::Result<u8, crate::Trap> {
- self.try_with_inner(|inner| inner.read_8(base, offset))?
- }
-
- fn read_16(&self, base: u64, offset: u64) -> core::result::Result<[u8; 2], crate::Trap> {
- self.try_with_inner(|inner| inner.read_16(base, offset))?
- }
-
- fn read_32(&self, base: u64, offset: u64) -> core::result::Result<[u8; 4], crate::Trap> {
- self.try_with_inner(|inner| inner.read_32(base, offset))?
- }
-
- fn read_64(&self, base: u64, offset: u64) -> core::result::Result<[u8; 8], crate::Trap> {
- self.try_with_inner(|inner| inner.read_64(base, offset))?
- }
-
- fn read_128(&self, base: u64, offset: u64) -> core::result::Result<[u8; 16], crate::Trap> {
- self.try_with_inner(|inner| inner.read_128(base, offset))?
- }
-
- fn write_8(&mut self, base: u64, offset: u64, byte: u8) -> core::result::Result<(), crate::Trap> {
- self.try_with_inner_mut(|inner| inner.write_8(base, offset, byte))?
- }
-
- fn write_16(&mut self, base: u64, offset: u64, bytes: [u8; 2]) -> core::result::Result<(), crate::Trap> {
- self.try_with_inner_mut(|inner| inner.write_16(base, offset, bytes))?
- }
-
- fn write_32(&mut self, base: u64, offset: u64, bytes: [u8; 4]) -> core::result::Result<(), crate::Trap> {
- self.try_with_inner_mut(|inner| inner.write_32(base, offset, bytes))?
- }
-
- fn write_64(&mut self, base: u64, offset: u64, bytes: [u8; 8]) -> core::result::Result<(), crate::Trap> {
- self.try_with_inner_mut(|inner| inner.write_64(base, offset, bytes))?
- }
-
- fn write_128(&mut self, base: u64, offset: u64, bytes: [u8; 16]) -> core::result::Result<(), crate::Trap> {
- self.try_with_inner_mut(|inner| inner.write_128(base, offset, bytes))?
+ let src_end = src.checked_add(len)?;
+ let dst_end = dst.checked_add(len)?;
+ if src_end > self.len() || dst_end > self.len() {
+ return None;
+ }
+ if self.inner.is_none() || len == 0 || dst == src {
+ return Some(());
+ }
+ self.materialize().copy_within(dst, src, len)
}
}
#[cfg(feature = "debug")]
impl core::fmt::Debug for LazyLinearMemory {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- f.debug_struct("LazyLinearMemory")
- .field("ty", &self.ty)
- .field("materialized", &self.inner.borrow().is_some())
- .finish()
+ f.debug_struct("LazyLinearMemory").field("ty", &self.ty).field("materialized", &self.inner.is_some()).finish()
}
}
diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs
index 2d34432..875980a 100644
--- a/crates/tinywasm/src/store/mod.rs
+++ b/crates/tinywasm/src/store/mod.rs
@@ -281,12 +281,17 @@ impl Store {
pub(crate) fn init_memories(
&mut self,
memories: &[MemoryType],
- init: fn(MemoryType, &MemoryBackend) -> Result<MemoryInstance>,
+ init: impl Fn(MemoryType, &MemoryBackend) -> Result<MemoryInstance>,
) -> Result<impl ExactSizeIterator<Item = MemAddr>> {
let start = self.state.memories.len() as MemAddr;
- self.state.memories.reserve_exact(memories.len());
- for &mem in memories {
- self.state.memories.push(init(mem, &self.engine.config().memory_backend)?);
+ for mem in memories {
+ self.state.memories.push(match init(*mem, &self.engine.config().memory_backend) {
+ Ok(mem) => mem,
+ Err(e) => {
+ cold_path();
+ return Err(e);
+ }
+ });
}
Ok(start..start + memories.len() as MemAddr)
}
diff --git a/crates/tinywasm/tests/memory_backends.rs b/crates/tinywasm/tests/memory_backends.rs
index b965c9f..c231bc1 100644
--- a/crates/tinywasm/tests/memory_backends.rs
+++ b/crates/tinywasm/tests/memory_backends.rs
@@ -129,8 +129,8 @@ fn exported_local_memory_is_not_eagerly_allocated() -> Result<()> {
}
#[test]
-fn exported_local_memory_materializes_on_first_method_call() -> Result<()> {
- let (store, instance, created) = instantiate_exported_memory_with_counting_backend(
+fn exported_local_memory_reads_zeroes_without_materializing() -> Result<()> {
+ let (mut store, instance, created) = instantiate_exported_memory_with_counting_backend(
r#"
(module
(memory (export "memory") 1)
@@ -141,7 +141,12 @@ fn exported_local_memory_materializes_on_first_method_call() -> Result<()> {
let memory = instance.memory("memory")?;
assert_eq!(created.load(Ordering::Relaxed), 0);
assert_eq!(memory.len(&store)?, 65536);
+ assert_eq!(memory.read_vec(&store, 65534, 2)?, &[0, 0]);
+ assert_eq!(created.load(Ordering::Relaxed), 0);
+
+ memory.copy_from_slice(&mut store, 65534, &[1, 2])?;
assert_eq!(created.load(Ordering::Relaxed), 1);
+ assert_eq!(memory.read_vec(&store, 65534, 2)?, &[1, 2]);
Ok(())
}