mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 15:59:20 +03:00
Merge pull request #4602 from roc-lang/wasm_interp_memory
Wasm interp memory
This commit is contained in:
commit
808dd85946
@ -1,5 +1,6 @@
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
use std::fmt::Write;
|
||||
use std::fmt::{self, Write};
|
||||
use std::iter;
|
||||
|
||||
use roc_wasm_module::opcodes::OpCode;
|
||||
use roc_wasm_module::parse::Parse;
|
||||
@ -17,9 +18,7 @@ pub enum Action {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExecutionState<'a> {
|
||||
#[allow(dead_code)]
|
||||
memory: Vec<'a, u8>,
|
||||
|
||||
pub memory: Vec<'a, u8>,
|
||||
pub call_stack: CallStack<'a>,
|
||||
pub value_stack: ValueStack<'a>,
|
||||
pub globals: Vec<'a, Value>,
|
||||
@ -36,7 +35,7 @@ impl<'a> ExecutionState<'a> {
|
||||
{
|
||||
let mem_bytes = memory_pages * MemorySection::PAGE_SIZE;
|
||||
ExecutionState {
|
||||
memory: Vec::with_capacity_in(mem_bytes as usize, arena),
|
||||
memory: Vec::from_iter_in(iter::repeat(0).take(mem_bytes as usize), arena),
|
||||
call_stack: CallStack::new(arena),
|
||||
value_stack: ValueStack::new(arena),
|
||||
globals: Vec::from_iter_in(globals, arena),
|
||||
@ -59,6 +58,8 @@ impl<'a> ExecutionState<'a> {
|
||||
e.offset, e.message
|
||||
)
|
||||
})?;
|
||||
let mut memory = Vec::from_iter_in(iter::repeat(0).take(mem_bytes as usize), arena);
|
||||
module.data.load_into(&mut memory)?;
|
||||
|
||||
let globals = module.global.initial_values(arena);
|
||||
|
||||
@ -110,7 +111,7 @@ impl<'a> ExecutionState<'a> {
|
||||
};
|
||||
|
||||
Ok(ExecutionState {
|
||||
memory: Vec::with_capacity_in(mem_bytes as usize, arena),
|
||||
memory,
|
||||
call_stack,
|
||||
value_stack,
|
||||
globals,
|
||||
@ -145,6 +146,34 @@ impl<'a> ExecutionState<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_load_address(&mut self, module: &WasmModule<'a>) -> u32 {
|
||||
// Alignment is not used in the execution steps from the spec! Maybe it's just an optimization hint?
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions
|
||||
// Also note: in the text format we can specify the useless `align=` but not the useful `offset=`!
|
||||
let _alignment = self.fetch_immediate_u32(module);
|
||||
let offset = self.fetch_immediate_u32(module);
|
||||
let base_addr = self.value_stack.pop_u32();
|
||||
base_addr + offset
|
||||
}
|
||||
|
||||
fn get_store_addr_value(&mut self, module: &WasmModule<'a>) -> (usize, Value) {
|
||||
// Alignment is not used in the execution steps from the spec! Maybe it's just an optimization hint?
|
||||
// https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions
|
||||
// Also note: in the text format we can specify the useless `align=` but not the useful `offset=`!
|
||||
let _alignment = self.fetch_immediate_u32(module);
|
||||
let offset = self.fetch_immediate_u32(module);
|
||||
let value = self.value_stack.pop();
|
||||
let base_addr = self.value_stack.pop_u32();
|
||||
let addr = (base_addr + offset) as usize;
|
||||
(addr, value)
|
||||
}
|
||||
|
||||
fn write_debug<T: fmt::Debug>(&mut self, value: T) {
|
||||
if let Some(debug_string) = self.debug_string.as_mut() {
|
||||
std::write!(debug_string, "{:?} ", value).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_next_instruction(&mut self, module: &WasmModule<'a>) -> Action {
|
||||
use OpCode::*;
|
||||
|
||||
@ -154,7 +183,7 @@ impl<'a> ExecutionState<'a> {
|
||||
|
||||
if let Some(debug_string) = self.debug_string.as_mut() {
|
||||
debug_string.clear();
|
||||
write!(debug_string, "{:?} ", op_code).unwrap();
|
||||
self.write_debug(op_code);
|
||||
}
|
||||
|
||||
let mut action = Action::Continue;
|
||||
@ -247,52 +276,192 @@ impl<'a> ExecutionState<'a> {
|
||||
let index = self.fetch_immediate_u32(module);
|
||||
self.globals[index as usize] = self.value_stack.pop();
|
||||
}
|
||||
I32LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
F32LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
F64LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32LOAD8S => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32LOAD8U => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32LOAD16S => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32LOAD16U => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64LOAD8S => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64LOAD8U => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64LOAD16S => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64LOAD16U => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64LOAD32S => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64LOAD32U => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
F32STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
F64STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32STORE8 => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32STORE16 => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64STORE8 => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64STORE16 => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I64STORE32 => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
CURRENTMEMORY => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
GROWMEMORY => todo!("{:?} @ {:#x}", op_code, file_offset),
|
||||
I32LOAD => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 4];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..4]);
|
||||
let value = i32::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I32(value));
|
||||
}
|
||||
I64LOAD => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 8];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..8]);
|
||||
let value = i64::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I64(value));
|
||||
}
|
||||
F32LOAD => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 4];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..4]);
|
||||
let value = f32::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::F32(value));
|
||||
}
|
||||
F64LOAD => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 8];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..8]);
|
||||
let value = f64::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::F64(value));
|
||||
}
|
||||
I32LOAD8S => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 1];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..1]);
|
||||
let value = i8::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I32(value as i32));
|
||||
}
|
||||
I32LOAD8U => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let value = self.memory[addr];
|
||||
self.value_stack.push(Value::I32(value as i32));
|
||||
}
|
||||
I32LOAD16S => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 2];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..2]);
|
||||
let value = i16::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I32(value as i32));
|
||||
}
|
||||
I32LOAD16U => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 2];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..2]);
|
||||
let value = u16::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I32(value as i32));
|
||||
}
|
||||
I64LOAD8S => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 1];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..1]);
|
||||
let value = i8::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I64(value as i64));
|
||||
}
|
||||
I64LOAD8U => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let value = self.memory[addr];
|
||||
self.value_stack.push(Value::I64(value as i64));
|
||||
}
|
||||
I64LOAD16S => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 2];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..2]);
|
||||
let value = i16::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I64(value as i64));
|
||||
}
|
||||
I64LOAD16U => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 2];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..2]);
|
||||
let value = u16::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I64(value as i64));
|
||||
}
|
||||
I64LOAD32S => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 4];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..4]);
|
||||
let value = i32::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I64(value as i64));
|
||||
}
|
||||
I64LOAD32U => {
|
||||
let addr = self.get_load_address(module) as usize;
|
||||
let mut bytes = [0; 4];
|
||||
bytes.copy_from_slice(&self.memory[addr..][..4]);
|
||||
let value = u32::from_le_bytes(bytes);
|
||||
self.value_stack.push(Value::I64(value as i64));
|
||||
}
|
||||
I32STORE => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_i32();
|
||||
let target = &mut self.memory[addr..][..4];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes());
|
||||
}
|
||||
I64STORE => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_i64();
|
||||
let target = &mut self.memory[addr..][..8];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes());
|
||||
}
|
||||
F32STORE => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_f32();
|
||||
let target = &mut self.memory[addr..][..4];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes());
|
||||
}
|
||||
F64STORE => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_f64();
|
||||
let target = &mut self.memory[addr..][..8];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes());
|
||||
}
|
||||
I32STORE8 => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_i32();
|
||||
let target = &mut self.memory[addr..][..1];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes()[..1]);
|
||||
}
|
||||
I32STORE16 => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_i32();
|
||||
let target = &mut self.memory[addr..][..2];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes()[..2]);
|
||||
}
|
||||
I64STORE8 => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_i64();
|
||||
let target = &mut self.memory[addr..][..1];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes()[..1]);
|
||||
}
|
||||
I64STORE16 => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_i64();
|
||||
let target = &mut self.memory[addr..][..2];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes()[..2]);
|
||||
}
|
||||
I64STORE32 => {
|
||||
let (addr, value) = self.get_store_addr_value(module);
|
||||
let unwrapped = value.unwrap_i64();
|
||||
let target = &mut self.memory[addr..][..4];
|
||||
target.copy_from_slice(&unwrapped.to_le_bytes()[..4]);
|
||||
}
|
||||
CURRENTMEMORY => {
|
||||
let size = self.memory.len() as i32 / MemorySection::PAGE_SIZE as i32;
|
||||
self.value_stack.push(Value::I32(size));
|
||||
}
|
||||
GROWMEMORY => {
|
||||
let old_bytes = self.memory.len() as u32;
|
||||
let old_pages = old_bytes / MemorySection::PAGE_SIZE as u32;
|
||||
let grow_pages = self.value_stack.pop_u32();
|
||||
let grow_bytes = grow_pages * MemorySection::PAGE_SIZE;
|
||||
let new_bytes = old_bytes + grow_bytes;
|
||||
|
||||
let success = match module.memory.max_bytes().unwrap() {
|
||||
Some(max_bytes) => new_bytes <= max_bytes,
|
||||
None => true,
|
||||
};
|
||||
if success {
|
||||
self.memory
|
||||
.extend(iter::repeat(0).take(grow_bytes as usize));
|
||||
self.value_stack.push(Value::I32(old_pages as i32));
|
||||
} else {
|
||||
self.value_stack.push(Value::I32(-1));
|
||||
}
|
||||
}
|
||||
I32CONST => {
|
||||
let value = i32::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
|
||||
if let Some(debug_string) = self.debug_string.as_mut() {
|
||||
write!(debug_string, "{}", value).unwrap();
|
||||
}
|
||||
self.write_debug(value);
|
||||
self.value_stack.push(Value::I32(value));
|
||||
}
|
||||
I64CONST => {
|
||||
let value = i64::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
|
||||
if let Some(debug_string) = self.debug_string.as_mut() {
|
||||
write!(debug_string, "{}", value).unwrap();
|
||||
}
|
||||
self.write_debug(value);
|
||||
self.value_stack.push(Value::I64(value));
|
||||
}
|
||||
F32CONST => {
|
||||
let mut bytes = [0; 4];
|
||||
bytes.copy_from_slice(&module.code.bytes[self.program_counter..][..4]);
|
||||
let value = f32::from_le_bytes(bytes);
|
||||
if let Some(debug_string) = self.debug_string.as_mut() {
|
||||
write!(debug_string, "{}", value).unwrap();
|
||||
}
|
||||
self.write_debug(value);
|
||||
self.value_stack.push(Value::F32(value));
|
||||
self.program_counter += 4;
|
||||
}
|
||||
@ -300,9 +469,7 @@ impl<'a> ExecutionState<'a> {
|
||||
let mut bytes = [0; 8];
|
||||
bytes.copy_from_slice(&module.code.bytes[self.program_counter..][..8]);
|
||||
let value = f64::from_le_bytes(bytes);
|
||||
if let Some(debug_string) = self.debug_string.as_mut() {
|
||||
write!(debug_string, "{}", value).unwrap();
|
||||
}
|
||||
self.write_debug(value);
|
||||
self.value_stack.push(Value::F64(value));
|
||||
self.program_counter += 8;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use roc_wasm_module::WasmModule;
|
||||
|
||||
pub const FLAG_FUNCTION: &str = "function";
|
||||
pub const FLAG_DEBUG: &str = "debug";
|
||||
pub const FLAG_HEX: &str = "hex";
|
||||
pub const WASM_FILE: &str = "WASM_FILE";
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
@ -29,6 +30,12 @@ fn main() -> io::Result<()> {
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false);
|
||||
|
||||
let flag_hex = Arg::new(FLAG_HEX)
|
||||
.long(FLAG_HEX)
|
||||
.help("If the called function returns a value, print it in hexadecimal format.")
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false);
|
||||
|
||||
let wasm_file_to_run = Arg::new(WASM_FILE)
|
||||
.help("The .wasm file to run")
|
||||
.allow_invalid_utf8(true)
|
||||
@ -38,6 +45,7 @@ fn main() -> io::Result<()> {
|
||||
.about("Run the given .wasm file")
|
||||
.arg(flag_function)
|
||||
.arg(flag_debug)
|
||||
.arg(flag_hex)
|
||||
.arg(wasm_file_to_run);
|
||||
|
||||
// Parse the command line arguments
|
||||
@ -45,6 +53,7 @@ fn main() -> io::Result<()> {
|
||||
let matches = app.get_matches();
|
||||
let start_fn_name = matches.get_one::<String>(FLAG_FUNCTION).unwrap();
|
||||
let is_debug_mode = matches.get_flag(FLAG_DEBUG);
|
||||
let is_hex_format = matches.get_flag(FLAG_HEX);
|
||||
|
||||
// Load the WebAssembly binary file
|
||||
|
||||
@ -58,7 +67,7 @@ fn main() -> io::Result<()> {
|
||||
let module = match WasmModule::preload(&arena, &module_bytes, require_relocatable) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("I couldn't parse this WebAssembly module! There's something wrong at byte offset 0x{}.", e.offset);
|
||||
eprintln!("I couldn't parse this WebAssembly module! There's something wrong at byte offset {:#x}.", e.offset);
|
||||
eprintln!("{}", e.message);
|
||||
eprintln!("If you think this could be a code generation problem in the Roc compiler, see crates/compiler/gen_wasm/README.md for debugging tips.");
|
||||
process::exit(1);
|
||||
@ -81,8 +90,20 @@ fn main() -> io::Result<()> {
|
||||
|
||||
match state.value_stack.len() {
|
||||
0 => {}
|
||||
1 => println!("{:?}", state.value_stack.pop()),
|
||||
_ => println!("{:?}", &state.value_stack),
|
||||
1 => {
|
||||
if is_hex_format {
|
||||
println!("{:#x?}", state.value_stack.pop())
|
||||
} else {
|
||||
println!("{:?}", state.value_stack.pop())
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if is_hex_format {
|
||||
println!("{:#x?}", &state.value_stack)
|
||||
} else {
|
||||
println!("{:?}", &state.value_stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -103,6 +103,18 @@ impl<'a> ValueStack<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory addresses etc
|
||||
pub fn pop_u32(&mut self) -> u32 {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(false), Some(false)) => pop_bytes!(u32, self.bytes),
|
||||
(Some(is_float), Some(is_64)) => panic!(
|
||||
"Expected I32 but found {:?}",
|
||||
type_from_flags(is_float, is_64)
|
||||
),
|
||||
_ => panic!("Expected I32 but value stack was empty"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_i32(&mut self) -> i32 {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(false), Some(false)) => pop_bytes!(i32, self.bytes),
|
||||
|
@ -3,7 +3,10 @@
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
use roc_wasm_interp::{Action, ExecutionState, ValueStack};
|
||||
use roc_wasm_module::{
|
||||
opcodes::OpCode, SerialBuffer, Serialize, Signature, Value, ValueType, WasmModule,
|
||||
opcodes::OpCode,
|
||||
sections::{DataMode, DataSegment, MemorySection},
|
||||
ConstExpr, Export, ExportType, SerialBuffer, Serialize, Signature, Value, ValueType,
|
||||
WasmModule,
|
||||
};
|
||||
|
||||
fn default_state(arena: &Bump) -> ExecutionState {
|
||||
@ -239,80 +242,459 @@ fn test_global() {
|
||||
assert_eq!(state.value_stack.pop(), Value::I32(222));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i32load() {}
|
||||
fn create_exported_function_no_locals<'a, F>(
|
||||
module: &mut WasmModule<'a>,
|
||||
name: &'a str,
|
||||
signature: Signature<'a>,
|
||||
write_instructions: F,
|
||||
) where
|
||||
F: FnOnce(&mut Vec<'a, u8>),
|
||||
{
|
||||
let internal_fn_index = module.code.function_offsets.len();
|
||||
let fn_index = module.import.function_count() + internal_fn_index;
|
||||
module.export.exports.push(Export {
|
||||
name,
|
||||
ty: ExportType::Func,
|
||||
index: fn_index as u32,
|
||||
});
|
||||
module.add_function_signature(signature);
|
||||
|
||||
// #[test]
|
||||
// fn test_i64load() {}
|
||||
let offset = module.code.bytes.encode_padded_u32(0);
|
||||
let start = module.code.bytes.len();
|
||||
module.code.bytes.push(0); // no locals
|
||||
write_instructions(&mut module.code.bytes);
|
||||
let len = module.code.bytes.len() - start;
|
||||
module.code.bytes.overwrite_padded_u32(offset, len as u32);
|
||||
|
||||
// #[test]
|
||||
// fn test_f32load() {}
|
||||
module.code.function_count += 1;
|
||||
module.code.function_offsets.push(offset as u32);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_f64load() {}
|
||||
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);
|
||||
|
||||
// #[test]
|
||||
// fn test_i32load8s() {}
|
||||
let is_debug_mode = false;
|
||||
let start_fn_name = "test";
|
||||
|
||||
// #[test]
|
||||
// fn test_i32load8u() {}
|
||||
module.memory = MemorySection::new(&arena, MemorySection::PAGE_SIZE);
|
||||
|
||||
// #[test]
|
||||
// fn test_i32load16s() {}
|
||||
module.data.append_segment(DataSegment {
|
||||
mode: DataMode::Active {
|
||||
offset: ConstExpr::I32(addr as i32),
|
||||
},
|
||||
init: Vec::from_iter_in(data.iter().copied(), &arena),
|
||||
});
|
||||
|
||||
// #[test]
|
||||
// fn test_i32load16u() {}
|
||||
let signature = Signature {
|
||||
param_types: bumpalo::vec![in &arena],
|
||||
ret_type: Some(ty),
|
||||
};
|
||||
|
||||
// #[test]
|
||||
// fn test_i64load8s() {}
|
||||
create_exported_function_no_locals(&mut module, start_fn_name, signature, |buf| {
|
||||
buf.append_u8(OpCode::I32CONST as u8);
|
||||
buf.encode_u32(addr);
|
||||
buf.append_u8(load_op as u8);
|
||||
buf.encode_u32(0); // align
|
||||
buf.encode_u32(offset);
|
||||
buf.append_u8(OpCode::END as u8);
|
||||
});
|
||||
|
||||
// #[test]
|
||||
// fn test_i64load8u() {}
|
||||
if false {
|
||||
let mut outfile_buf = Vec::new_in(&arena);
|
||||
module.serialize(&mut outfile_buf);
|
||||
std::fs::write("/tmp/roc/interp_load_test.wasm", outfile_buf).unwrap();
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i64load16s() {}
|
||||
let mut state =
|
||||
ExecutionState::for_module(&arena, &module, start_fn_name, is_debug_mode).unwrap();
|
||||
|
||||
// #[test]
|
||||
// fn test_i64load16u() {}
|
||||
while let Action::Continue = state.execute_next_instruction(&module) {}
|
||||
|
||||
// #[test]
|
||||
// fn test_i64load32s() {}
|
||||
state.value_stack.pop()
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i64load32u() {}
|
||||
#[test]
|
||||
fn test_i32load() {
|
||||
let bytes = "abcdefgh".as_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD, ValueType::I32, bytes, 0x11, 0),
|
||||
Value::I32(0x64636261)
|
||||
);
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD, ValueType::I32, bytes, 0x11, 2),
|
||||
Value::I32(0x66656463)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i32store() {}
|
||||
#[test]
|
||||
fn test_i64load() {
|
||||
let bytes = "abcdefghijkl".as_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I64LOAD, ValueType::I64, bytes, 0x11, 0),
|
||||
Value::I64(0x6867666564636261)
|
||||
);
|
||||
assert_eq!(
|
||||
test_load(OpCode::I64LOAD, ValueType::I64, bytes, 0x11, 2),
|
||||
Value::I64(0x6a69686766656463)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i64store() {}
|
||||
#[test]
|
||||
fn test_f32load() {
|
||||
let value: f32 = 1.23456;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::F32LOAD, ValueType::F32, &bytes, 0x11, 0),
|
||||
Value::F32(value)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_f32store() {}
|
||||
#[test]
|
||||
fn test_f64load() {
|
||||
let value: f64 = 1.23456;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::F64LOAD, ValueType::F64, &bytes, 0x11, 0),
|
||||
Value::F64(value)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_f64store() {}
|
||||
#[test]
|
||||
fn test_i32load8s() {
|
||||
let value: i8 = -42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD8S, ValueType::I32, &bytes, 0x11, 0),
|
||||
Value::I32(value as i32)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i32store8() {}
|
||||
#[test]
|
||||
fn test_i32load8u() {
|
||||
let value: u8 = 42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD8U, ValueType::I32, &bytes, 0x11, 0),
|
||||
Value::I32(value as i32)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i32store16() {}
|
||||
#[test]
|
||||
fn test_i32load16s() {
|
||||
let value: i16 = -42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD16S, ValueType::I32, &bytes, 0x11, 0),
|
||||
Value::I32(value as i32)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i64store8() {}
|
||||
#[test]
|
||||
fn test_i32load16u() {
|
||||
let value: u16 = 42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD16U, ValueType::I32, &bytes, 0x11, 0),
|
||||
Value::I32(value as i32)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i64store16() {}
|
||||
#[test]
|
||||
fn test_i64load8s() {
|
||||
let value: i8 = -42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I64LOAD8S, ValueType::I64, &bytes, 0x11, 0),
|
||||
Value::I64(value as i64)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_i64store32() {}
|
||||
#[test]
|
||||
fn test_i64load8u() {
|
||||
let value: u8 = 42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD8U, ValueType::I32, &bytes, 0x11, 0),
|
||||
Value::I32(value as i32)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_currentmemory() {}
|
||||
#[test]
|
||||
fn test_i64load16s() {
|
||||
let value: i16 = -42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I64LOAD8S, ValueType::I64, &bytes, 0x11, 0),
|
||||
Value::I64(value as i64)
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_growmemory() {}
|
||||
#[test]
|
||||
fn test_i64load16u() {
|
||||
let value: u16 = 42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD8U, ValueType::I32, &bytes, 0x11, 0),
|
||||
Value::I32(value as i32)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i64load32s() {
|
||||
let value: i32 = -42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I64LOAD8S, ValueType::I64, &bytes, 0x11, 0),
|
||||
Value::I64(value as i64)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i64load32u() {
|
||||
let value: u32 = 42;
|
||||
let bytes = value.to_le_bytes();
|
||||
assert_eq!(
|
||||
test_load(OpCode::I32LOAD8U, ValueType::I32, &bytes, 0x11, 0),
|
||||
Value::I32(value as i32)
|
||||
);
|
||||
}
|
||||
|
||||
fn test_store<'a>(
|
||||
arena: &'a Bump,
|
||||
module: &mut WasmModule<'a>,
|
||||
addr: u32,
|
||||
store_op: OpCode,
|
||||
offset: u32,
|
||||
value: Value,
|
||||
) -> Vec<'a, u8> {
|
||||
let is_debug_mode = false;
|
||||
let start_fn_name = "test";
|
||||
|
||||
module.memory = MemorySection::new(arena, MemorySection::PAGE_SIZE);
|
||||
|
||||
let signature = Signature {
|
||||
param_types: bumpalo::vec![in arena],
|
||||
ret_type: None,
|
||||
};
|
||||
|
||||
create_exported_function_no_locals(module, start_fn_name, signature, |buf| {
|
||||
buf.append_u8(OpCode::I32CONST as u8);
|
||||
buf.encode_u32(addr);
|
||||
match value {
|
||||
Value::I32(x) => {
|
||||
buf.append_u8(OpCode::I32CONST as u8);
|
||||
buf.encode_i32(x);
|
||||
}
|
||||
Value::I64(x) => {
|
||||
buf.append_u8(OpCode::I64CONST as u8);
|
||||
buf.encode_i64(x);
|
||||
}
|
||||
Value::F32(x) => {
|
||||
buf.append_u8(OpCode::F32CONST as u8);
|
||||
buf.encode_f32(x);
|
||||
}
|
||||
Value::F64(x) => {
|
||||
buf.append_u8(OpCode::F64CONST as u8);
|
||||
buf.encode_f64(x);
|
||||
}
|
||||
}
|
||||
buf.append_u8(store_op as u8);
|
||||
buf.encode_u32(0); // align
|
||||
buf.encode_u32(offset);
|
||||
buf.append_u8(OpCode::END as u8);
|
||||
});
|
||||
|
||||
let mut state =
|
||||
ExecutionState::for_module(arena, module, start_fn_name, is_debug_mode).unwrap();
|
||||
|
||||
while let Action::Continue = state.execute_next_instruction(module) {}
|
||||
|
||||
state.memory
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i32store() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::I32STORE;
|
||||
let offset = 1;
|
||||
let value = Value::I32(0x12345678);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(&memory[index..][..4], &[0x78, 0x56, 0x34, 0x12]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i64store() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::I64STORE;
|
||||
let offset = 1;
|
||||
let value = Value::I64(0x123456789abcdef0);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(
|
||||
&memory[index..][..8],
|
||||
&[0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f32store() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::F32STORE;
|
||||
let offset = 1;
|
||||
let inner: f32 = 1.23456;
|
||||
let value = Value::F32(inner);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(&memory[index..][..4], &inner.to_le_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f64store() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::F64STORE;
|
||||
let offset = 1;
|
||||
let inner: f64 = 1.23456;
|
||||
let value = Value::F64(inner);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(&memory[index..][..8], &inner.to_le_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i32store8() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::I32STORE8;
|
||||
let offset = 1;
|
||||
let value = Value::I32(0x12345678);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(&memory[index..][..4], &[0x78, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i32store16() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::I32STORE16;
|
||||
let offset = 1;
|
||||
let value = Value::I32(0x12345678);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(&memory[index..][..4], &[0x78, 0x56, 0x00, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i64store8() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::I64STORE8;
|
||||
let offset = 1;
|
||||
let value = Value::I64(0x123456789abcdef0);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(
|
||||
&memory[index..][..8],
|
||||
&[0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i64store16() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::I64STORE16;
|
||||
let offset = 1;
|
||||
let value = Value::I64(0x123456789abcdef0);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(
|
||||
&memory[index..][..8],
|
||||
&[0xf0, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i64store32() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let addr: u32 = 0x11;
|
||||
let store_op = OpCode::I64STORE32;
|
||||
let offset = 1;
|
||||
let value = Value::I64(0x123456789abcdef0);
|
||||
let memory = test_store(&arena, &mut module, addr, store_op, offset, value);
|
||||
|
||||
let index = (addr + offset) as usize;
|
||||
assert_eq!(
|
||||
&memory[index..][..8],
|
||||
&[0xf0, 0xde, 0xbc, 0x9a, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_currentmemory() {
|
||||
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);
|
||||
module.code.bytes.push(OpCode::CURRENTMEMORY as u8);
|
||||
|
||||
let mut state = ExecutionState::new(&arena, pages, pc, []);
|
||||
state.execute_next_instruction(&module);
|
||||
assert_eq!(state.value_stack.pop(), Value::I32(3))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_growmemory() {
|
||||
let arena = Bump::new();
|
||||
let mut module = WasmModule::new(&arena);
|
||||
|
||||
let existing_pages = 3;
|
||||
let grow_pages = 2;
|
||||
let pc = 0;
|
||||
module.memory = MemorySection::new(&arena, existing_pages * MemorySection::PAGE_SIZE);
|
||||
module.code.bytes.push(OpCode::I32CONST as u8);
|
||||
module.code.bytes.encode_i32(grow_pages);
|
||||
module.code.bytes.push(OpCode::GROWMEMORY as u8);
|
||||
|
||||
let mut state = ExecutionState::new(&arena, existing_pages, pc, []);
|
||||
state.execute_next_instruction(&module);
|
||||
state.execute_next_instruction(&module);
|
||||
assert_eq!(state.memory.len(), 5 * MemorySection::PAGE_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i32const() {
|
||||
|
@ -679,6 +679,33 @@ pub enum Value {
|
||||
F64(f64),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn unwrap_i32(&self) -> i32 {
|
||||
match self {
|
||||
Value::I32(x) => *x,
|
||||
_ => panic!("Expected I32 but found {:?}", self),
|
||||
}
|
||||
}
|
||||
pub fn unwrap_i64(&self) -> i64 {
|
||||
match self {
|
||||
Value::I64(x) => *x,
|
||||
_ => panic!("Expected I64 but found {:?}", self),
|
||||
}
|
||||
}
|
||||
pub fn unwrap_f32(&self) -> f32 {
|
||||
match self {
|
||||
Value::F32(x) => *x,
|
||||
_ => panic!("Expected F32 but found {:?}", self),
|
||||
}
|
||||
}
|
||||
pub fn unwrap_f64(&self) -> f64 {
|
||||
match self {
|
||||
Value::F64(x) => *x,
|
||||
_ => panic!("Expected F64 but found {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasm memory alignment for load/store instructions.
|
||||
/// Rust representation matches Wasm encoding.
|
||||
/// It's an error to specify alignment higher than the "natural" alignment of the instruction
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::io::Write;
|
||||
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
@ -789,6 +790,16 @@ impl<'a> MemorySection<'a> {
|
||||
};
|
||||
Ok(min_pages * MemorySection::PAGE_SIZE)
|
||||
}
|
||||
|
||||
pub fn max_bytes(&self) -> Result<Option<u32>, ParseError> {
|
||||
let mut cursor = 0;
|
||||
let memory_limits = Limits::parse((), &self.bytes, &mut cursor)?;
|
||||
let bytes = match memory_limits {
|
||||
Limits::Min(_) => None,
|
||||
Limits::MinMax(_, pages) => Some(pages * MemorySection::PAGE_SIZE),
|
||||
};
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
section_impl!(MemorySection, SectionId::Memory);
|
||||
@ -1490,6 +1501,29 @@ impl<'a> DataSection<'a> {
|
||||
segment.serialize(&mut self.bytes);
|
||||
index
|
||||
}
|
||||
|
||||
pub fn load_into(&self, memory: &mut [u8]) -> Result<(), String> {
|
||||
let mut cursor = 0;
|
||||
for _ in 0..self.count {
|
||||
let mode =
|
||||
DataMode::parse((), &self.bytes, &mut cursor).map_err(|e| format!("{:?}", e))?;
|
||||
let start = match mode {
|
||||
DataMode::Active {
|
||||
offset: ConstExpr::I32(addr),
|
||||
} => addr as usize,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let len32 = u32::parse((), &self.bytes, &mut cursor).map_err(|e| format!("{:?}", e))?;
|
||||
let len = len32 as usize;
|
||||
let mut target_slice = &mut memory[start..][..len];
|
||||
target_slice
|
||||
.write(&self.bytes[cursor..][..len])
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<&'a Bump> for DataSection<'a> {
|
||||
|
Loading…
Reference in New Issue
Block a user