Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions crates/herkos-runtime/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,33 @@ impl<const MAX_PAGES: usize> IsolatedMemory<MAX_PAGES> {
Ok(())
}

/// Wasm `memory.fill` — fill `len` bytes starting at `dst` with `val`.
///
/// Only the low 8 bits of `val` are used (Wasm spec). Traps (`OutOfBounds`)
/// if the region extends beyond the current active memory.
pub fn fill(&mut self, dst: usize, val: u8, len: usize) -> WasmResult<()> {
let active = self.active_size();
fill_inner(self.flat_mut(), active, dst, val, len)
}

/// Wasm `memory.init` — copy `len` bytes from `data[src_offset..]` into
/// linear memory at `dst`.
///
/// Unlike `init_data` (which copies an entire slice), this copies a
/// sub-range of a passive data segment. Traps (`OutOfBounds`) if either
/// the source range extends beyond `data` or the destination region extends
/// beyond active memory.
pub fn init_data_partial(
&mut self,
dst: usize,
data: &[u8],
src_offset: usize,
len: usize,
) -> WasmResult<()> {
let active = self.active_size();
init_data_partial_inner(self.flat_mut(), active, dst, data, src_offset, len)
}

// ── Bounds-checked (safe) load/store ──────────────────────────────

/// Load an i32 from linear memory with bounds checking.
Expand Down Expand Up @@ -467,6 +494,38 @@ fn init_data_inner(
Ok(())
}

#[inline(never)]
fn fill_inner(
memory: &mut [u8],
active_bytes: usize,
dst: usize,
val: u8,
len: usize,
) -> WasmResult<()> {
let region = checked_slice_mut(memory, active_bytes, dst, len)?;
region.fill(val);
Ok(())
}

#[inline(never)]
fn init_data_partial_inner(
memory: &mut [u8],
active_bytes: usize,
dst: usize,
data: &[u8],
src_offset: usize,
len: usize,
) -> WasmResult<()> {
let src_end = src_offset.checked_add(len).ok_or(WasmTrap::OutOfBounds)?;
if src_end > data.len() {
return Err(WasmTrap::OutOfBounds);
}
let src = &data[src_offset..src_end];
let dst_region = checked_slice_mut(memory, active_bytes, dst, len)?;
dst_region.copy_from_slice(src);
Ok(())
}

// ── Unchecked inner functions ─────────────────────────────────────────
//
// SAFETY: the caller (verified backend) guarantees the offset is in-bounds,
Expand Down Expand Up @@ -747,6 +806,89 @@ mod tests {
assert_eq!(mem.load_u8(5).unwrap(), 0xAB);
}

// ── fill ──

#[test]
fn fill_writes_byte_pattern() {
let mut mem = Mem::try_new(1).unwrap();
mem.fill(100, 0xAB, 5).unwrap();
for i in 0..5usize {
assert_eq!(mem.load_u8(100 + i).unwrap(), 0xAB);
}
}

#[test]
fn fill_zero_len_is_noop() {
let mut mem = Mem::try_new(1).unwrap();
assert!(mem.fill(0, 0xFF, 0).is_ok());
}

#[test]
fn fill_out_of_bounds() {
let mut mem = Mem::try_new(1).unwrap();
assert_eq!(mem.fill(PAGE_SIZE - 3, 0, 10), Err(WasmTrap::OutOfBounds));
}

#[test]
fn fill_at_boundary() {
let mut mem = Mem::try_new(1).unwrap();
assert!(mem.fill(PAGE_SIZE - 4, 0x42, 4).is_ok());
assert_eq!(mem.load_u8(PAGE_SIZE - 1).unwrap(), 0x42);
}

// ── init_data_partial ──

#[test]
fn init_data_partial_copies_subrange() {
let mut mem = Mem::try_new(1).unwrap();
let data = b"Hello, World!";
mem.init_data_partial(0, data, 7, 5).unwrap(); // "World"
assert_eq!(mem.load_u8(0).unwrap(), b'W');
assert_eq!(mem.load_u8(4).unwrap(), b'd');
}

#[test]
fn init_data_partial_zero_len_is_noop() {
let mut mem = Mem::try_new(1).unwrap();
assert!(mem.init_data_partial(0, b"Hello", 0, 0).is_ok());
}

#[test]
fn init_data_partial_full_segment() {
let mut mem = Mem::try_new(1).unwrap();
mem.init_data_partial(10, b"Hello", 0, 5).unwrap();
assert_eq!(mem.load_u8(10).unwrap(), b'H');
assert_eq!(mem.load_u8(14).unwrap(), b'o');
}

#[test]
fn init_data_partial_src_out_of_bounds() {
let mut mem = Mem::try_new(1).unwrap();
// src_offset=3, len=5: 3+5=8 > 5 (data.len())
assert_eq!(
mem.init_data_partial(0, b"Hello", 3, 5),
Err(WasmTrap::OutOfBounds)
);
}

#[test]
fn init_data_partial_dst_out_of_bounds() {
let mut mem = Mem::try_new(1).unwrap();
assert_eq!(
mem.init_data_partial(PAGE_SIZE - 2, b"Hello", 0, 5),
Err(WasmTrap::OutOfBounds)
);
}

#[test]
fn init_data_partial_src_offset_overflow() {
let mut mem = Mem::try_new(1).unwrap();
assert_eq!(
mem.init_data_partial(0, b"Hello", usize::MAX, 1),
Err(WasmTrap::OutOfBounds)
);
}

// ── little-endian encoding ──

#[test]
Expand Down
6 changes: 6 additions & 0 deletions crates/herkos-tests/data/wat/i32_extend16_s.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(func (export "func_0") (param i32) (result i32)
local.get 0
i32.extend16_s
)
)
6 changes: 6 additions & 0 deletions crates/herkos-tests/data/wat/i32_extend8_s.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(func (export "func_0") (param i32) (result i32)
local.get 0
i32.extend8_s
)
)
6 changes: 6 additions & 0 deletions crates/herkos-tests/data/wat/i64_extend16_s.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(func (export "func_0") (param i64) (result i64)
local.get 0
i64.extend16_s
)
)
6 changes: 6 additions & 0 deletions crates/herkos-tests/data/wat/i64_extend32_s.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(func (export "func_0") (param i64) (result i64)
local.get 0
i64.extend32_s
)
)
6 changes: 6 additions & 0 deletions crates/herkos-tests/data/wat/i64_extend8_s.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(func (export "func_0") (param i64) (result i64)
local.get 0
i64.extend8_s
)
)
14 changes: 14 additions & 0 deletions crates/herkos-tests/data/wat/memory_fill.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(module
(memory 1 1)

;; Fill a region of memory with a byte value.
(func (export "fill_region") (param $dst i32) (param $val i32) (param $len i32)
local.get $dst
local.get $val
local.get $len
memory.fill)

;; Load a single byte (i32.load8_u) for verification.
(func (export "load_byte") (param $addr i32) (result i32)
local.get $addr
i32.load8_u))
22 changes: 22 additions & 0 deletions crates/herkos-tests/data/wat/memory_init.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(module
(memory 1 1)

;; Passive data segment (index 0): "Hello"
(data "Hello")

;; Copy a sub-range of the passive segment into linear memory.
(func (export "init_region")
(param $dst i32) (param $src_offset i32) (param $len i32)
local.get $dst
local.get $src_offset
local.get $len
memory.init 0)

;; Drop the passive segment (no-op in the safe backend).
(func (export "drop_segment")
data.drop 0)

;; Load a single byte for verification.
(func (export "load_byte") (param $addr i32) (result i32)
local.get $addr
i32.load8_u))
Loading
Loading