From c7ccc2092a450ea162d337387a1dafe293654eff Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 8 Jul 2023 20:35:33 +0200 Subject: [PATCH] add the memory.fill and memory.copy commands to our wasm interpreter --- crates/wasm_interp/src/instance.rs | 41 +++++++++++++++ crates/wasm_interp/src/tests/test_mem.rs | 66 ++++++++++++++++++++++++ crates/wasm_interp/src/wasi.rs | 21 +++++++- crates/wasm_module/src/opcodes.rs | 2 + 4 files changed, 128 insertions(+), 2 deletions(-) diff --git a/crates/wasm_interp/src/instance.rs b/crates/wasm_interp/src/instance.rs index 7040b8757e..7b454bf075 100644 --- a/crates/wasm_interp/src/instance.rs +++ b/crates/wasm_interp/src/instance.rs @@ -976,6 +976,47 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> { self.value_store.push(Value::I32(-1)); } } + MEMORY => { + // the first argument determines exactly which memory operation we have + let op = module.code.bytes[self.program_counter]; + self.program_counter += 1; + + match op { + 8 => { + // memory.init + todo!("WASM instruction: memory.init") + } + 9 => { + // data.drop x + todo!("WASM instruction: data.drop") + } + 10 => { + // memory.copy + let size = self.value_store.pop_u32()? as usize; + let source = self.value_store.pop_u32()? as usize; + let destination = self.value_store.pop_u32()? as usize; + + // skip two zero bytes; in future versions of WebAssembly this byte may be + // used to index additional memories + self.program_counter += 2; + + self.memory.copy_within(source..source + size, destination) + } + 11 => { + // memory.fill + let size = self.value_store.pop_u32()? as usize; + let byte_value = self.value_store.pop_u32()? as u8; + let destination = self.value_store.pop_u32()? as usize; + + // skip a zero byte; in future versions of WebAssembly this byte may be + // used to index additional memories + self.program_counter += 1; + + self.memory[destination..][..size].fill(byte_value); + } + other => unreachable!("invalid memory instruction {:?}", other), + } + } I32CONST => { let value = i32::parse((), &module.code.bytes, &mut self.program_counter).unwrap(); self.write_debug(value); diff --git a/crates/wasm_interp/src/tests/test_mem.rs b/crates/wasm_interp/src/tests/test_mem.rs index 731b2b12b9..09f54d8e54 100644 --- a/crates/wasm_interp/src/tests/test_mem.rs +++ b/crates/wasm_interp/src/tests/test_mem.rs @@ -49,6 +49,72 @@ fn test_growmemory() { assert_eq!(state.memory.len(), 5 * MemorySection::PAGE_SIZE as usize); } +#[test] +fn test_memory_fill() { + let arena = Bump::new(); + let mut module = WasmModule::new(&arena); + + let pages = 3; + let pc = 0; + module.memory = MemorySection::new(&arena, pages * MemorySection::PAGE_SIZE); + + const SIZE: i32 = 16; + let byte_value = 0xAA; + let destination = 0x4; + + let bytes = [OpCode::MEMORY as u8, 11, 0x0]; + module.code.bytes.extend(bytes); + + let mut state = Instance::new(&arena, pages, pc, [], DefaultImportDispatcher::default()); + + state.value_store.push(Value::I32(destination)); + state.value_store.push(Value::I32(byte_value)); + state.value_store.push(Value::I32(SIZE)); + + // before the instruction, the memory is all zeros + let slice = &state.memory[destination as usize..][..SIZE as usize]; + assert_eq!(slice, &[0; SIZE as usize]); + + state.execute_next_instruction(&module).unwrap(); + + let slice = &state.memory[destination as usize..][..SIZE as usize]; + assert_eq!(slice, &[byte_value as u8; SIZE as usize]) +} + +#[test] +fn test_memory_copy() { + let arena = Bump::new(); + let mut module = WasmModule::new(&arena); + + let pages = 3; + let pc = 0; + module.memory = MemorySection::new(&arena, pages * MemorySection::PAGE_SIZE); + + const SIZE: i32 = 4; + let source = 0x4; + let destination = 0x8; + + let bytes = [OpCode::MEMORY as u8, 10, 0x0, 0x0]; + module.code.bytes.extend(bytes); + + let mut state = Instance::new(&arena, pages, pc, [], DefaultImportDispatcher::default()); + + state.value_store.push(Value::I32(destination)); + state.value_store.push(Value::I32(source)); + state.value_store.push(Value::I32(SIZE)); + + // before the instruction, the memory is all zeros + let slice = &mut state.memory[source as usize..][..SIZE as usize]; + assert_eq!(slice, &[0; SIZE as usize]); + + slice.fill(0xAA); + + state.execute_next_instruction(&module).unwrap(); + + let slice = &state.memory[destination as usize..][..SIZE as usize]; + assert_eq!(slice, &[0xAA; SIZE as usize]) +} + fn test_load(load_op: OpCode, ty: ValueType, data: &[u8], addr: u32, offset: u32) -> Value { let arena = Bump::new(); let mut module = WasmModule::new(&arena); diff --git a/crates/wasm_interp/src/wasi.rs b/crates/wasm_interp/src/wasi.rs index a8a288de24..b6a9242ef3 100644 --- a/crates/wasm_interp/src/wasi.rs +++ b/crates/wasm_interp/src/wasi.rs @@ -88,8 +88,25 @@ impl<'a> WasiDispatcher<'a> { success_code } - "environ_get" => todo!("WASI {}({:?})", function_name, arguments), - "environ_sizes_get" => todo!("WASI {}({:?})", function_name, arguments), + "environ_get" => { + // `environ_sizes_get` always reports 0 environment variables + // so we don't have to do anything here. + + success_code + } + "environ_sizes_get" => { + let num_env_ptr = arguments[0].expect_i32().unwrap() as usize; + let size_env_ptr = arguments[1].expect_i32().unwrap() as usize; + + // Calculate the total size required for environment variables + let total_size = 0; + let count = 0; + + write_u32(memory, num_env_ptr, count); + write_u32(memory, size_env_ptr, total_size as u32); + + success_code + } "clock_res_get" => success_code, // this dummy implementation seems to be good enough for some functions "clock_time_get" => success_code, "fd_advise" => todo!("WASI {}({:?})", function_name, arguments), diff --git a/crates/wasm_module/src/opcodes.rs b/crates/wasm_module/src/opcodes.rs index a6058f01bf..c294cc47d1 100644 --- a/crates/wasm_module/src/opcodes.rs +++ b/crates/wasm_module/src/opcodes.rs @@ -50,6 +50,7 @@ pub enum OpCode { I64STORE32 = 0x3e, CURRENTMEMORY = 0x3f, GROWMEMORY = 0x40, + MEMORY = 0xFC, I32CONST = 0x41, I64CONST = 0x42, F32CONST = 0x43, @@ -232,6 +233,7 @@ fn immediates_for(op: OpCode) -> Result { | I64STORE32 => Leb32x2, CURRENTMEMORY | GROWMEMORY => Byte1, + MEMORY => Leb32x2, I32CONST => Leb32x1, I64CONST => Leb64x1,