Merge pull request #1284 from Hywan/feat-interface-types-instructions-string-and-memory

feat(interface-types) Implement string and memory instructions
This commit is contained in:
Syrus Akbary 2020-03-10 09:48:25 -07:00 committed by GitHub
commit ea641495f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 259 additions and 261 deletions

View File

@ -2,6 +2,7 @@
## **[Unreleased]** ## **[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<T, Array>` with a length of 0 and `WasmPtr<T>` where `std::mem::size_of::<T>()` is 0 to always return `None` - [#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<T, Array>` with a length of 0 and `WasmPtr<T>` where `std::mem::size_of::<T>()` is 0 to always return `None`
## 0.15.0 - 2020-03-04 ## 0.15.0 - 2020-03-04

View File

@ -89,12 +89,12 @@ pub struct Export<'input> {
/// Represents an adapter. /// Represents an adapter.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct Adapter<'input> { pub struct Adapter {
/// The adapter function type. /// The adapter function type.
pub function_type: u32, pub function_type: u32,
/// The instructions. /// The instructions.
pub instructions: Vec<Instruction<'input>>, pub instructions: Vec<Instruction>,
} }
/// Represents an implementation. /// Represents an implementation.
@ -137,7 +137,7 @@ pub struct Interfaces<'input> {
pub imports: Vec<Import<'input>>, pub imports: Vec<Import<'input>>,
/// All the adapters. /// All the adapters.
pub adapters: Vec<Adapter<'input>>, pub adapters: Vec<Adapter>,
/// All the exported functions. /// All the exported functions.
pub exports: Vec<Export<'input>>, pub exports: Vec<Export<'input>>,

View File

@ -174,14 +174,14 @@ fn instruction<'input, E: ParseError<&'input [u8]>>(
) )
} }
0x03 => (input, Instruction::ReadUtf8), 0x03 => (input, Instruction::MemoryToString),
0x04 => { 0x04 => {
consume!((input, argument_0) = string(input)?); consume!((input, argument_0) = uleb(input)?);
( (
input, input,
Instruction::WriteUtf8 { Instruction::StringToMemory {
allocator_name: argument_0, allocator_index: argument_0 as u32,
}, },
) )
} }
@ -630,8 +630,8 @@ mod tests {
0x2b, // list of 43 items 0x2b, // list of 43 items
0x00, 0x01, // ArgumentGet { index: 1 } 0x00, 0x01, // ArgumentGet { index: 1 }
0x01, 0x01, // CallCore { function_index: 1 } 0x01, 0x01, // CallCore { function_index: 1 }
0x03, // ReadUtf8 0x03, // MemoryToString
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } 0x04, 0x01, // StringToMemory { allocator_index: 1 }
0x07, // I32ToS8 0x07, // I32ToS8
0x08, // I32ToS8X 0x08, // I32ToS8X
0x09, // I32ToU8 0x09, // I32ToU8
@ -678,10 +678,8 @@ mod tests {
vec![ vec![
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::CallCore { function_index: 1 }, Instruction::CallCore { function_index: 1 },
Instruction::ReadUtf8, Instruction::MemoryToString,
Instruction::WriteUtf8 { Instruction::StringToMemory { allocator_index: 1 },
allocator_name: "abc",
},
Instruction::I32ToS8, Instruction::I32ToS8,
Instruction::I32ToS8X, Instruction::I32ToS8X,
Instruction::I32ToU8, Instruction::I32ToU8,

View File

@ -28,8 +28,8 @@ mod keyword {
// Instructions. // Instructions.
custom_keyword!(argument_get = "arg.get"); custom_keyword!(argument_get = "arg.get");
custom_keyword!(call_core = "call-core"); custom_keyword!(call_core = "call-core");
custom_keyword!(read_utf8 = "read-utf8"); 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_s8 = "i32-to-s8");
custom_keyword!(i32_to_s8x = "i32-to-s8x"); custom_keyword!(i32_to_s8x = "i32-to-s8x");
custom_keyword!(i32_to_u8 = "i32-to-u8"); custom_keyword!(i32_to_u8 = "i32-to-u8");
@ -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)] #[allow(clippy::cognitive_complexity)]
fn parse(parser: Parser<'a>) -> Result<Self> { fn parse(parser: Parser<'a>) -> Result<Self> {
let mut lookahead = parser.lookahead1(); let mut lookahead = parser.lookahead1();
@ -154,15 +154,15 @@ impl<'a> Parse<'a> for Instruction<'a> {
Ok(Instruction::CallCore { Ok(Instruction::CallCore {
function_index: parser.parse::<u64>()? as usize, function_index: parser.parse::<u64>()? as usize,
}) })
} else if lookahead.peek::<keyword::read_utf8>() { } else if lookahead.peek::<keyword::memory_to_string>() {
parser.parse::<keyword::read_utf8>()?; parser.parse::<keyword::memory_to_string>()?;
Ok(Instruction::ReadUtf8) Ok(Instruction::MemoryToString)
} else if lookahead.peek::<keyword::write_utf8>() { } else if lookahead.peek::<keyword::string_to_memory>() {
parser.parse::<keyword::write_utf8>()?; parser.parse::<keyword::string_to_memory>()?;
Ok(Instruction::WriteUtf8 { Ok(Instruction::StringToMemory {
allocator_name: parser.parse()?, allocator_index: parser.parse()?,
}) })
} else if lookahead.peek::<keyword::i32_to_s8>() { } else if lookahead.peek::<keyword::i32_to_s8>() {
parser.parse::<keyword::i32_to_s8>()?; parser.parse::<keyword::i32_to_s8>()?;
@ -392,7 +392,7 @@ impl Parse<'_> for FunctionType {
enum Interface<'a> { enum Interface<'a> {
Type(Type), Type(Type),
Import(Import<'a>), Import(Import<'a>),
Adapter(Adapter<'a>), Adapter(Adapter),
Export(Export<'a>), Export(Export<'a>),
Implementation(Implementation), 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<Self> { fn parse(parser: Parser<'a>) -> Result<Self> {
parser.parse::<keyword::func>()?; parser.parse::<keyword::func>()?;
@ -667,8 +667,8 @@ mod tests {
let inputs = vec![ let inputs = vec![
"arg.get 7", "arg.get 7",
"call-core 7", "call-core 7",
"read-utf8", "memory-to-string",
r#"write-utf8 "foo""#, "string-to-memory 42",
"i32-to-s8", "i32-to-s8",
"i32-to-s8x", "i32-to-s8x",
"i32-to-u8", "i32-to-u8",
@ -712,9 +712,9 @@ mod tests {
let outputs = vec![ let outputs = vec![
Instruction::ArgumentGet { index: 7 }, Instruction::ArgumentGet { index: 7 },
Instruction::CallCore { function_index: 7 }, Instruction::CallCore { function_index: 7 },
Instruction::ReadUtf8, Instruction::MemoryToString,
Instruction::WriteUtf8 { Instruction::StringToMemory {
allocator_name: "foo", allocator_index: 42,
}, },
Instruction::I32ToS8, Instruction::I32ToS8,
Instruction::I32ToS8X, Instruction::I32ToS8X,

View File

@ -162,7 +162,7 @@ where
/// Encode an `Adapter` into bytes. /// Encode an `Adapter` into bytes.
/// ///
/// Decoder is in `decoders::binary::adapters`. /// Decoder is in `decoders::binary::adapters`.
impl<W> ToBytes<W> for Adapter<'_> impl<W> ToBytes<W> for Adapter
where where
W: Write, W: Write,
{ {
@ -244,7 +244,7 @@ where
/// Encode an `Instruction` into bytes. /// Encode an `Instruction` into bytes.
/// ///
/// Decoder is `decoders::binary::instruction`. /// Decoder is `decoders::binary::instruction`.
impl<W> ToBytes<W> for Instruction<'_> impl<W> ToBytes<W> for Instruction
where where
W: Write, W: Write,
{ {
@ -260,11 +260,11 @@ where
(*function_index as u64).to_bytes(writer)?; (*function_index as u64).to_bytes(writer)?;
} }
Instruction::ReadUtf8 => 0x03_u8.to_bytes(writer)?, Instruction::MemoryToString => 0x03_u8.to_bytes(writer)?,
Instruction::WriteUtf8 { allocator_name } => { Instruction::StringToMemory { allocator_index } => {
0x04_u8.to_bytes(writer)?; 0x04_u8.to_bytes(writer)?;
allocator_name.to_bytes(writer)?; (*allocator_index as u64).to_bytes(writer)?;
} }
Instruction::I32ToS8 => 0x07_u8.to_bytes(writer)?, Instruction::I32ToS8 => 0x07_u8.to_bytes(writer)?,
@ -550,10 +550,8 @@ mod tests {
vec![ vec![
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::CallCore { function_index: 1 }, Instruction::CallCore { function_index: 1 },
Instruction::ReadUtf8, Instruction::MemoryToString,
Instruction::WriteUtf8 { Instruction::StringToMemory { allocator_index: 1 },
allocator_name: "abc",
},
Instruction::I32ToS8, Instruction::I32ToS8,
Instruction::I32ToS8X, Instruction::I32ToS8X,
Instruction::I32ToU8, Instruction::I32ToU8,
@ -598,8 +596,8 @@ mod tests {
0x2b, // list of 43 items 0x2b, // list of 43 items
0x00, 0x01, // ArgumentGet { index: 1 } 0x00, 0x01, // ArgumentGet { index: 1 }
0x01, 0x01, // CallCore { function_index: 1 } 0x01, 0x01, // CallCore { function_index: 1 }
0x03, // ReadUtf8 0x03, // MemoryToString
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } 0x04, 0x01, // StringToMemory { allocator_index: 1 }
0x07, // I32ToS8 0x07, // I32ToS8
0x08, // I32ToS8X 0x08, // I32ToS8X
0x09, // I32ToU8 0x09, // I32ToU8

View File

@ -80,14 +80,14 @@ impl ToString for &InterfaceType {
} }
/// Encode an `Instruction` into a string. /// Encode an `Instruction` into a string.
impl<'input> ToString for &Instruction<'input> { impl ToString for &Instruction {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
Instruction::ArgumentGet { index } => format!("arg.get {}", index), Instruction::ArgumentGet { index } => format!("arg.get {}", index),
Instruction::CallCore { function_index } => format!("call-core {}", function_index), Instruction::CallCore { function_index } => format!("call-core {}", function_index),
Instruction::ReadUtf8 => "read-utf8".into(), Instruction::MemoryToString => "memory-to-string".into(),
Instruction::WriteUtf8 { allocator_name } => { Instruction::StringToMemory { allocator_index } => {
format!(r#"write-utf8 "{}""#, allocator_name) format!(r#"string-to-memory {}"#, allocator_index)
} }
Instruction::I32ToS8 => "i32-to-s8".into(), Instruction::I32ToS8 => "i32-to-s8".into(),
Instruction::I32ToS8X => "i32-to-s8x".into(), Instruction::I32ToS8X => "i32-to-s8x".into(),
@ -194,7 +194,7 @@ impl<'input> ToString for &Import<'input> {
} }
/// Encode an `Adapter` into a string. /// Encode an `Adapter` into a string.
impl<'input> ToString for &Adapter<'input> { impl ToString for &Adapter {
fn to_string(&self) -> String { fn to_string(&self) -> String {
format!( format!(
r#"(@interface func (type {function_type}){instructions})"#, r#"(@interface func (type {function_type}){instructions})"#,
@ -361,9 +361,9 @@ mod tests {
let inputs: Vec<String> = vec![ let inputs: Vec<String> = vec![
(&Instruction::ArgumentGet { index: 7 }).to_string(), (&Instruction::ArgumentGet { index: 7 }).to_string(),
(&Instruction::CallCore { function_index: 7 }).to_string(), (&Instruction::CallCore { function_index: 7 }).to_string(),
(&Instruction::ReadUtf8).to_string(), (&Instruction::MemoryToString).to_string(),
(&Instruction::WriteUtf8 { (&Instruction::StringToMemory {
allocator_name: "foo", allocator_index: 42,
}) })
.to_string(), .to_string(),
(&Instruction::I32ToS8).to_string(), (&Instruction::I32ToS8).to_string(),
@ -409,8 +409,8 @@ mod tests {
let outputs = vec![ let outputs = vec![
"arg.get 7", "arg.get 7",
"call-core 7", "call-core 7",
"read-utf8", "memory-to-string",
r#"write-utf8 "foo""#, "string-to-memory 42",
"i32-to-s8", "i32-to-s8",
"i32-to-s8x", "i32-to-s8x",
"i32-to-u8", "i32-to-u8",

View File

@ -2,7 +2,7 @@
/// Represents all the possible WIT instructions. /// Represents all the possible WIT instructions.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum Instruction<'input> { pub enum Instruction {
/// The `arg.get` instruction. /// The `arg.get` instruction.
ArgumentGet { ArgumentGet {
/// The argument index. /// The argument index.
@ -15,13 +15,13 @@ pub enum Instruction<'input> {
function_index: usize, function_index: usize,
}, },
/// The `read-utf8` instruction. /// The `memory-to-string` instruction.
ReadUtf8, MemoryToString,
/// The `write-utf8` instruction. /// The `string-to-memory` instruction.
WriteUtf8 { StringToMemory {
/// The allocator function name. /// The allocator function index.
allocator_name: &'input str, allocator_index: u32,
}, },
/// The `i32-to-s8,` instruction. /// The `i32-to-s8,` instruction.

View File

@ -2,7 +2,7 @@ use crate::interpreter::wasm::values::InterfaceValue;
use std::{cell::Cell, convert::TryFrom}; use std::{cell::Cell, convert::TryFrom};
executable_instruction!( executable_instruction!(
read_utf8(instruction_name: String) -> _ { memory_to_string(instruction_name: String) -> _ {
move |runtime| -> _ { move |runtime| -> _ {
match runtime.stack.pop(2) { match runtime.stack.pop(2) {
Some(inputs) => match runtime.wasm_instance.memory(0) { Some(inputs) => match runtime.wasm_instance.memory(0) {
@ -55,11 +55,11 @@ executable_instruction!(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
test_executable_instruction!( test_executable_instruction!(
test_read_utf8 = test_memory_to_string =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8, Instruction::MemoryToString,
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(13), InterfaceValue::I32(13),
@ -75,11 +75,11 @@ mod tests {
); );
test_executable_instruction!( test_executable_instruction!(
test_read_utf8__read_out_of_memory = test_memory_to_string__read_out_of_memory =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8, Instruction::MemoryToString,
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(13), InterfaceValue::I32(13),
@ -91,15 +91,15 @@ mod tests {
memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
..Default::default() ..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_executable_instruction!(
test_read_utf8__invalid_encoding = test_memory_to_string__invalid_encoding =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8, Instruction::MemoryToString,
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(4), InterfaceValue::I32(4),
@ -111,21 +111,21 @@ mod tests {
memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::<Vec<Cell<u8>>>()), memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::<Vec<Cell<u8>>>()),
..Default::default() ..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_executable_instruction!(
test_read_utf8__stack_is_too_small = test_memory_to_string__stack_is_too_small =
instructions: [ instructions: [
Instruction::ArgumentGet { index: 0 }, Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8, Instruction::MemoryToString,
// ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present. // ^^^^^^^^^^^^^^ `memory-to-string` expects 2 values on the stack, only one is present.
], ],
invocation_inputs: [ invocation_inputs: [
InterfaceValue::I32(13), InterfaceValue::I32(13),
InterfaceValue::I32(0), InterfaceValue::I32(0),
], ],
instance: Instance::new(), 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)."#,
); );
} }

View File

@ -1,14 +1,14 @@
mod argument_get; mod argument_get;
mod call_core; mod call_core;
mod lowering_lifting; mod lowering_lifting;
mod read_utf8; mod memory_to_string;
mod write_utf8; mod string_to_memory;
pub(crate) use argument_get::argument_get; pub(crate) use argument_get::argument_get;
pub(crate) use call_core::call_core; pub(crate) use call_core::call_core;
pub(crate) use lowering_lifting::*; 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; pub(crate) use string_to_memory::string_to_memory;
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
@ -131,23 +131,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 hashmap
}, },
locals_or_imports: { locals_or_imports: {
let mut hashmap = HashMap::new(); let mut hashmap = HashMap::new();
// sum
hashmap.insert( hashmap.insert(
42, 42,
LocalImport { LocalImport {
@ -161,6 +150,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 hashmap
}, },

View File

@ -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])."#,
);
}

View File

@ -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_read_utf8 =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc" },
Instruction::ReadUtf8,
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: Instance::new(),
stack: [InterfaceValue::String("Hello, World!".into())],
);
test_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])."#,
);
}

View File

@ -174,8 +174,7 @@ where
} }
/// Transforms a `Vec<Instruction>` into an `Interpreter`. /// Transforms a `Vec<Instruction>` into an `Interpreter`.
impl<'binary_input, Instance, Export, LocalImport, Memory, MemoryView> impl<Instance, Export, LocalImport, Memory, MemoryView> TryFrom<&Vec<Instruction>>
TryFrom<&Vec<Instruction<'binary_input>>>
for Interpreter<Instance, Export, LocalImport, Memory, MemoryView> for Interpreter<Instance, Export, LocalImport, Memory, MemoryView>
where where
Export: wasm::structures::Export, Export: wasm::structures::Export,
@ -199,9 +198,9 @@ where
Instruction::CallCore { function_index } => { Instruction::CallCore { function_index } => {
instructions::call_core(*function_index, instruction_name) instructions::call_core(*function_index, instruction_name)
} }
Instruction::ReadUtf8 => instructions::read_utf8(instruction_name), Instruction::MemoryToString => instructions::memory_to_string(instruction_name),
Instruction::WriteUtf8 { allocator_name } => { Instruction::StringToMemory { allocator_index } => {
instructions::write_utf8((*allocator_name).to_owned(), instruction_name) instructions::string_to_memory(*allocator_index, instruction_name)
} }
Instruction::I32ToS8 => instructions::i32_to_s8(), Instruction::I32ToS8 => instructions::i32_to_s8(),