Skip to content

Commit 10c68e7

Browse files
authored
Merge pull request #18 from arnoox/add-missing-memory-ops
Add missing memory ops
2 parents 97408a6 + 00deacd commit 10c68e7

20 files changed

Lines changed: 793 additions & 34 deletions

File tree

crates/herkos-runtime/src/memory.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,33 @@ impl<const MAX_PAGES: usize> IsolatedMemory<MAX_PAGES> {
146146
Ok(())
147147
}
148148

149+
/// Wasm `memory.fill` — fill `len` bytes starting at `dst` with `val`.
150+
///
151+
/// Only the low 8 bits of `val` are used (Wasm spec). Traps (`OutOfBounds`)
152+
/// if the region extends beyond the current active memory.
153+
pub fn fill(&mut self, dst: usize, val: u8, len: usize) -> WasmResult<()> {
154+
let active = self.active_size();
155+
fill_inner(self.flat_mut(), active, dst, val, len)
156+
}
157+
158+
/// Wasm `memory.init` — copy `len` bytes from `data[src_offset..]` into
159+
/// linear memory at `dst`.
160+
///
161+
/// Unlike `init_data` (which copies an entire slice), this copies a
162+
/// sub-range of a passive data segment. Traps (`OutOfBounds`) if either
163+
/// the source range extends beyond `data` or the destination region extends
164+
/// beyond active memory.
165+
pub fn init_data_partial(
166+
&mut self,
167+
dst: usize,
168+
data: &[u8],
169+
src_offset: usize,
170+
len: usize,
171+
) -> WasmResult<()> {
172+
let active = self.active_size();
173+
init_data_partial_inner(self.flat_mut(), active, dst, data, src_offset, len)
174+
}
175+
149176
// ── Bounds-checked (safe) load/store ──────────────────────────────
150177

151178
/// Load an i32 from linear memory with bounds checking.
@@ -467,6 +494,38 @@ fn init_data_inner(
467494
Ok(())
468495
}
469496

497+
#[inline(never)]
498+
fn fill_inner(
499+
memory: &mut [u8],
500+
active_bytes: usize,
501+
dst: usize,
502+
val: u8,
503+
len: usize,
504+
) -> WasmResult<()> {
505+
let region = checked_slice_mut(memory, active_bytes, dst, len)?;
506+
region.fill(val);
507+
Ok(())
508+
}
509+
510+
#[inline(never)]
511+
fn init_data_partial_inner(
512+
memory: &mut [u8],
513+
active_bytes: usize,
514+
dst: usize,
515+
data: &[u8],
516+
src_offset: usize,
517+
len: usize,
518+
) -> WasmResult<()> {
519+
let src_end = src_offset.checked_add(len).ok_or(WasmTrap::OutOfBounds)?;
520+
if src_end > data.len() {
521+
return Err(WasmTrap::OutOfBounds);
522+
}
523+
let src = &data[src_offset..src_end];
524+
let dst_region = checked_slice_mut(memory, active_bytes, dst, len)?;
525+
dst_region.copy_from_slice(src);
526+
Ok(())
527+
}
528+
470529
// ── Unchecked inner functions ─────────────────────────────────────────
471530
//
472531
// SAFETY: the caller (verified backend) guarantees the offset is in-bounds,
@@ -747,6 +806,89 @@ mod tests {
747806
assert_eq!(mem.load_u8(5).unwrap(), 0xAB);
748807
}
749808

809+
// ── fill ──
810+
811+
#[test]
812+
fn fill_writes_byte_pattern() {
813+
let mut mem = Mem::try_new(1).unwrap();
814+
mem.fill(100, 0xAB, 5).unwrap();
815+
for i in 0..5usize {
816+
assert_eq!(mem.load_u8(100 + i).unwrap(), 0xAB);
817+
}
818+
}
819+
820+
#[test]
821+
fn fill_zero_len_is_noop() {
822+
let mut mem = Mem::try_new(1).unwrap();
823+
assert!(mem.fill(0, 0xFF, 0).is_ok());
824+
}
825+
826+
#[test]
827+
fn fill_out_of_bounds() {
828+
let mut mem = Mem::try_new(1).unwrap();
829+
assert_eq!(mem.fill(PAGE_SIZE - 3, 0, 10), Err(WasmTrap::OutOfBounds));
830+
}
831+
832+
#[test]
833+
fn fill_at_boundary() {
834+
let mut mem = Mem::try_new(1).unwrap();
835+
assert!(mem.fill(PAGE_SIZE - 4, 0x42, 4).is_ok());
836+
assert_eq!(mem.load_u8(PAGE_SIZE - 1).unwrap(), 0x42);
837+
}
838+
839+
// ── init_data_partial ──
840+
841+
#[test]
842+
fn init_data_partial_copies_subrange() {
843+
let mut mem = Mem::try_new(1).unwrap();
844+
let data = b"Hello, World!";
845+
mem.init_data_partial(0, data, 7, 5).unwrap(); // "World"
846+
assert_eq!(mem.load_u8(0).unwrap(), b'W');
847+
assert_eq!(mem.load_u8(4).unwrap(), b'd');
848+
}
849+
850+
#[test]
851+
fn init_data_partial_zero_len_is_noop() {
852+
let mut mem = Mem::try_new(1).unwrap();
853+
assert!(mem.init_data_partial(0, b"Hello", 0, 0).is_ok());
854+
}
855+
856+
#[test]
857+
fn init_data_partial_full_segment() {
858+
let mut mem = Mem::try_new(1).unwrap();
859+
mem.init_data_partial(10, b"Hello", 0, 5).unwrap();
860+
assert_eq!(mem.load_u8(10).unwrap(), b'H');
861+
assert_eq!(mem.load_u8(14).unwrap(), b'o');
862+
}
863+
864+
#[test]
865+
fn init_data_partial_src_out_of_bounds() {
866+
let mut mem = Mem::try_new(1).unwrap();
867+
// src_offset=3, len=5: 3+5=8 > 5 (data.len())
868+
assert_eq!(
869+
mem.init_data_partial(0, b"Hello", 3, 5),
870+
Err(WasmTrap::OutOfBounds)
871+
);
872+
}
873+
874+
#[test]
875+
fn init_data_partial_dst_out_of_bounds() {
876+
let mut mem = Mem::try_new(1).unwrap();
877+
assert_eq!(
878+
mem.init_data_partial(PAGE_SIZE - 2, b"Hello", 0, 5),
879+
Err(WasmTrap::OutOfBounds)
880+
);
881+
}
882+
883+
#[test]
884+
fn init_data_partial_src_offset_overflow() {
885+
let mut mem = Mem::try_new(1).unwrap();
886+
assert_eq!(
887+
mem.init_data_partial(0, b"Hello", usize::MAX, 1),
888+
Err(WasmTrap::OutOfBounds)
889+
);
890+
}
891+
750892
// ── little-endian encoding ──
751893

752894
#[test]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(module
2+
(func (export "func_0") (param i32) (result i32)
3+
local.get 0
4+
i32.extend16_s
5+
)
6+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(module
2+
(func (export "func_0") (param i32) (result i32)
3+
local.get 0
4+
i32.extend8_s
5+
)
6+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(module
2+
(func (export "func_0") (param i64) (result i64)
3+
local.get 0
4+
i64.extend16_s
5+
)
6+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(module
2+
(func (export "func_0") (param i64) (result i64)
3+
local.get 0
4+
i64.extend32_s
5+
)
6+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(module
2+
(func (export "func_0") (param i64) (result i64)
3+
local.get 0
4+
i64.extend8_s
5+
)
6+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(module
2+
(memory 1 1)
3+
4+
;; Fill a region of memory with a byte value.
5+
(func (export "fill_region") (param $dst i32) (param $val i32) (param $len i32)
6+
local.get $dst
7+
local.get $val
8+
local.get $len
9+
memory.fill)
10+
11+
;; Load a single byte (i32.load8_u) for verification.
12+
(func (export "load_byte") (param $addr i32) (result i32)
13+
local.get $addr
14+
i32.load8_u))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(module
2+
(memory 1 1)
3+
4+
;; Passive data segment (index 0): "Hello"
5+
(data "Hello")
6+
7+
;; Copy a sub-range of the passive segment into linear memory.
8+
(func (export "init_region")
9+
(param $dst i32) (param $src_offset i32) (param $len i32)
10+
local.get $dst
11+
local.get $src_offset
12+
local.get $len
13+
memory.init 0)
14+
15+
;; Drop the passive segment (no-op in the safe backend).
16+
(func (export "drop_segment")
17+
data.drop 0)
18+
19+
;; Load a single byte for verification.
20+
(func (export "load_byte") (param $addr i32) (result i32)
21+
local.get $addr
22+
i32.load8_u))

0 commit comments

Comments
 (0)