From 39d491e4658c6c659697857e0f0cd22555219371 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 Mar 2020 15:06:35 +0100 Subject: [PATCH 1/9] feat(interface-types) `read-utf8` is renamed `memory-to-string`. --- lib/interface-types/src/decoders/binary.rs | 6 ++--- lib/interface-types/src/decoders/wat.rs | 12 ++++----- lib/interface-types/src/encoders/binary.rs | 6 ++--- lib/interface-types/src/encoders/wat.rs | 6 ++--- .../src/interpreter/instruction.rs | 4 +-- .../{read_utf8.rs => memory_to_string.rs} | 26 +++++++++---------- .../src/interpreter/instructions/mod.rs | 4 +-- .../interpreter/instructions/write_utf8.rs | 4 +-- lib/interface-types/src/interpreter/mod.rs | 4 +-- 9 files changed, 36 insertions(+), 36 deletions(-) rename lib/interface-types/src/interpreter/instructions/{read_utf8.rs => memory_to_string.rs} (82%) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 571548c9fb3..6412cbaa55b 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -184,7 +184,7 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( ) } - 0x03 => (input, Instruction::ReadUtf8), + 0x03 => (input, Instruction::MemoryToString), 0x04 => { consume!((input, argument_0) = string(input)?); @@ -641,7 +641,7 @@ mod tests { 0x00, 0x01, // ArgumentGet { index: 1 } 0x01, 0x01, // Call { function_index: 1 } 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" } - 0x03, // ReadUtf8 + 0x03, // MemoryToString 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } 0x07, // I32ToS8 0x08, // I32ToS8X @@ -690,7 +690,7 @@ mod tests { Instruction::ArgumentGet { index: 1 }, Instruction::Call { function_index: 1 }, Instruction::CallExport { export_name: "abc" }, - Instruction::ReadUtf8, + Instruction::MemoryToString, Instruction::WriteUtf8 { allocator_name: "abc", }, diff --git a/lib/interface-types/src/decoders/wat.rs b/lib/interface-types/src/decoders/wat.rs index 2342017ed5b..8a25064f2fb 100644 --- a/lib/interface-types/src/decoders/wat.rs +++ b/lib/interface-types/src/decoders/wat.rs @@ -29,7 +29,7 @@ mod keyword { custom_keyword!(argument_get = "arg.get"); custom_keyword!(call); custom_keyword!(call_export = "call-export"); - custom_keyword!(read_utf8 = "read-utf8"); + custom_keyword!(memory_to_string = "memory-to-string"); custom_keyword!(write_utf8 = "write-utf8"); custom_keyword!(i32_to_s8 = "i32-to-s8"); custom_keyword!(i32_to_s8x = "i32-to-s8x"); @@ -161,10 +161,10 @@ impl<'a> Parse<'a> for Instruction<'a> { Ok(Instruction::CallExport { export_name: parser.parse()?, }) - } else if lookahead.peek::() { - parser.parse::()?; + } else if lookahead.peek::() { + parser.parse::()?; - Ok(Instruction::ReadUtf8) + Ok(Instruction::MemoryToString) } else if lookahead.peek::() { parser.parse::()?; @@ -675,7 +675,7 @@ mod tests { "arg.get 7", "call 7", r#"call-export "foo""#, - "read-utf8", + "memory-to-string", r#"write-utf8 "foo""#, "i32-to-s8", "i32-to-s8x", @@ -721,7 +721,7 @@ mod tests { Instruction::ArgumentGet { index: 7 }, Instruction::Call { function_index: 7 }, Instruction::CallExport { export_name: "foo" }, - Instruction::ReadUtf8, + Instruction::MemoryToString, Instruction::WriteUtf8 { allocator_name: "foo", }, diff --git a/lib/interface-types/src/encoders/binary.rs b/lib/interface-types/src/encoders/binary.rs index 7486d5eb402..1d6762553bb 100644 --- a/lib/interface-types/src/encoders/binary.rs +++ b/lib/interface-types/src/encoders/binary.rs @@ -265,7 +265,7 @@ where export_name.to_bytes(writer)?; } - Instruction::ReadUtf8 => 0x03_u8.to_bytes(writer)?, + Instruction::MemoryToString => 0x03_u8.to_bytes(writer)?, Instruction::WriteUtf8 { allocator_name } => { 0x04_u8.to_bytes(writer)?; @@ -556,7 +556,7 @@ mod tests { Instruction::ArgumentGet { index: 1 }, Instruction::Call { function_index: 1 }, Instruction::CallExport { export_name: "abc" }, - Instruction::ReadUtf8, + Instruction::MemoryToString, Instruction::WriteUtf8 { allocator_name: "abc", }, @@ -605,7 +605,7 @@ mod tests { 0x00, 0x01, // ArgumentGet { index: 1 } 0x01, 0x01, // Call { function_index: 1 } 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" } - 0x03, // ReadUtf8 + 0x03, // MemoryToString 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } 0x07, // I32ToS8 0x08, // I32ToS8X diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index 1e883b764ba..c0cff22994f 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -86,7 +86,7 @@ impl<'input> ToString for &Instruction<'input> { Instruction::ArgumentGet { index } => format!("arg.get {}", index), Instruction::Call { function_index } => format!("call {}", function_index), Instruction::CallExport { export_name } => format!(r#"call-export "{}""#, export_name), - Instruction::ReadUtf8 => "read-utf8".into(), + Instruction::MemoryToString => "memory-to-string".into(), Instruction::WriteUtf8 { allocator_name } => { format!(r#"write-utf8 "{}""#, allocator_name) } @@ -363,7 +363,7 @@ mod tests { (&Instruction::ArgumentGet { index: 7 }).to_string(), (&Instruction::Call { function_index: 7 }).to_string(), (&Instruction::CallExport { export_name: "foo" }).to_string(), - (&Instruction::ReadUtf8).to_string(), + (&Instruction::MemoryToString).to_string(), (&Instruction::WriteUtf8 { allocator_name: "foo", }) @@ -412,7 +412,7 @@ mod tests { "arg.get 7", "call 7", r#"call-export "foo""#, - "read-utf8", + "memory-to-string", r#"write-utf8 "foo""#, "i32-to-s8", "i32-to-s8x", diff --git a/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs index feca1cc7e5f..9cccacd0532 100644 --- a/lib/interface-types/src/interpreter/instruction.rs +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -21,8 +21,8 @@ pub enum Instruction<'input> { export_name: &'input str, }, - /// The `read-utf8` instruction. - ReadUtf8, + /// The `memory-to-string` instruction. + MemoryToString, /// The `write-utf8` instruction. WriteUtf8 { diff --git a/lib/interface-types/src/interpreter/instructions/read_utf8.rs b/lib/interface-types/src/interpreter/instructions/memory_to_string.rs similarity index 82% rename from lib/interface-types/src/interpreter/instructions/read_utf8.rs rename to lib/interface-types/src/interpreter/instructions/memory_to_string.rs index a06bc56309f..678d08d7474 100644 --- a/lib/interface-types/src/interpreter/instructions/read_utf8.rs +++ b/lib/interface-types/src/interpreter/instructions/memory_to_string.rs @@ -2,7 +2,7 @@ use crate::interpreter::wasm::values::InterfaceValue; use std::{cell::Cell, convert::TryFrom}; executable_instruction!( - read_utf8(instruction_name: String) -> _ { + memory_to_string(instruction_name: String) -> _ { move |runtime| -> _ { match runtime.stack.pop(2) { Some(inputs) => match runtime.wasm_instance.memory(0) { @@ -55,11 +55,11 @@ executable_instruction!( #[cfg(test)] mod tests { test_executable_instruction!( - test_read_utf8 = + test_memory_to_string = instructions: [ Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 0 }, - Instruction::ReadUtf8, + Instruction::MemoryToString, ], invocation_inputs: [ InterfaceValue::I32(13), @@ -75,11 +75,11 @@ mod tests { ); test_executable_instruction!( - test_read_utf8__read_out_of_memory = + test_memory_to_string__read_out_of_memory = instructions: [ Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 0 }, - Instruction::ReadUtf8, + Instruction::MemoryToString, ], invocation_inputs: [ InterfaceValue::I32(13), @@ -91,15 +91,15 @@ mod tests { memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), ..Default::default() }, - error: r#"`read-utf8` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#, + error: r#"`memory-to-string` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#, ); test_executable_instruction!( - test_read_utf8__invalid_encoding = + test_memory_to_string__invalid_encoding = instructions: [ Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 0 }, - Instruction::ReadUtf8, + Instruction::MemoryToString, ], invocation_inputs: [ InterfaceValue::I32(4), @@ -111,21 +111,21 @@ mod tests { memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::>>()), ..Default::default() }, - error: r#"`read-utf8` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#, + error: r#"`memory-to-string` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#, ); test_executable_instruction!( - test_read_utf8__stack_is_too_small = + test_memory_to_string__stack_is_too_small = instructions: [ Instruction::ArgumentGet { index: 0 }, - Instruction::ReadUtf8, - // ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present. + Instruction::MemoryToString, + // ^^^^^^^^^^^^^^ `memory-to-string` expects 2 values on the stack, only one is present. ], invocation_inputs: [ InterfaceValue::I32(13), InterfaceValue::I32(0), ], instance: Instance::new(), - error: r#"`read-utf8` failed because there is not enough data on the stack (needs 2)."#, + error: r#"`memory-to-string` failed because there is not enough data on the stack (needs 2)."#, ); } diff --git a/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs index 28aa5209bb8..3bf7ed4a880 100644 --- a/lib/interface-types/src/interpreter/instructions/mod.rs +++ b/lib/interface-types/src/interpreter/instructions/mod.rs @@ -2,14 +2,14 @@ mod argument_get; mod call; mod call_export; mod lowering_lifting; -mod read_utf8; +mod memory_to_string; mod write_utf8; 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 read_utf8::read_utf8; +pub(crate) use memory_to_string::memory_to_string; pub(crate) use write_utf8::write_utf8; #[cfg(test)] diff --git a/lib/interface-types/src/interpreter/instructions/write_utf8.rs b/lib/interface-types/src/interpreter/instructions/write_utf8.rs index a1e21509f14..a3f53d5501a 100644 --- a/lib/interface-types/src/interpreter/instructions/write_utf8.rs +++ b/lib/interface-types/src/interpreter/instructions/write_utf8.rs @@ -90,11 +90,11 @@ mod tests { ); test_executable_instruction!( - test_write_utf8__roundtrip_with_read_utf8 = + test_write_utf8__roundtrip_with_memory_to_string = instructions: [ Instruction::ArgumentGet { index: 0 }, Instruction::WriteUtf8 { allocator_name: "alloc" }, - Instruction::ReadUtf8, + Instruction::MemoryToString, ], invocation_inputs: [InterfaceValue::String("Hello, World!".into())], instance: Instance::new(), diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index 1cfe66531b6..1dfd5af7d39 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -202,7 +202,7 @@ where Instruction::CallExport { export_name } => { instructions::call_export((*export_name).to_owned(), instruction_name) } - Instruction::ReadUtf8 => instructions::read_utf8(instruction_name), + Instruction::MemoryToString => instructions::memory_to_string(instruction_name), Instruction::WriteUtf8 { allocator_name } => { instructions::write_utf8((*allocator_name).to_owned(), instruction_name) } @@ -265,7 +265,7 @@ mod tests { Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 }, Instruction::CallExport { export_name: "foo" }, - Instruction::ReadUtf8, + Instruction::MemoryToString, Instruction::Call { function_index: 7 }, ]; let interpreter: Interpreter<(), (), (), (), EmptyMemoryView> = From 1b0bda82dd0d07c073c9b839f76a4f35cfc4dfa0 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 10:25:58 +0100 Subject: [PATCH 2/9] feat(interface-types) Rename `write-utf8` to `string-to-memory`. --- lib/interface-types/src/decoders/binary.rs | 12 +- lib/interface-types/src/decoders/wat.rs | 16 +- lib/interface-types/src/encoders/binary.rs | 10 +- lib/interface-types/src/encoders/wat.rs | 10 +- .../src/interpreter/instruction.rs | 8 +- .../src/interpreter/instructions/mod.rs | 30 ++-- .../instructions/string_to_memory.rs | 169 ++++++++++++++++++ .../interpreter/instructions/write_utf8.rs | 169 ------------------ lib/interface-types/src/interpreter/mod.rs | 4 +- 9 files changed, 213 insertions(+), 215 deletions(-) create mode 100644 lib/interface-types/src/interpreter/instructions/string_to_memory.rs delete mode 100644 lib/interface-types/src/interpreter/instructions/write_utf8.rs diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 6412cbaa55b..befb3dd7cb4 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/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/lib/interface-types/src/decoders/wat.rs b/lib/interface-types/src/decoders/wat.rs index 8a25064f2fb..3cdf5c1fc50 100644 --- a/lib/interface-types/src/decoders/wat.rs +++ b/lib/interface-types/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/lib/interface-types/src/encoders/binary.rs b/lib/interface-types/src/encoders/binary.rs index 1d6762553bb..4c0fbe2ca6f 100644 --- a/lib/interface-types/src/encoders/binary.rs +++ b/lib/interface-types/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/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index c0cff22994f..4f0c4341113 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/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/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs index 9cccacd0532..70818e6c49e 100644 --- a/lib/interface-types/src/interpreter/instruction.rs +++ b/lib/interface-types/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/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs index 3bf7ed4a880..e8912a51739 100644 --- a/lib/interface-types/src/interpreter/instructions/mod.rs +++ b/lib/interface-types/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/lib/interface-types/src/interpreter/instructions/string_to_memory.rs b/lib/interface-types/src/interpreter/instructions/string_to_memory.rs new file mode 100644 index 00000000000..57414d03f5a --- /dev/null +++ b/lib/interface-types/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/lib/interface-types/src/interpreter/instructions/write_utf8.rs b/lib/interface-types/src/interpreter/instructions/write_utf8.rs deleted file mode 100644 index a3f53d5501a..00000000000 --- a/lib/interface-types/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/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index 1dfd5af7d39..29e1194a1c7 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/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(), From e9de8f999976dc359fdf5017abac2e7ae8b96ad1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 10:27:17 +0100 Subject: [PATCH 3/9] feat(interface-types) `write-utf8` is renamed `string-to-memory`. --- lib/runtime-core/src/typed_func.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 0b29d54eadb..6ae883b1a25 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -323,12 +323,17 @@ impl<'a> DynamicFunc<'a> { .iter() .enumerate() .map(|(i, t)| { - let i = i + 1; // skip vmctx + let i = i as isize + 1; // skip vmctx + match *t { - Type::I32 => Value::I32(*args.offset(i as _) as i32), - Type::I64 => Value::I64(*args.offset(i as _) as i64), - Type::F32 => Value::F32(f32::from_bits(*args.offset(i as _) as u32)), - Type::F64 => Value::F64(f64::from_bits(*args.offset(i as _) as u64)), + Type::I32 => Value::I32(*args.offset(i) as i32), + Type::I64 => Value::I64(*args.offset(i) as i64), + Type::F32 => { + eprintln!("{:?}", 3.0f32.to_le_bytes()); + eprintln!("{:#064x}", *args.offset(i)); + Value::F32(f32::from_bits(*args.offset(i) as u32)) + } + Type::F64 => Value::F64(f64::from_bits(*args.offset(i) as u64)), Type::V128 => { todo!("enter_host_polymorphic: 128-bit types are not supported") } @@ -336,6 +341,7 @@ impl<'a> DynamicFunc<'a> { }) .collect(); let rets = (ctx.func)(vmctx, &args); + if rets.len() == 0 { 0 } else if rets.len() == 1 { From 52312ff0adb6e97624121d41295fc600fff5caf3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 10:37:09 +0100 Subject: [PATCH 4/9] feat(interface-types) Remove the `'input` lifetime on `Instruction`. --- lib/interface-types/src/ast.rs | 6 +++--- lib/interface-types/src/decoders/wat.rs | 6 +++--- lib/interface-types/src/encoders/binary.rs | 4 ++-- lib/interface-types/src/encoders/wat.rs | 4 ++-- lib/interface-types/src/interpreter/instruction.rs | 2 +- lib/interface-types/src/interpreter/mod.rs | 3 +-- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 7b86c64ed3c..21b76020d66 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -89,12 +89,12 @@ pub struct Export<'input> { /// Represents an adapter. #[derive(PartialEq, Debug)] -pub struct Adapter<'input> { +pub struct Adapter { /// The adapter function type. pub function_type: u32, /// The instructions. - pub instructions: Vec>, + pub instructions: Vec, } /// Represents an implementation. @@ -137,7 +137,7 @@ pub struct Interfaces<'input> { pub imports: Vec>, /// All the adapters. - pub adapters: Vec>, + pub adapters: Vec, /// All the exported functions. pub exports: Vec>, diff --git a/lib/interface-types/src/decoders/wat.rs b/lib/interface-types/src/decoders/wat.rs index 069db8dc545..809b5e5a69d 100644 --- a/lib/interface-types/src/decoders/wat.rs +++ b/lib/interface-types/src/decoders/wat.rs @@ -137,7 +137,7 @@ impl Parse<'_> for InterfaceType { } } -impl<'a> Parse<'a> for Instruction<'a> { +impl<'a> Parse<'a> for Instruction { #[allow(clippy::cognitive_complexity)] fn parse(parser: Parser<'a>) -> Result { let mut lookahead = parser.lookahead1(); @@ -392,7 +392,7 @@ impl Parse<'_> for FunctionType { enum Interface<'a> { Type(Type), Import(Import<'a>), - Adapter(Adapter<'a>), + Adapter(Adapter), Export(Export<'a>), Implementation(Implementation), } @@ -520,7 +520,7 @@ impl<'a> Parse<'a> for Implementation { } } -impl<'a> Parse<'a> for Adapter<'a> { +impl<'a> Parse<'a> for Adapter { fn parse(parser: Parser<'a>) -> Result { parser.parse::()?; diff --git a/lib/interface-types/src/encoders/binary.rs b/lib/interface-types/src/encoders/binary.rs index b73d527bee8..cd322232d89 100644 --- a/lib/interface-types/src/encoders/binary.rs +++ b/lib/interface-types/src/encoders/binary.rs @@ -162,7 +162,7 @@ where /// Encode an `Adapter` into bytes. /// /// Decoder is in `decoders::binary::adapters`. -impl ToBytes for Adapter<'_> +impl ToBytes for Adapter where W: Write, { @@ -244,7 +244,7 @@ where /// Encode an `Instruction` into bytes. /// /// Decoder is `decoders::binary::instruction`. -impl ToBytes for Instruction<'_> +impl ToBytes for Instruction where W: Write, { diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index ef5a0919291..74a9d1a15b5 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -80,7 +80,7 @@ impl ToString for &InterfaceType { } /// Encode an `Instruction` into a string. -impl<'input> ToString for &Instruction<'input> { +impl ToString for &Instruction { fn to_string(&self) -> String { match self { Instruction::ArgumentGet { index } => format!("arg.get {}", index), @@ -194,7 +194,7 @@ impl<'input> ToString for &Import<'input> { } /// Encode an `Adapter` into a string. -impl<'input> ToString for &Adapter<'input> { +impl ToString for &Adapter { fn to_string(&self) -> String { format!( r#"(@interface func (type {function_type}){instructions})"#, diff --git a/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs index 9f7cc399e36..109f269b808 100644 --- a/lib/interface-types/src/interpreter/instruction.rs +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -2,7 +2,7 @@ /// Represents all the possible WIT instructions. #[derive(PartialEq, Debug)] -pub enum Instruction<'input> { +pub enum Instruction { /// The `arg.get` instruction. ArgumentGet { /// The argument index. diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index 2cf6b21ddb7..bdc939fe22e 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -174,8 +174,7 @@ where } /// Transforms a `Vec` into an `Interpreter`. -impl<'binary_input, Instance, Export, LocalImport, Memory, MemoryView> - TryFrom<&Vec>> +impl TryFrom<&Vec> for Interpreter where Export: wasm::structures::Export, From c1e40f165e57d41e64dfdd6ee5bc843cc891f1a2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 15:58:27 +0100 Subject: [PATCH 5/9] fix: Revert a file that wasn't a commit candidate. --- lib/runtime-core/src/typed_func.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 6ae883b1a25..0b29d54eadb 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -323,17 +323,12 @@ impl<'a> DynamicFunc<'a> { .iter() .enumerate() .map(|(i, t)| { - let i = i as isize + 1; // skip vmctx - + let i = i + 1; // skip vmctx match *t { - Type::I32 => Value::I32(*args.offset(i) as i32), - Type::I64 => Value::I64(*args.offset(i) as i64), - Type::F32 => { - eprintln!("{:?}", 3.0f32.to_le_bytes()); - eprintln!("{:#064x}", *args.offset(i)); - Value::F32(f32::from_bits(*args.offset(i) as u32)) - } - Type::F64 => Value::F64(f64::from_bits(*args.offset(i) as u64)), + Type::I32 => Value::I32(*args.offset(i as _) as i32), + Type::I64 => Value::I64(*args.offset(i as _) as i64), + Type::F32 => Value::F32(f32::from_bits(*args.offset(i as _) as u32)), + Type::F64 => Value::F64(f64::from_bits(*args.offset(i as _) as u64)), Type::V128 => { todo!("enter_host_polymorphic: 128-bit types are not supported") } @@ -341,7 +336,6 @@ impl<'a> DynamicFunc<'a> { }) .collect(); let rets = (ctx.func)(vmctx, &args); - if rets.len() == 0 { 0 } else if rets.len() == 1 { From 58c3b3489a2d2b995a0b601e9cbb7affd0c6c3a1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 16:00:05 +0100 Subject: [PATCH 6/9] doc(changelog) Add #1284. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0856e49dc49..f90c879971a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1284](https://github.com/wasmerio/wasmer/pull/1284) Implement string and memory instructions in `wasmer-interface-types` - [#1272](https://github.com/wasmerio/wasmer/pull/1272) Fix off-by-one error bug when accessing memory with a `WasmPtr` that contains the last valid byte of memory. Also changes the behavior of `WasmPtr` with a length of 0 and `WasmPtr` where `std::mem::size_of::()` is 0 to always return `None` ## 0.15.0 - 2020-03-04 From 2b94d6d38d7e18af0cb47ef9290928b57bc5c2d4 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 15:41:49 +0100 Subject: [PATCH 7/9] feat(interface-types) Use better errors. The new `errors` module contains structure to represent errors, instead of using basic strings. The first usage is in the interpreter itself. --- lib/interface-types/src/ast.rs | 2 +- lib/interface-types/src/errors.rs | 219 ++++++++++++++++++ .../src/interpreter/instruction.rs | 2 +- .../interpreter/instructions/argument_get.rs | 15 +- .../src/interpreter/instructions/call_core.rs | 70 +++--- .../instructions/lowering_lifting.rs | 54 +++-- .../instructions/memory_to_string.rs | 97 ++++---- .../src/interpreter/instructions/mod.rs | 21 ++ .../instructions/string_to_memory.rs | 82 ++++--- lib/interface-types/src/interpreter/mod.rs | 89 +++---- .../src/interpreter/wasm/values.rs | 49 ++-- lib/interface-types/src/lib.rs | 1 + lib/interface-types/src/macros.rs | 2 +- 13 files changed, 498 insertions(+), 205 deletions(-) create mode 100644 lib/interface-types/src/errors.rs diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 21b76020d66..9828043db0d 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -5,7 +5,7 @@ use crate::interpreter::Instruction; use std::str; /// Represents the types supported by WIT. -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub enum InterfaceType { /// A 8-bits signed integer. S8, diff --git a/lib/interface-types/src/errors.rs b/lib/interface-types/src/errors.rs new file mode 100644 index 00000000000..24ca5566a50 --- /dev/null +++ b/lib/interface-types/src/errors.rs @@ -0,0 +1,219 @@ +//! The error module contains all the data structures that represent +//! an error. + +use crate::{ast::InterfaceType, interpreter::Instruction}; +use std::{ + fmt::{self, Display, Formatter}, + result::Result, + string::{self, ToString}, +}; + +/// A type alias for instruction's results. +pub type InstructionResult = Result; + +/// A type alias for the interpreter result. +pub type InterpreterResult = Result; + +/// Structure to represent errors when casting from an `InterfaceType` +/// to a native value. +#[derive(Debug)] +pub struct WasmValueNativeCastError { + /// The initial type. + pub from: InterfaceType, + + /// The targeted type. + /// + /// `InterfaceType` is used to represent the native type by + /// associativity. + pub to: InterfaceType, +} + +/// Structure to represent the errors for instructions. +#[derive(Debug)] +pub struct InstructionError { + /// The instruction that raises the error. + pub instruction: Instruction, + + /// The error kind. + pub error_kind: InstructionErrorKind, +} + +impl InstructionError { + pub(crate) fn new(instruction: Instruction, error_kind: InstructionErrorKind) -> Self { + Self { + instruction, + error_kind, + } + } +} + +impl Display for InstructionError { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "`{}` {}", + (&self.instruction).to_string(), + self.error_kind + ) + } +} + +/// The kind of instruction errors. +#[derive(Debug)] +pub enum InstructionErrorKind { + /// The instruction needs to read an invocation input at index `index`, but it's missing. + InvocationInputIsMissing { + /// The invocation input index. + index: u32, + }, + + /// Failed to cast from a WIT value to a native value. + ToNative(WasmValueNativeCastError), + + /// Failed to cast from `from` to `to`. + LoweringLifting { + /// The initial type. + from: InterfaceType, + + /// The targeted type. + to: InterfaceType, + }, + + /// Read a value from the stack, but it doesn't have the expected + /// type. + InvalidValueOnTheStack { + /// The expected type. + expected_type: InterfaceType, + + /// The received type. + received_type: InterfaceType, + }, + + /// Need to read some values from the stack, but it doesn't + /// contain enough data. + StackIsTooSmall { + /// The number of values that were needed. + needed: usize, + }, + + /// The local or import function doesn't exist. + LocalOrImportIsMissing { + /// The local or import function index. + function_index: u32, + }, + + /// Values given to a local or import function doesn't match the + /// function signature. + LocalOrImportSignatureMismatch { + /// The local or import function index. + function_index: u32, + + /// The expected signature. + expected: (Vec, Vec), + + /// The received signature. + received: (Vec, Vec), + }, + + /// Failed to call a local or import function. + LocalOrImportCall { + /// The local or import function index that has been called. + function_index: u32, + }, + + /// The memory doesn't exist. + MemoryIsMissing { + /// The memory indeX. + memory_index: u32, + }, + + /// Tried to read out of bounds of the memory. + MemoryOutOfBoundsAccess { + /// The access index. + index: usize, + + /// The memory length. + length: usize, + }, + + /// The string contains invalid UTF-8 encoding. + String(string::FromUtf8Error), +} + +impl Display for InstructionErrorKind { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + match self { + Self::InvocationInputIsMissing { index } => write!( + formatter, + "cannot access invocation inputs #{} because it doesn't exist", + index + ), + + Self::ToNative(WasmValueNativeCastError { from, .. }) => write!( + formatter, + "failed to cast the WIT value `{:?}` to its native type", + from, + ), + + Self::LoweringLifting { from, to } => { + write!(formatter, "failed to cast `{:?}` to `{:?}`", from, to) + } + + Self::InvalidValueOnTheStack { + expected_type, + received_type, + } => write!( + formatter, + "read a value of type `{:?}` from the stack, but the type `{:?}` was expected", + received_type, expected_type, + ), + + Self::StackIsTooSmall { needed } => write!( + formatter, + "needed to read `{}` value(s) from the stack, but it doesn't contain enough data", + needed + ), + + Self::LocalOrImportIsMissing { function_index } => write!( + formatter, + "the local or import function `{}` doesn't exist", + function_index + ), + + Self::LocalOrImportSignatureMismatch { function_index, expected, received } => write!( + formatter, + "the local or import function `{}` has the signature `{:?} -> {:?}` but it received values of kind `{:?} -> {:?}`", + function_index, + expected.0, + expected.1, + received.0, + received.1, + ), + + Self::LocalOrImportCall { function_index } => write!( + formatter, + "failed while calling the local or import function `{}`", + function_index + ), + + Self::MemoryIsMissing { memory_index } => write!( + formatter, + "memory `{}` does not exist", + memory_index, + ), + + Self::MemoryOutOfBoundsAccess { index, length } => write!( + formatter, + "read out of the memory bounds (index {} > memory length {})", + index, + length, + ), + + Self::String(error) => write!( + formatter, + "{}", + error + ), + } + } +} diff --git a/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs index 109f269b808..425cdad5ca0 100644 --- a/lib/interface-types/src/interpreter/instruction.rs +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -1,7 +1,7 @@ //use crate::ast::InterfaceType; /// Represents all the possible WIT instructions. -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone, Copy)] pub enum Instruction { /// The `arg.get` instruction. ArgumentGet { diff --git a/lib/interface-types/src/interpreter/instructions/argument_get.rs b/lib/interface-types/src/interpreter/instructions/argument_get.rs index e530d880a34..dd161e15860 100644 --- a/lib/interface-types/src/interpreter/instructions/argument_get.rs +++ b/lib/interface-types/src/interpreter/instructions/argument_get.rs @@ -1,12 +1,17 @@ +use crate::{ + errors::{InstructionError, InstructionErrorKind}, + interpreter::Instruction, +}; + executable_instruction!( - argument_get(index: u32, instruction_name: String) -> _ { + argument_get(index: u32, instruction: Instruction) -> _ { move |runtime| -> _ { let invocation_inputs = runtime.invocation_inputs; if index >= (invocation_inputs.len() as u32) { - return Err(format!( - "`{}` cannot access argument #{} because it doesn't exist.", - instruction_name, index + return Err(InstructionError::new( + instruction, + InstructionErrorKind::InvocationInputIsMissing { index }, )); } @@ -49,6 +54,6 @@ mod tests { instructions: [Instruction::ArgumentGet { index: 1 }], invocation_inputs: [InterfaceValue::I32(42)], instance: Instance::new(), - error: "`arg.get 1` cannot access argument #1 because it doesn't exist." + error: "`arg.get 1` cannot access invocation inputs #1 because it doesn't exist" ); } diff --git a/lib/interface-types/src/interpreter/instructions/call_core.rs b/lib/interface-types/src/interpreter/instructions/call_core.rs index 3179c9080ab..dfaf520e463 100644 --- a/lib/interface-types/src/interpreter/instructions/call_core.rs +++ b/lib/interface-types/src/interpreter/instructions/call_core.rs @@ -1,10 +1,14 @@ -use crate::interpreter::wasm::{ - structures::{FunctionIndex, TypedIndex}, - values::InterfaceType, +use crate::{ + errors::{InstructionError, InstructionErrorKind}, + interpreter::wasm::{ + structures::{FunctionIndex, TypedIndex}, + values::InterfaceType, + }, + interpreter::Instruction, }; executable_instruction!( - call_core(function_index: usize, instruction_name: String) -> _ { + call_core(function_index: usize, instruction: Instruction) -> _ { move |runtime| -> _ { let instance = &mut runtime.wasm_instance; let index = FunctionIndex::new(function_index); @@ -21,12 +25,16 @@ executable_instruction!( .collect::>(); if input_types != local_or_import.inputs() { - return Err(format!( - "`{}` cannot call the local or imported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).", - instruction_name, - function_index, - local_or_import.inputs(), - )) + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportSignatureMismatch { + function_index: function_index as u32, + expected: (local_or_import.inputs().to_vec(), vec![]), + received: (input_types, vec![]), + } + ) + ) } match local_or_import.call(&inputs) { @@ -37,26 +45,28 @@ executable_instruction!( Ok(()) } - Err(_) => Err(format!( - "`{}` failed when calling the local or imported function `{}`.", - instruction_name, - function_index - )) + Err(_) => Err( + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportCall { function_index: function_index as u32, }, + ) + ) } } - None => Err(format!( - "`{}` cannot call the local or imported function `{}` because there is not enough data on the stack for the arguments (needs {}).", - instruction_name, - function_index, - inputs_cardinality, - )) + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: inputs_cardinality }, + ) + ) } } - None => Err(format!( - "`{}` cannot call the local or imported function `{}` because it doesn't exist.", - instruction_name, - function_index, - )) + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportIsMissing { function_index: function_index as u32, }, + ) + ) } } } @@ -89,7 +99,7 @@ mod tests { InterfaceValue::I32(4), ], instance: Default::default(), - error: r#"`call-core 42` cannot call the local or imported function `42` because it doesn't exist."#, + error: r#"`call-core 42` the local or import function `42` doesn't exist"#, ); test_executable_instruction!( @@ -104,7 +114,7 @@ mod tests { InterfaceValue::I32(4), ], instance: Instance::new(), - error: r#"`call-core 42` cannot call the local or imported function `42` because there is not enough data on the stack for the arguments (needs 2)."#, + error: r#"`call-core 42` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#, ); test_executable_instruction!( @@ -120,7 +130,7 @@ mod tests { // ^^^ mismatch with `42` signature ], instance: Instance::new(), - error: r#"`call-core 42` cannot call the local or imported function `42` because the value types on the stack mismatch the function signature (expects [I32, I32])."#, + error: r#"`call-core 42` the local or import function `42` has the signature `[I32, I32] -> []` but it received values of kind `[I32, I64] -> []`"#, ); test_executable_instruction!( @@ -151,7 +161,7 @@ mod tests { }, ..Default::default() }, - error: r#"`call-core 42` failed when calling the local or imported function `42`."#, + error: r#"`call-core 42` failed while calling the local or import function `42`"#, ); test_executable_instruction!( diff --git a/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs b/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs index c24fd992bb7..f18c24385f6 100644 --- a/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs +++ b/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs @@ -1,10 +1,14 @@ -use crate::interpreter::wasm::values::InterfaceValue; +use crate::{ + ast::InterfaceType, + errors::{InstructionError, InstructionErrorKind}, + interpreter::{wasm::values::InterfaceValue, Instruction}, +}; use std::convert::TryInto; macro_rules! lowering_lifting { ($instruction_function_name:ident, $instruction_name:expr, $from_variant:ident, $to_variant:ident) => { executable_instruction!( - $instruction_function_name() -> _ { + $instruction_function_name(instruction: Instruction) -> _ { move |runtime| -> _ { match runtime.stack.pop1() { Some(InterfaceValue::$from_variant(value)) => { @@ -12,39 +16,33 @@ macro_rules! lowering_lifting { .stack .push(InterfaceValue::$to_variant(value.try_into().map_err( |_| { - concat!( - "Failed to cast `", - stringify!($from_variant), - "` to `", - stringify!($to_variant), - "`." - ).to_string() + InstructionError::new( + instruction, + InstructionErrorKind::LoweringLifting { from: InterfaceType::$from_variant, to: InterfaceType::$to_variant }, + ) }, )?)) } Some(wrong_value) => { - return Err(format!( - concat!( - "Instruction `", - $instruction_name, - "` expects a `", - stringify!($from_variant), - "` value on the stack, got `{:?}`.", - ), - wrong_value - + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::InvalidValueOnTheStack { + expected_type: InterfaceType::$from_variant, + received_type: (&wrong_value).into(), + } + ) ) - .to_string()) }, None => { - return Err(concat!( - "Instruction `", - $instruction_name, - "` needs one value on the stack." + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 1 }, + ) ) - .to_string()) } } @@ -103,7 +101,7 @@ mod tests { instructions: [Instruction::ArgumentGet { index: 0}, Instruction::I32ToS8], invocation_inputs: [InterfaceValue::I32(128)], instance: Instance::new(), - error: "Failed to cast `I32` to `S8`." + error: "`i32-to-s8` failed to cast `I32` to `S8`" ); test_executable_instruction!( @@ -111,7 +109,7 @@ mod tests { instructions: [Instruction::ArgumentGet { index: 0}, Instruction::I32ToS8], invocation_inputs: [InterfaceValue::I64(42)], instance: Instance::new(), - error: "Instruction `i32-to-s8` expects a `I32` value on the stack, got `I64(42)`." + error: "`i32-to-s8` read a value of type `I64` from the stack, but the type `I32` was expected" ); test_executable_instruction!( @@ -119,7 +117,7 @@ mod tests { instructions: [Instruction::I32ToS8], invocation_inputs: [InterfaceValue::I32(42)], instance: Instance::new(), - error: "Instruction `i32-to-s8` needs one value on the stack." + error: "`i32-to-s8` needed to read `1` value(s) from the stack, but it doesn't contain enough data" ); test_executable_instruction!( diff --git a/lib/interface-types/src/interpreter/instructions/memory_to_string.rs b/lib/interface-types/src/interpreter/instructions/memory_to_string.rs index 678d08d7474..b1b9230356a 100644 --- a/lib/interface-types/src/interpreter/instructions/memory_to_string.rs +++ b/lib/interface-types/src/interpreter/instructions/memory_to_string.rs @@ -1,52 +1,69 @@ -use crate::interpreter::wasm::values::InterfaceValue; -use std::{cell::Cell, convert::TryFrom}; +use super::to_native; +use crate::{ + errors::{InstructionError, InstructionErrorKind}, + interpreter::{wasm::values::InterfaceValue, Instruction}, +}; +use std::cell::Cell; executable_instruction!( - memory_to_string(instruction_name: String) -> _ { + memory_to_string(instruction: Instruction) -> _ { move |runtime| -> _ { match runtime.stack.pop(2) { - Some(inputs) => match runtime.wasm_instance.memory(0) { - Some(memory) => { - let length = i32::try_from(&inputs[0])? as usize; - let pointer = i32::try_from(&inputs[1])? as usize; - let memory_view = memory.view(); + Some(inputs) => { + let memory_index: u32 = 0; - if memory_view.len() < pointer + length { - return Err(format!( - "`{}` failed because it has to read out of the memory bounds (index {} > memory length {}).", - instruction_name, - pointer + length, - memory_view.len() - )); - } + match runtime.wasm_instance.memory(memory_index as usize) { + Some(memory) => { + let length = to_native::(&inputs[0], instruction)? as usize; + let pointer = to_native::(&inputs[1], instruction)? as usize; + let memory_view = memory.view(); + + if memory_view.len() < pointer + length { + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::MemoryOutOfBoundsAccess { + index: pointer + length, + length: memory_view.len(), + } + ), + ) + } - let data: Vec = (&memory_view[pointer..pointer + length]) - .iter() - .map(Cell::get) - .collect(); + let data: Vec = (&memory_view[pointer..pointer + length]) + .iter() + .map(Cell::get) + .collect(); - match String::from_utf8(data) { - Ok(string) => { - runtime.stack.push(InterfaceValue::String(string)); + match String::from_utf8(data) { + Ok(string) => { + runtime.stack.push(InterfaceValue::String(string)); - Ok(()) + Ok(()) + } + Err(utf8_error) => Err( + InstructionError::new( + instruction, + InstructionErrorKind::String(utf8_error) + ), + ) } - Err(utf8_error) => Err(format!( - "`{}` failed because the read string isn't UTF-8 valid ({}).", - instruction_name, - utf8_error, - )) } + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::MemoryIsMissing { memory_index } + ), + ) } - None => Err(format!( - "`{}` failed because there is no memory to read.", - instruction_name - )) } - None => Err(format!( - "`{}` failed because there is not enough data on the stack (needs 2).", - instruction_name, - )) + + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 2 } + ), + ) } } } @@ -91,7 +108,7 @@ mod tests { memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), ..Default::default() }, - error: r#"`memory-to-string` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#, + error: r#"`memory-to-string` read out of the memory bounds (index 13 > memory length 6)"#, ); test_executable_instruction!( @@ -111,7 +128,7 @@ mod tests { memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::>>()), ..Default::default() }, - error: r#"`memory-to-string` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#, + error: r#"`memory-to-string` invalid utf-8 sequence of 1 bytes from index 1"#, ); test_executable_instruction!( @@ -126,6 +143,6 @@ mod tests { InterfaceValue::I32(0), ], instance: Instance::new(), - error: r#"`memory-to-string` failed because there is not enough data on the stack (needs 2)."#, + error: r#"`memory-to-string` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#, ); } diff --git a/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs index 8466b9938d4..4400cf002b4 100644 --- a/lib/interface-types/src/interpreter/instructions/mod.rs +++ b/lib/interface-types/src/interpreter/instructions/mod.rs @@ -4,12 +4,33 @@ mod lowering_lifting; mod memory_to_string; mod string_to_memory; +use crate::{ + errors::{InstructionError, InstructionErrorKind, InstructionResult, WasmValueNativeCastError}, + interpreter::{ + wasm::values::{InterfaceValue, NativeType}, + Instruction, + }, +}; pub(crate) use argument_get::argument_get; pub(crate) use call_core::call_core; pub(crate) use lowering_lifting::*; pub(crate) use memory_to_string::memory_to_string; +use std::convert::TryFrom; pub(crate) use string_to_memory::string_to_memory; +/// Just a short helper to map the error of a cast from an +/// `InterfaceValue` to a native value. +pub(crate) fn to_native<'a, T>( + wit_value: &'a InterfaceValue, + instruction: Instruction, +) -> InstructionResult +where + T: NativeType + TryFrom<&'a InterfaceValue, Error = WasmValueNativeCastError>, +{ + T::try_from(wit_value) + .map_err(|error| InstructionError::new(instruction, InstructionErrorKind::ToNative(error))) +} + #[cfg(test)] pub(crate) mod tests { use crate::interpreter::wasm::{ diff --git a/lib/interface-types/src/interpreter/instructions/string_to_memory.rs b/lib/interface-types/src/interpreter/instructions/string_to_memory.rs index 57414d03f5a..a4341a7c39a 100644 --- a/lib/interface-types/src/interpreter/instructions/string_to_memory.rs +++ b/lib/interface-types/src/interpreter/instructions/string_to_memory.rs @@ -1,60 +1,66 @@ -use crate::interpreter::wasm::{ - structures::{FunctionIndex, TypedIndex}, - values::{InterfaceType, InterfaceValue}, +use super::to_native; +use crate::{ + ast::InterfaceType, + errors::{InstructionError, InstructionErrorKind}, + interpreter::{ + wasm::{ + structures::{FunctionIndex, TypedIndex}, + values::InterfaceValue, + }, + Instruction, + }, }; -use std::convert::TryInto; executable_instruction!( - string_to_memory(allocator_index: u32, instruction_name: String) -> _ { + string_to_memory(allocator_index: u32, instruction: Instruction) -> _ { 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 + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportIsMissing { function_index: 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, - )); + return Err(InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportSignatureMismatch { + function_index: allocator_index, + expected: (vec![InterfaceType::I32], vec![InterfaceType::I32]), + received: (allocator.inputs().to_vec(), allocator.outputs().to_vec()) + } + )) } 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 + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 1 } ) })?; - let string: String = (&string).try_into()?; + let string: String = to_native(&string, instruction)?; let string_bytes = string.as_bytes(); - let string_length = (string_bytes.len() as i32) - .try_into() - .map_err(|error| format!("{}", error))?; + let string_length = string_bytes.len() as i32; - 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 outputs = allocator.call(&[InterfaceValue::I32(string_length)]).map_err(|_| { + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportCall { function_index: allocator_index }, + ) + })?; + let string_pointer: i32 = to_native(&outputs[0], instruction)?; + let memory_index: u32 = 0; let memory_view = instance - .memory(0) + .memory(memory_index as usize) .ok_or_else(|| { - format!( - "`{}` failed because there is no memory to write into.", - instruction_name + InstructionError::new( + instruction, + InstructionErrorKind::MemoryIsMissing { memory_index } ) })? .view(); @@ -106,7 +112,7 @@ mod tests { 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."#, + error: r#"`string-to-memory 43` the local or import function `43` doesn't exist"#, ); test_executable_instruction!( @@ -117,7 +123,7 @@ mod tests { ], 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)."#, + error: r#"`string-to-memory 43` needed to read `1` value(s) from the stack, but it doesn't contain enough data"#, ); test_executable_instruction!( @@ -141,7 +147,7 @@ mod tests { instance }, - error: r#"`string-to-memory 153` failed when calling the allocator `153`."#, + error: r#"`string-to-memory 153` failed while calling the local or import function `153`"#, ); test_executable_instruction!( @@ -164,6 +170,6 @@ mod tests { instance }, - error: r#"`string-to-memory 153` failed because the allocator `153` has an invalid signature (expects [I32] -> [I32])."#, + error: r#"`string-to-memory 153` the local or import function `153` has the signature `[I32] -> [I32]` but it received values of kind `[I32, I32] -> []`"#, ); } diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index bdc939fe22e..122c482d21b 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -5,6 +5,7 @@ mod instructions; pub mod stack; pub mod wasm; +use crate::errors::{InstructionResult, InterpreterResult}; pub use instruction::Instruction; use stack::Stack; use std::{convert::TryFrom, marker::PhantomData}; @@ -38,7 +39,9 @@ where /// Type alias for an executable instruction. It's an implementation /// details, but an instruction is a boxed closure instance. pub(crate) type ExecutableInstruction = Box< - dyn Fn(&mut Runtime) -> Result<(), String>, + dyn Fn( + &mut Runtime, + ) -> InstructionResult<()>, >; /// An interpreter is the central piece of this crate. It is a set of @@ -154,7 +157,7 @@ where &self, invocation_inputs: &[InterfaceValue], wasm_instance: &mut Instance, - ) -> Result, String> { + ) -> InterpreterResult> { let mut runtime = Runtime { invocation_inputs, stack: Stack::new(), @@ -165,7 +168,7 @@ where for executable_instruction in self.iter() { match executable_instruction(&mut runtime) { Ok(_) => continue, - Err(message) => return Err(message), + Err(error) => return Err(error), } } @@ -183,62 +186,64 @@ where MemoryView: wasm::structures::MemoryView, Instance: wasm::structures::Instance, { - type Error = String; + type Error = (); fn try_from(instructions: &Vec) -> Result { let executable_instructions = instructions .iter() .map(|instruction| { - let instruction_name = instruction.to_string(); - match instruction { Instruction::ArgumentGet { index } => { - instructions::argument_get(*index, instruction_name) + instructions::argument_get(*index, *instruction) } Instruction::CallCore { function_index } => { - instructions::call_core(*function_index, instruction_name) + instructions::call_core(*function_index, *instruction) } - Instruction::MemoryToString => instructions::memory_to_string(instruction_name), + Instruction::MemoryToString => instructions::memory_to_string(*instruction), Instruction::StringToMemory { allocator_index } => { - instructions::string_to_memory(*allocator_index, instruction_name) + instructions::string_to_memory(*allocator_index, *instruction) } - Instruction::I32ToS8 => instructions::i32_to_s8(), + Instruction::I32ToS8 => instructions::i32_to_s8(*instruction), //Instruction::I32ToS8X - Instruction::I32ToU8 => instructions::i32_to_u8(), - Instruction::I32ToS16 => instructions::i32_to_s16(), + Instruction::I32ToU8 => instructions::i32_to_u8(*instruction), + Instruction::I32ToS16 => instructions::i32_to_s16(*instruction), //Instruction::I32ToS16X - Instruction::I32ToU16 => instructions::i32_to_u16(), - Instruction::I32ToS32 => instructions::i32_to_s32(), - Instruction::I32ToU32 => instructions::i32_to_u32(), - Instruction::I32ToS64 => instructions::i32_to_s64(), - Instruction::I32ToU64 => instructions::i32_to_u64(), - Instruction::I64ToS8 => instructions::i64_to_s8(), + Instruction::I32ToU16 => instructions::i32_to_u16(*instruction), + Instruction::I32ToS32 => instructions::i32_to_s32(*instruction), + Instruction::I32ToU32 => instructions::i32_to_u32(*instruction), + Instruction::I32ToS64 => instructions::i32_to_s64(*instruction), + Instruction::I32ToU64 => instructions::i32_to_u64(*instruction), + Instruction::I64ToS8 => instructions::i64_to_s8(*instruction), //Instruction::I64ToS8X - Instruction::I64ToU8 => instructions::i64_to_u8(), - Instruction::I64ToS16 => instructions::i64_to_s16(), + Instruction::I64ToU8 => instructions::i64_to_u8(*instruction), + Instruction::I64ToS16 => instructions::i64_to_s16(*instruction), //Instruction::I64ToS16X - Instruction::I64ToU16 => instructions::i64_to_u16(), - Instruction::I64ToS32 => instructions::i64_to_s32(), - Instruction::I64ToU32 => instructions::i64_to_u32(), - Instruction::I64ToS64 => instructions::i64_to_s64(), - Instruction::I64ToU64 => instructions::i64_to_u64(), - Instruction::S8ToI32 => instructions::s8_to_i32(), - Instruction::U8ToI32 => instructions::u8_to_i32(), - Instruction::S16ToI32 => instructions::s16_to_i32(), - Instruction::U16ToI32 => instructions::u16_to_i32(), - Instruction::S32ToI32 => instructions::s32_to_i32(), - Instruction::U32ToI32 => instructions::u32_to_i32(), - Instruction::S64ToI32 | Instruction::S64ToI32X => instructions::s64_to_i32(), - Instruction::U64ToI32 | Instruction::U64ToI32X => instructions::u64_to_i32(), - Instruction::S8ToI64 => instructions::s8_to_i64(), - Instruction::U8ToI64 => instructions::u8_to_i64(), - Instruction::S16ToI64 => instructions::s16_to_i64(), - Instruction::U16ToI64 => instructions::u16_to_i64(), - Instruction::S32ToI64 => instructions::s32_to_i64(), - Instruction::U32ToI64 => instructions::u32_to_i64(), - Instruction::S64ToI64 => instructions::s64_to_i64(), - Instruction::U64ToI64 => instructions::u64_to_i64(), + Instruction::I64ToU16 => instructions::i64_to_u16(*instruction), + Instruction::I64ToS32 => instructions::i64_to_s32(*instruction), + Instruction::I64ToU32 => instructions::i64_to_u32(*instruction), + Instruction::I64ToS64 => instructions::i64_to_s64(*instruction), + Instruction::I64ToU64 => instructions::i64_to_u64(*instruction), + Instruction::S8ToI32 => instructions::s8_to_i32(*instruction), + Instruction::U8ToI32 => instructions::u8_to_i32(*instruction), + Instruction::S16ToI32 => instructions::s16_to_i32(*instruction), + Instruction::U16ToI32 => instructions::u16_to_i32(*instruction), + Instruction::S32ToI32 => instructions::s32_to_i32(*instruction), + Instruction::U32ToI32 => instructions::u32_to_i32(*instruction), + Instruction::S64ToI32 | Instruction::S64ToI32X => { + instructions::s64_to_i32(*instruction) + } + Instruction::U64ToI32 | Instruction::U64ToI32X => { + instructions::u64_to_i32(*instruction) + } + Instruction::S8ToI64 => instructions::s8_to_i64(*instruction), + Instruction::U8ToI64 => instructions::u8_to_i64(*instruction), + Instruction::S16ToI64 => instructions::s16_to_i64(*instruction), + Instruction::U16ToI64 => instructions::u16_to_i64(*instruction), + Instruction::S32ToI64 => instructions::s32_to_i64(*instruction), + Instruction::U32ToI64 => instructions::u32_to_i64(*instruction), + Instruction::S64ToI64 => instructions::s64_to_i64(*instruction), + Instruction::U64ToI64 => instructions::u64_to_i64(*instruction), _ => unimplemented!(), } }) diff --git a/lib/interface-types/src/interpreter/wasm/values.rs b/lib/interface-types/src/interpreter/wasm/values.rs index 1e0976cfe42..484fa1dae58 100644 --- a/lib/interface-types/src/interpreter/wasm/values.rs +++ b/lib/interface-types/src/interpreter/wasm/values.rs @@ -1,8 +1,8 @@ #![allow(missing_docs)] -use std::convert::TryFrom; - pub use crate::ast::InterfaceType; +use crate::errors::WasmValueNativeCastError; +use std::convert::TryFrom; #[derive(Debug, Clone, PartialEq)] pub enum InterfaceValue { @@ -49,35 +49,46 @@ impl Default for InterfaceValue { } } -macro_rules! from_x_for_interface_value { - ($native_type:ty, $value_variant:ident) => { +pub trait NativeType { + const INTERFACE_TYPE: InterfaceType; +} + +macro_rules! native { + ($native_type:ty, $variant:ident) => { + impl NativeType for $native_type { + const INTERFACE_TYPE: InterfaceType = InterfaceType::$variant; + } + impl From<$native_type> for InterfaceValue { fn from(n: $native_type) -> Self { - Self::$value_variant(n) + Self::$variant(n) } } impl TryFrom<&InterfaceValue> for $native_type { - type Error = &'static str; + type Error = WasmValueNativeCastError; fn try_from(w: &InterfaceValue) -> Result { match w { - InterfaceValue::$value_variant(n) => Ok(n.clone()), - _ => Err("Invalid cast."), + InterfaceValue::$variant(n) => Ok(n.clone()), + _ => Err(WasmValueNativeCastError { + from: w.into(), + to: <$native_type>::INTERFACE_TYPE, + }), } } } }; } -from_x_for_interface_value!(i8, S8); -from_x_for_interface_value!(i16, S16); -from_x_for_interface_value!(u8, U8); -from_x_for_interface_value!(u16, U16); -from_x_for_interface_value!(u32, U32); -from_x_for_interface_value!(u64, U64); -from_x_for_interface_value!(f32, F32); -from_x_for_interface_value!(f64, F64); -from_x_for_interface_value!(String, String); -from_x_for_interface_value!(i32, I32); -from_x_for_interface_value!(i64, I64); +native!(i8, S8); +native!(i16, S16); +native!(u8, U8); +native!(u16, U16); +native!(u32, U32); +native!(u64, U64); +native!(f32, F32); +native!(f64, F64); +native!(String, String); +native!(i32, I32); +native!(i64, I64); diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 95545943800..9e88cac281b 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -55,4 +55,5 @@ pub mod ast; mod macros; pub mod decoders; pub mod encoders; +pub mod errors; pub mod interpreter; diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs index 5e2217e29e0..b2e2cfa807d 100644 --- a/lib/interface-types/src/macros.rs +++ b/lib/interface-types/src/macros.rs @@ -122,7 +122,7 @@ macro_rules! test_executable_instruction { assert!(run.is_err()); - let error = run.unwrap_err(); + let error = run.unwrap_err().to_string(); assert_eq!(error, String::from($error)); } From 50883827698df8cbe5070ec9eb0674de0b737a32 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 15:53:46 +0100 Subject: [PATCH 8/9] feat(interface-types) Reformat the instructions. --- .../src/interpreter/instructions/call_core.rs | 93 +++++++++---------- .../instructions/lowering_lifting.rs | 31 +++---- .../instructions/memory_to_string.rs | 89 ++++++++---------- 3 files changed, 96 insertions(+), 117 deletions(-) diff --git a/lib/interface-types/src/interpreter/instructions/call_core.rs b/lib/interface-types/src/interpreter/instructions/call_core.rs index dfaf520e463..77486498f6c 100644 --- a/lib/interface-types/src/interpreter/instructions/call_core.rs +++ b/lib/interface-types/src/interpreter/instructions/call_core.rs @@ -13,61 +13,54 @@ executable_instruction!( let instance = &mut runtime.wasm_instance; let index = FunctionIndex::new(function_index); - match instance.local_or_import(index) { - Some(local_or_import) => { - let inputs_cardinality = local_or_import.inputs_cardinality(); - - match runtime.stack.pop(inputs_cardinality) { - Some(inputs) => { - let input_types = inputs - .iter() - .map(Into::into) - .collect::>(); + let local_or_import = instance.local_or_import(index).ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportIsMissing { + function_index: function_index as u32, + }, + ) + })?; + let inputs_cardinality = local_or_import.inputs_cardinality(); - if input_types != local_or_import.inputs() { - return Err( - InstructionError::new( - instruction, - InstructionErrorKind::LocalOrImportSignatureMismatch { - function_index: function_index as u32, - expected: (local_or_import.inputs().to_vec(), vec![]), - received: (input_types, vec![]), - } - ) - ) - } + let inputs = runtime.stack.pop(inputs_cardinality).ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { + needed: inputs_cardinality, + }, + ) + })?; + let input_types = inputs + .iter() + .map(Into::into) + .collect::>(); - match local_or_import.call(&inputs) { - Ok(outputs) => { - for output in outputs.iter() { - runtime.stack.push(output.clone()); - } + if input_types != local_or_import.inputs() { + return Err(InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportSignatureMismatch { + function_index: function_index as u32, + expected: (local_or_import.inputs().to_vec(), vec![]), + received: (input_types, vec![]), + }, + )); + } - Ok(()) - } - Err(_) => Err( - InstructionError::new( - instruction, - InstructionErrorKind::LocalOrImportCall { function_index: function_index as u32, }, - ) - ) - } - } - None => Err( - InstructionError::new( - instruction, - InstructionErrorKind::StackIsTooSmall { needed: inputs_cardinality }, - ) - ) - } - } - None => Err( - InstructionError::new( - instruction, - InstructionErrorKind::LocalOrImportIsMissing { function_index: function_index as u32, }, - ) + let outputs = local_or_import.call(&inputs).map_err(|_| { + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportCall { + function_index: function_index as u32, + }, ) + })?; + + for output in outputs.iter() { + runtime.stack.push(output.clone()); } + + Ok(()) } } ); diff --git a/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs b/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs index f18c24385f6..d3903e6604a 100644 --- a/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs +++ b/lib/interface-types/src/interpreter/instructions/lowering_lifting.rs @@ -18,31 +18,30 @@ macro_rules! lowering_lifting { |_| { InstructionError::new( instruction, - InstructionErrorKind::LoweringLifting { from: InterfaceType::$from_variant, to: InterfaceType::$to_variant }, + InstructionErrorKind::LoweringLifting { + from: InterfaceType::$from_variant, + to: InterfaceType::$to_variant + }, ) }, )?)) } Some(wrong_value) => { - return Err( - InstructionError::new( - instruction, - InstructionErrorKind::InvalidValueOnTheStack { - expected_type: InterfaceType::$from_variant, - received_type: (&wrong_value).into(), - } - ) - ) + return Err(InstructionError::new( + instruction, + InstructionErrorKind::InvalidValueOnTheStack { + expected_type: InterfaceType::$from_variant, + received_type: (&wrong_value).into(), + } + )) }, None => { - return Err( - InstructionError::new( - instruction, - InstructionErrorKind::StackIsTooSmall { needed: 1 }, - ) - ) + return Err(InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 1 }, + )) } } diff --git a/lib/interface-types/src/interpreter/instructions/memory_to_string.rs b/lib/interface-types/src/interpreter/instructions/memory_to_string.rs index b1b9230356a..f96c47f22f6 100644 --- a/lib/interface-types/src/interpreter/instructions/memory_to_string.rs +++ b/lib/interface-types/src/interpreter/instructions/memory_to_string.rs @@ -8,63 +8,50 @@ use std::cell::Cell; executable_instruction!( memory_to_string(instruction: Instruction) -> _ { move |runtime| -> _ { - match runtime.stack.pop(2) { - Some(inputs) => { - let memory_index: u32 = 0; + let inputs = runtime.stack.pop(2).ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 2 }, + ) + })?; - match runtime.wasm_instance.memory(memory_index as usize) { - Some(memory) => { - let length = to_native::(&inputs[0], instruction)? as usize; - let pointer = to_native::(&inputs[1], instruction)? as usize; - let memory_view = memory.view(); + let memory_index: u32 = 0; - if memory_view.len() < pointer + length { - return Err( - InstructionError::new( - instruction, - InstructionErrorKind::MemoryOutOfBoundsAccess { - index: pointer + length, - length: memory_view.len(), - } - ), - ) - } + let memory = runtime + .wasm_instance + .memory(memory_index as usize) + .ok_or_else(|| { + InstructionError::new( + instruction, + InstructionErrorKind::MemoryIsMissing { memory_index }, + ) + })?; - let data: Vec = (&memory_view[pointer..pointer + length]) - .iter() - .map(Cell::get) - .collect(); + let length = to_native::(&inputs[0], instruction)? as usize; + let pointer = to_native::(&inputs[1], instruction)? as usize; + let memory_view = memory.view(); - match String::from_utf8(data) { - Ok(string) => { - runtime.stack.push(InterfaceValue::String(string)); + if memory_view.len() < pointer + length { + return Err(InstructionError::new( + instruction, + InstructionErrorKind::MemoryOutOfBoundsAccess { + index: pointer + length, + length: memory_view.len(), + }, + )); + } - Ok(()) - } - Err(utf8_error) => Err( - InstructionError::new( - instruction, - InstructionErrorKind::String(utf8_error) - ), - ) - } - } - None => Err( - InstructionError::new( - instruction, - InstructionErrorKind::MemoryIsMissing { memory_index } - ), - ) - } - } + let data: Vec = (&memory_view[pointer..pointer + length]) + .iter() + .map(Cell::get) + .collect(); - None => Err( - InstructionError::new( - instruction, - InstructionErrorKind::StackIsTooSmall { needed: 2 } - ), - ) - } + let string = String::from_utf8(data) + .map_err(|error| InstructionError::new(instruction, InstructionErrorKind::String(error)))?; + + runtime.stack.push(InterfaceValue::String(string)); + + Ok(()) } } ); From abf30d1da6bdc11e6741d0ee153f017424a56d77 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 16:07:07 +0100 Subject: [PATCH 9/9] doc(changelog) Add #1285. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f90c879971a..4a8de268716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1285](https://github.com/wasmerio/wasmer/pull/1285) Greatly improve errors in `wasmer-interface-types` - [#1284](https://github.com/wasmerio/wasmer/pull/1284) Implement string and memory instructions in `wasmer-interface-types` - [#1272](https://github.com/wasmerio/wasmer/pull/1272) Fix off-by-one error bug when accessing memory with a `WasmPtr` that contains the last valid byte of memory. Also changes the behavior of `WasmPtr` with a length of 0 and `WasmPtr` where `std::mem::size_of::()` is 0 to always return `None`