diff --git a/src/decoders/binary.rs b/src/decoders/binary.rs index 6412cba..befb3dd 100644 --- a/src/decoders/binary.rs +++ b/src/decoders/binary.rs @@ -187,11 +187,11 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( 0x03 => (input, Instruction::MemoryToString), 0x04 => { - consume!((input, argument_0) = string(input)?); + consume!((input, argument_0) = uleb(input)?); ( input, - Instruction::WriteUtf8 { - allocator_name: argument_0, + Instruction::StringToMemory { + allocator_index: argument_0 as u32, }, ) } @@ -642,7 +642,7 @@ mod tests { 0x01, 0x01, // Call { function_index: 1 } 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" } 0x03, // MemoryToString - 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } + 0x04, 0x01, // StringToMemory { allocator_index: 1 } 0x07, // I32ToS8 0x08, // I32ToS8X 0x09, // I32ToU8 @@ -691,9 +691,7 @@ mod tests { Instruction::Call { function_index: 1 }, Instruction::CallExport { export_name: "abc" }, Instruction::MemoryToString, - Instruction::WriteUtf8 { - allocator_name: "abc", - }, + Instruction::StringToMemory { allocator_index: 1 }, Instruction::I32ToS8, Instruction::I32ToS8X, Instruction::I32ToU8, diff --git a/src/decoders/wat.rs b/src/decoders/wat.rs index 8a25064..3cdf5c1 100644 --- a/src/decoders/wat.rs +++ b/src/decoders/wat.rs @@ -30,7 +30,7 @@ mod keyword { custom_keyword!(call); custom_keyword!(call_export = "call-export"); custom_keyword!(memory_to_string = "memory-to-string"); - custom_keyword!(write_utf8 = "write-utf8"); + custom_keyword!(string_to_memory = "string-to-memory"); custom_keyword!(i32_to_s8 = "i32-to-s8"); custom_keyword!(i32_to_s8x = "i32-to-s8x"); custom_keyword!(i32_to_u8 = "i32-to-u8"); @@ -165,11 +165,11 @@ impl<'a> Parse<'a> for Instruction<'a> { parser.parse::()?; Ok(Instruction::MemoryToString) - } else if lookahead.peek::() { - parser.parse::()?; + } else if lookahead.peek::() { + parser.parse::()?; - Ok(Instruction::WriteUtf8 { - allocator_name: parser.parse()?, + Ok(Instruction::StringToMemory { + allocator_index: parser.parse()?, }) } else if lookahead.peek::() { parser.parse::()?; @@ -676,7 +676,7 @@ mod tests { "call 7", r#"call-export "foo""#, "memory-to-string", - r#"write-utf8 "foo""#, + "string-to-memory 42", "i32-to-s8", "i32-to-s8x", "i32-to-u8", @@ -722,8 +722,8 @@ mod tests { Instruction::Call { function_index: 7 }, Instruction::CallExport { export_name: "foo" }, Instruction::MemoryToString, - Instruction::WriteUtf8 { - allocator_name: "foo", + Instruction::StringToMemory { + allocator_index: 42, }, Instruction::I32ToS8, Instruction::I32ToS8X, diff --git a/src/encoders/binary.rs b/src/encoders/binary.rs index 1d67625..4c0fbe2 100644 --- a/src/encoders/binary.rs +++ b/src/encoders/binary.rs @@ -267,9 +267,9 @@ where Instruction::MemoryToString => 0x03_u8.to_bytes(writer)?, - Instruction::WriteUtf8 { allocator_name } => { + Instruction::StringToMemory { allocator_index } => { 0x04_u8.to_bytes(writer)?; - allocator_name.to_bytes(writer)?; + (*allocator_index as u64).to_bytes(writer)?; } Instruction::I32ToS8 => 0x07_u8.to_bytes(writer)?, @@ -557,9 +557,7 @@ mod tests { Instruction::Call { function_index: 1 }, Instruction::CallExport { export_name: "abc" }, Instruction::MemoryToString, - Instruction::WriteUtf8 { - allocator_name: "abc", - }, + Instruction::StringToMemory { allocator_index: 1 }, Instruction::I32ToS8, Instruction::I32ToS8X, Instruction::I32ToU8, @@ -606,7 +604,7 @@ mod tests { 0x01, 0x01, // Call { function_index: 1 } 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" } 0x03, // MemoryToString - 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } + 0x04, 0x01, // StringToMemory { allocator_index: 1 } 0x07, // I32ToS8 0x08, // I32ToS8X 0x09, // I32ToU8 diff --git a/src/encoders/wat.rs b/src/encoders/wat.rs index c0cff22..4f0c434 100644 --- a/src/encoders/wat.rs +++ b/src/encoders/wat.rs @@ -87,8 +87,8 @@ impl<'input> ToString for &Instruction<'input> { Instruction::Call { function_index } => format!("call {}", function_index), Instruction::CallExport { export_name } => format!(r#"call-export "{}""#, export_name), Instruction::MemoryToString => "memory-to-string".into(), - Instruction::WriteUtf8 { allocator_name } => { - format!(r#"write-utf8 "{}""#, allocator_name) + Instruction::StringToMemory { allocator_index } => { + format!(r#"string-to-memory {}"#, allocator_index) } Instruction::I32ToS8 => "i32-to-s8".into(), Instruction::I32ToS8X => "i32-to-s8x".into(), @@ -364,8 +364,8 @@ mod tests { (&Instruction::Call { function_index: 7 }).to_string(), (&Instruction::CallExport { export_name: "foo" }).to_string(), (&Instruction::MemoryToString).to_string(), - (&Instruction::WriteUtf8 { - allocator_name: "foo", + (&Instruction::StringToMemory { + allocator_index: 42, }) .to_string(), (&Instruction::I32ToS8).to_string(), @@ -413,7 +413,7 @@ mod tests { "call 7", r#"call-export "foo""#, "memory-to-string", - r#"write-utf8 "foo""#, + "string-to-memory 42", "i32-to-s8", "i32-to-s8x", "i32-to-u8", diff --git a/src/interpreter/instruction.rs b/src/interpreter/instruction.rs index 9cccacd..70818e6 100644 --- a/src/interpreter/instruction.rs +++ b/src/interpreter/instruction.rs @@ -24,10 +24,10 @@ pub enum Instruction<'input> { /// The `memory-to-string` instruction. MemoryToString, - /// The `write-utf8` instruction. - WriteUtf8 { - /// The allocator function name. - allocator_name: &'input str, + /// The `string-to-memory` instruction. + StringToMemory { + /// The allocator function index. + allocator_index: u32, }, /// The `i32-to-s8,` instruction. diff --git a/src/interpreter/instructions/mod.rs b/src/interpreter/instructions/mod.rs index 3bf7ed4..e8912a5 100644 --- a/src/interpreter/instructions/mod.rs +++ b/src/interpreter/instructions/mod.rs @@ -3,14 +3,14 @@ mod call; mod call_export; mod lowering_lifting; mod memory_to_string; -mod write_utf8; +mod string_to_memory; pub(crate) use argument_get::argument_get; pub(crate) use call::call; pub(crate) use call_export::call_export; pub(crate) use lowering_lifting::*; pub(crate) use memory_to_string::memory_to_string; -pub(crate) use write_utf8::write_utf8; +pub(crate) use string_to_memory::string_to_memory; #[cfg(test)] pub(crate) mod tests { @@ -133,23 +133,12 @@ pub(crate) 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 }, locals_or_imports: { let mut hashmap = HashMap::new(); + // sum hashmap.insert( 42, LocalImport { @@ -163,6 +152,19 @@ pub(crate) mod tests { }, }, ); + // string allocator + hashmap.insert( + 43, + LocalImport { + 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 }, diff --git a/src/interpreter/instructions/string_to_memory.rs b/src/interpreter/instructions/string_to_memory.rs new file mode 100644 index 0000000..57414d0 --- /dev/null +++ b/src/interpreter/instructions/string_to_memory.rs @@ -0,0 +1,169 @@ +use crate::interpreter::wasm::{ + structures::{FunctionIndex, TypedIndex}, + values::{InterfaceType, InterfaceValue}, +}; +use std::convert::TryInto; + +executable_instruction!( + string_to_memory(allocator_index: u32, instruction_name: String) -> _ { + move |runtime| -> _ { + let instance = &mut runtime.wasm_instance; + let index = FunctionIndex::new(allocator_index as usize); + + let allocator = instance.local_or_import(index).ok_or_else(|| { + format!( + "`{}` failed because the function `{}` (the allocator) doesn't exist.", + instruction_name, + allocator_index + ) + })?; + + 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_index, + )); + } + + let string = runtime.stack.pop1().ok_or_else(|| { + format!( + "`{}` cannot call the allocator `{}` because there is not enough data on the stack for the arguments (needs {}).", + instruction_name, + allocator_index, + 1 + ) + })?; + + 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))?; + + let outputs = allocator.call(&[InterfaceValue::I32(string_length)]).map_err(|_| format!( + "`{}` failed when calling the allocator `{}`.", + instruction_name, + allocator_index, + ))?; + + let string_pointer: i32 = (&outputs[0]).try_into()?; + + let memory_view = instance + .memory(0) + .ok_or_else(|| { + format!( + "`{}` failed because there is no memory to write into.", + instruction_name + ) + })? + .view(); + + 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(()) + } + } +); + +#[cfg(test)] +mod tests { + test_executable_instruction!( + test_string_to_memory = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringToMemory { allocator_index: 43 }, + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + stack: [ + InterfaceValue::I32(0), + // ^^^^^^ pointer + InterfaceValue::I32(13), + // ^^^^^^^ length + ] + ); + + test_executable_instruction!( + test_string_to_memory__roundtrip_with_memory_to_string = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringToMemory { allocator_index: 43 }, + Instruction::MemoryToString, + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + stack: [InterfaceValue::String("Hello, World!".into())], + ); + + test_executable_instruction!( + test_string_to_memory__allocator_does_not_exist = + instructions: [Instruction::StringToMemory { allocator_index: 43 }], + invocation_inputs: [], + instance: Instance { ..Default::default() }, + error: r#"`string-to-memory 43` failed because the function `43` (the allocator) doesn't exist."#, + ); + + test_executable_instruction!( + test_string_to_memory__stack_is_too_small = + instructions: [ + Instruction::StringToMemory { allocator_index: 43 } + // ^^ `43` expects 1 value on the stack, none is present + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: Instance::new(), + error: r#"`string-to-memory 43` cannot call the allocator `43` because there is not enough data on the stack for the arguments (needs 1)."#, + ); + + test_executable_instruction!( + test_string_to_memory__failure_when_calling_the_allocator = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringToMemory { allocator_index: 153 } + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: { + let mut instance = Instance::new(); + instance.locals_or_imports.insert( + 153, + LocalImport { + inputs: vec![InterfaceType::I32], + outputs: vec![InterfaceType::I32], + function: |_| Err(()), + // ^^^^^^^ function fails + }, + ); + + instance + }, + error: r#"`string-to-memory 153` failed when calling the allocator `153`."#, + ); + + test_executable_instruction!( + test_string_to_memory__invalid_allocator_signature = + instructions: [ + Instruction::ArgumentGet { index: 0 }, + Instruction::StringToMemory { allocator_index: 153 } + ], + invocation_inputs: [InterfaceValue::String("Hello, World!".into())], + instance: { + let mut instance = Instance::new(); + instance.locals_or_imports.insert( + 153, + LocalImport { + inputs: vec![InterfaceType::I32, InterfaceType::I32], + outputs: vec![], + function: |_| Err(()), + }, + ); + + instance + }, + error: r#"`string-to-memory 153` failed because the allocator `153` has an invalid signature (expects [I32] -> [I32])."#, + ); +} diff --git a/src/interpreter/instructions/write_utf8.rs b/src/interpreter/instructions/write_utf8.rs deleted file mode 100644 index a3f53d5..0000000 --- a/src/interpreter/instructions/write_utf8.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::interpreter::wasm::values::{InterfaceType, InterfaceValue}; -use std::convert::TryInto; - -executable_instruction!( - write_utf8(allocator_name: String, instruction_name: String) -> _ { - move |runtime| -> _ { - let instance = &mut 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 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 not 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 - )) - } - } - } -); - -#[cfg(test)] -mod tests { - test_executable_instruction!( - test_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_executable_instruction!( - test_write_utf8__roundtrip_with_memory_to_string = - instructions: [ - Instruction::ArgumentGet { index: 0 }, - Instruction::WriteUtf8 { allocator_name: "alloc" }, - Instruction::MemoryToString, - ], - invocation_inputs: [InterfaceValue::String("Hello, World!".into())], - instance: Instance::new(), - stack: [InterfaceValue::String("Hello, World!".into())], - ); - - test_executable_instruction!( - test_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_executable_instruction!( - test_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 not enough data on the stack for the arguments (needs 1)."#, - ); - - test_executable_instruction!( - test_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_executable_instruction!( - test_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/interpreter/mod.rs b/src/interpreter/mod.rs index 1dfd5af..29e1194 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -203,8 +203,8 @@ where instructions::call_export((*export_name).to_owned(), instruction_name) } Instruction::MemoryToString => instructions::memory_to_string(instruction_name), - Instruction::WriteUtf8 { allocator_name } => { - instructions::write_utf8((*allocator_name).to_owned(), instruction_name) + Instruction::StringToMemory { allocator_index } => { + instructions::string_to_memory(*allocator_index, instruction_name) } Instruction::I32ToS8 => instructions::i32_to_s8(),