diff --git a/src/instructions/interpreter.rs b/src/instructions/interpreter.rs index 9781d48..0c8a55f 100644 --- a/src/instructions/interpreter.rs +++ b/src/instructions/interpreter.rs @@ -3,7 +3,11 @@ use crate::instructions::{ wasm::{self, FunctionIndex, InterfaceType, InterfaceValue, TypedIndex}, Instruction, }; -use std::{cell::Cell, convert::TryFrom, marker::PhantomData}; +use std::{ + cell::Cell, + convert::{TryFrom, TryInto}, + marker::PhantomData, +}; struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory> where @@ -15,9 +19,9 @@ where invocation_inputs: &'invocation [InterfaceValue], stack: Stack, wasm_instance: &'instance Instance, - wasm_exports: PhantomData, - wasm_locals_or_imports: PhantomData, - wasm_memories: PhantomData, + _wasm_exports: PhantomData, + _wasm_locals_or_imports: PhantomData, + _wasm_memories: PhantomData, } type ExecutableInstruction = @@ -56,9 +60,9 @@ where invocation_inputs, stack: Stack::new(), wasm_instance, - wasm_exports: PhantomData, - wasm_locals_or_imports: PhantomData, - wasm_memories: PhantomData, + _wasm_exports: PhantomData, + _wasm_locals_or_imports: PhantomData, + _wasm_memories: PhantomData, }; for executable_instruction in self.iter() { @@ -274,6 +278,76 @@ where } }) } + Instruction::WriteUtf8 { allocator_name } => { + let allocator_name = (*allocator_name).to_owned(); + let instruction_name: String = instruction.into(); + + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + let instance = runtime.wasm_instance; + + match instance.export(&allocator_name) { + Some(allocator) => { + if allocator.inputs() != [InterfaceType::I32] || + allocator.outputs() != [InterfaceType::I32] { + return Err(format!( + "`{}` failed because the allocator `{}` has an invalid signature (expects [I32] -> [I32]).", + instruction_name, + allocator_name, + )) + } + + match runtime.wasm_instance.memory(0) { + Some(memory) => match runtime.stack.pop1() { + Some(string) => { + let memory_view = memory.view::(); + + let string: String = (&string).try_into()?; + let string_bytes = string.as_bytes(); + let string_length = (string_bytes.len() as i32) + .try_into() + .map_err(|error| format!("{}", error))?; + + match allocator.call(&[InterfaceValue::I32(string_length)]) { + Ok(outputs) => { + let string_pointer: i32 = (&outputs[0]).try_into()?; + + for (nth, byte) in string_bytes.iter().enumerate() { + memory_view[string_pointer as usize + nth].set(*byte); + } + + runtime.stack.push(InterfaceValue::I32(string_pointer)); + runtime.stack.push(InterfaceValue::I32(string_length)); + + Ok(()) + } + Err(_) => Err(format!( + "`{}` failed when calling the allocator `{}`.", + instruction_name, + allocator_name, + )) + } + } + None => Err(format!( + "`{}` cannot call the allocator `{}` because there is no enough data on the stack for the arguments (needs {}).", + instruction_name, + allocator_name, + 1 + )) + } + None => Err(format!( + "`{}` failed because there is no memory to write into.", + instruction_name + )) + } + } + None => Err(format!( + "`{}` failed because the exported function `{}` (the allocator) doesn't exist.", + instruction_name, + allocator_name + )) + } + }) + } _ => unimplemented!(), } }, @@ -396,6 +470,18 @@ mod tests { }, }, ); + hashmap.insert( + "alloc".into(), + Export { + inputs: vec![InterfaceType::I32], + outputs: vec![InterfaceType::I32], + function: |arguments: &[InterfaceValue]| { + let _size: i32 = (&arguments[0]).try_into().unwrap(); + + Ok(vec![InterfaceValue::I32(0)]) + }, + }, + ); hashmap }, @@ -417,7 +503,7 @@ mod tests { hashmap }, - memory: Memory::new(vec![]), + memory: Memory::new(vec![Cell::new(0); 128]), } } } @@ -850,4 +936,98 @@ mod tests { }, stack: [], ); + + test!( + test_interpreter_write_utf8 = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::WriteUtf8 { allocator_name: "alloc" }, + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + stack: [ + InterfaceValue::I32(0), + // ^^^^^^ pointer + InterfaceValue::I32(13), + // ^^^^^^^ length + ] + ); + + test!( + test_interpreter_write_utf8__roundtrip_with_read_utf8 = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::WriteUtf8 { allocator_name: "alloc" }, + Instruction::ReadUtf8, + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + stack: [InterfaceValue::String("Hello, World!".into())], + ); + + test!( + test_interpreter_write_utf8__allocator_does_not_exist = + instructions: [Instruction::WriteUtf8 { allocator_name: "alloc" }], + invocation_inputs: [], + instance: Instance { ..Default::default() }, + error: r#"`write-utf8 "alloc"` failed because the exported function `alloc` (the allocator) doesn't exist."#, + ); + + test!( + test_interpreter_write_utf8__stack_is_too_small = + instructions: [ + Instruction::WriteUtf8 { allocator_name: "alloc" } + // ^^^^^ `alloc` expects 1 value on the stack, none is present + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + error: r#"`write-utf8 "alloc"` cannot call the allocator `alloc` because there is no enough data on the stack for the arguments (needs 1)."#, + ); + + test!( + test_interpreter_write_utf8__failure_when_calling_the_allocator = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::WriteUtf8 { allocator_name: "alloc-fail" } + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: { + let mut instance = Instance::new(); + instance.exports.insert( + "alloc-fail".into(), + Export { + inputs: vec![InterfaceType::I32], + outputs: vec![InterfaceType::I32], + function: |_| Err(()), + // ^^^^^^^ function fails + }, + ); + + instance + }, + error: r#"`write-utf8 "alloc-fail"` failed when calling the allocator `alloc-fail`."#, + ); + + test!( + test_interpreter_write_utf8__invalid_allocator_signature = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::WriteUtf8 { allocator_name: "alloc-fail" } + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: { + let mut instance = Instance::new(); + instance.exports.insert( + "alloc-fail".into(), + Export { + inputs: vec![InterfaceType::I32, InterfaceType::I32], + outputs: vec![], + function: |_| Err(()), + }, + ); + + instance + }, + error: r#"`write-utf8 "alloc-fail"` failed because the allocator `alloc-fail` has an invalid signature (expects [I32] -> [I32])."#, + ); } diff --git a/src/instructions/wasm.rs b/src/instructions/wasm.rs index aa3090f..014b95c 100644 --- a/src/instructions/wasm.rs +++ b/src/instructions/wasm.rs @@ -49,8 +49,8 @@ macro_rules! from_x_for_interface_value { type Error = &'static str; fn try_from(w: &InterfaceValue) -> Result { - match *w { - InterfaceValue::$value_variant(n) => Ok(n), + match w { + InterfaceValue::$value_variant(n) => Ok(n.clone()), _ => Err("Invalid cast."), } } @@ -58,6 +58,7 @@ macro_rules! from_x_for_interface_value { }; } +from_x_for_interface_value!(String, String); from_x_for_interface_value!(i32, I32); from_x_for_interface_value!(i64, I64); from_x_for_interface_value!(f32, F32);