mirror of
https://github.com/fluencelabs/wasmer
synced 2025-03-30 22:41:03 +00:00
feat(interface-types) Implement the read-utf8
instruction.
It implies to create the `wasm::Memory` trait. Also, the patch updates `wasm::Type` and `wasm::Value` to `wasm::InterfaceType` and `wasm::InterfaceValue`. It enforces a new rule that is: All values in the stack must be owned by the stack. Any value going in or out must be cloned.
This commit is contained in:
parent
aea18f6250
commit
be5624e28b
@ -1,51 +1,56 @@
|
|||||||
use crate::instructions::{
|
use crate::instructions::{
|
||||||
stack::{Stack, Stackable},
|
stack::{Stack, Stackable},
|
||||||
wasm::{self, Type, Value},
|
wasm::{self, InterfaceType, InterfaceValue},
|
||||||
Instruction,
|
Instruction,
|
||||||
};
|
};
|
||||||
use std::{convert::TryFrom, marker::PhantomData};
|
use std::{cell::Cell, convert::TryFrom, marker::PhantomData};
|
||||||
|
|
||||||
struct Runtime<'invocation, 'instance, Instance, Export>
|
struct Runtime<'invocation, 'instance, Instance, Export, Memory>
|
||||||
where
|
where
|
||||||
Export: wasm::Export + 'instance,
|
Export: wasm::Export + 'instance,
|
||||||
Instance: wasm::Instance<Export> + 'instance,
|
Memory: wasm::Memory + 'instance,
|
||||||
|
Instance: wasm::Instance<Export, Memory> + 'instance,
|
||||||
{
|
{
|
||||||
invocation_inputs: &'invocation [Value],
|
invocation_inputs: &'invocation [InterfaceValue],
|
||||||
stack: Stack<Value>,
|
stack: Stack<InterfaceValue>,
|
||||||
wasm_instance: &'instance Instance,
|
wasm_instance: &'instance Instance,
|
||||||
wasm_exports: PhantomData<Export>,
|
wasm_exports: PhantomData<Export>,
|
||||||
|
wasm_memory: PhantomData<Memory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecutableInstruction<Instance, Export> =
|
type ExecutableInstruction<Instance, Export, Memory> =
|
||||||
Box<dyn Fn(&mut Runtime<Instance, Export>) -> Result<(), String>>;
|
Box<dyn Fn(&mut Runtime<Instance, Export, Memory>) -> Result<(), String>>;
|
||||||
|
|
||||||
pub struct Interpreter<Instance, Export>
|
pub struct Interpreter<Instance, Export, Memory>
|
||||||
where
|
where
|
||||||
Export: wasm::Export,
|
Export: wasm::Export,
|
||||||
Instance: wasm::Instance<Export>,
|
Memory: wasm::Memory,
|
||||||
|
Instance: wasm::Instance<Export, Memory>,
|
||||||
{
|
{
|
||||||
executable_instructions: Vec<ExecutableInstruction<Instance, Export>>,
|
executable_instructions: Vec<ExecutableInstruction<Instance, Export, Memory>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Instance, Export> Interpreter<Instance, Export>
|
impl<Instance, Export, Memory> Interpreter<Instance, Export, Memory>
|
||||||
where
|
where
|
||||||
Export: wasm::Export,
|
Export: wasm::Export,
|
||||||
Instance: wasm::Instance<Export>,
|
Memory: wasm::Memory,
|
||||||
|
Instance: wasm::Instance<Export, Memory>,
|
||||||
{
|
{
|
||||||
fn iter(&self) -> impl Iterator<Item = &ExecutableInstruction<Instance, Export>> + '_ {
|
fn iter(&self) -> impl Iterator<Item = &ExecutableInstruction<Instance, Export, Memory>> + '_ {
|
||||||
self.executable_instructions.iter()
|
self.executable_instructions.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
&self,
|
&self,
|
||||||
invocation_inputs: &[Value],
|
invocation_inputs: &[InterfaceValue],
|
||||||
wasm_instance: &Instance,
|
wasm_instance: &Instance,
|
||||||
) -> Result<Stack<Value>, String> {
|
) -> Result<Stack<InterfaceValue>, String> {
|
||||||
let mut runtime = Runtime {
|
let mut runtime = Runtime {
|
||||||
invocation_inputs,
|
invocation_inputs,
|
||||||
stack: Stack::new(),
|
stack: Stack::new(),
|
||||||
wasm_instance,
|
wasm_instance,
|
||||||
wasm_exports: PhantomData,
|
wasm_exports: PhantomData,
|
||||||
|
wasm_memory: PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
for executable_instruction in self.iter() {
|
for executable_instruction in self.iter() {
|
||||||
@ -59,11 +64,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'binary_input, Instance, Export> TryFrom<&Vec<Instruction<'binary_input>>>
|
impl<'binary_input, Instance, Export, Memory> TryFrom<&Vec<Instruction<'binary_input>>>
|
||||||
for Interpreter<Instance, Export>
|
for Interpreter<Instance, Export, Memory>
|
||||||
where
|
where
|
||||||
Export: wasm::Export,
|
Export: wasm::Export,
|
||||||
Instance: wasm::Instance<Export>,
|
Memory: wasm::Memory,
|
||||||
|
Instance: wasm::Instance<Export, Memory>,
|
||||||
{
|
{
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
@ -71,13 +77,13 @@ where
|
|||||||
let executable_instructions = instructions
|
let executable_instructions = instructions
|
||||||
.iter()
|
.iter()
|
||||||
.map(
|
.map(
|
||||||
|instruction| -> ExecutableInstruction<Instance, Export> {
|
|instruction| -> ExecutableInstruction<Instance, Export, Memory> {
|
||||||
match instruction {
|
match instruction {
|
||||||
Instruction::ArgumentGet(index) => {
|
Instruction::ArgumentGet(index) => {
|
||||||
let index = index.to_owned();
|
let index = index.to_owned();
|
||||||
let instruction_name: String = instruction.into();
|
let instruction_name: String = instruction.into();
|
||||||
|
|
||||||
Box::new(move |runtime: &mut Runtime<Instance, Export>| -> Result<(), _> {
|
Box::new(move |runtime: &mut Runtime<Instance, Export, Memory>| -> Result<(), _> {
|
||||||
let invocation_inputs = runtime.invocation_inputs;
|
let invocation_inputs = runtime.invocation_inputs;
|
||||||
|
|
||||||
if index >= (invocation_inputs.len() as u64) {
|
if index >= (invocation_inputs.len() as u64) {
|
||||||
@ -87,7 +93,7 @@ where
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.stack.push(invocation_inputs[index as usize]);
|
runtime.stack.push(invocation_inputs[index as usize].clone());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -96,7 +102,7 @@ where
|
|||||||
let export_name = (*export_name).to_owned();
|
let export_name = (*export_name).to_owned();
|
||||||
let instruction_name: String = instruction.into();
|
let instruction_name: String = instruction.into();
|
||||||
|
|
||||||
Box::new(move |runtime: &mut Runtime<Instance, Export>| -> Result<(), _> {
|
Box::new(move |runtime: &mut Runtime<Instance, Export, Memory>| -> Result<(), _> {
|
||||||
let instance = runtime.wasm_instance;
|
let instance = runtime.wasm_instance;
|
||||||
|
|
||||||
match instance.export(&export_name) {
|
match instance.export(&export_name) {
|
||||||
@ -108,11 +114,11 @@ where
|
|||||||
let input_types = inputs
|
let input_types = inputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|input| input.into())
|
.map(|input| input.into())
|
||||||
.collect::<Vec<Type>>();
|
.collect::<Vec<InterfaceType>>();
|
||||||
|
|
||||||
if input_types != export.inputs() {
|
if input_types != export.inputs() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"`{}` cannot call the exported function `{}` because the value types in the stack mismatch the function signature (expects {:?}).",
|
"`{}` cannot call the exported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).",
|
||||||
instruction_name,
|
instruction_name,
|
||||||
export_name,
|
export_name,
|
||||||
export.inputs(),
|
export.inputs(),
|
||||||
@ -122,7 +128,7 @@ where
|
|||||||
match export.call(&inputs) {
|
match export.call(&inputs) {
|
||||||
Ok(outputs) => {
|
Ok(outputs) => {
|
||||||
for output in outputs.iter() {
|
for output in outputs.iter() {
|
||||||
runtime.stack.push(*output);
|
runtime.stack.push(output.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -135,7 +141,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Err(format!(
|
None => Err(format!(
|
||||||
"`{}` cannot call the exported function `{}` because there is no enought data in the stack for the arguments (needs {}).",
|
"`{}` cannot call the exported function `{}` because there is no enough data on the stack for the arguments (needs {}).",
|
||||||
instruction_name,
|
instruction_name,
|
||||||
export_name,
|
export_name,
|
||||||
inputs_cardinality,
|
inputs_cardinality,
|
||||||
@ -151,16 +157,59 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
Instruction::ReadUtf8 => {
|
Instruction::ReadUtf8 => {
|
||||||
Box::new(|_runtime: &mut Runtime<Instance, Export>| -> Result<(), _> {
|
let instruction_name: String = instruction.into();
|
||||||
println!("read utf8");
|
|
||||||
|
|
||||||
Ok(())
|
Box::new(move |runtime: &mut Runtime<Instance, Export, Memory>| -> Result<(), _> {
|
||||||
|
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::<u8>();
|
||||||
|
|
||||||
|
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()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Vec<u8> = (&memory_view[pointer..pointer + length])
|
||||||
|
.iter()
|
||||||
|
.map(Cell::get)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match String::from_utf8(data) {
|
||||||
|
Ok(string) => {
|
||||||
|
runtime.stack.push(InterfaceValue::String(string));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(utf8_error) => Err(format!(
|
||||||
|
"`{}` failed because the read string isn't UTF-8 valid ({}).",
|
||||||
|
instruction_name,
|
||||||
|
utf8_error,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(format!(
|
||||||
|
"`{}` failed because there is no memory to read.",
|
||||||
|
instruction_name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
None => Err(format!(
|
||||||
|
"`{}` failed because there is no enough data on the stack (needs 2).",
|
||||||
|
instruction_name,
|
||||||
|
))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Instruction::Call(index) => {
|
Instruction::Call(index) => {
|
||||||
let index = index.to_owned();
|
let index = index.to_owned();
|
||||||
|
|
||||||
Box::new(move |_runtime: &mut Runtime<Instance, Export>| -> Result<(), _> {
|
Box::new(move |_runtime: &mut Runtime<Instance, Export, Memory>| -> Result<(), _> {
|
||||||
println!("call {}", index);
|
println!("call {}", index);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -183,15 +232,15 @@ mod tests {
|
|||||||
use super::Interpreter;
|
use super::Interpreter;
|
||||||
use crate::instructions::{
|
use crate::instructions::{
|
||||||
stack::Stackable,
|
stack::Stackable,
|
||||||
wasm::{self, Type, Value},
|
wasm::{self, InterfaceType, InterfaceValue},
|
||||||
Instruction,
|
Instruction,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, convert::TryInto};
|
use std::{cell::Cell, collections::HashMap, convert::TryInto};
|
||||||
|
|
||||||
struct Export {
|
struct Export {
|
||||||
inputs: Vec<Type>,
|
inputs: Vec<InterfaceType>,
|
||||||
outputs: Vec<Type>,
|
outputs: Vec<InterfaceType>,
|
||||||
function: fn(arguments: &[Value]) -> Result<Vec<Value>, ()>,
|
function: fn(arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl wasm::Export for Export {
|
impl wasm::Export for Export {
|
||||||
@ -203,21 +252,42 @@ mod tests {
|
|||||||
self.outputs.len()
|
self.outputs.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inputs(&self) -> &[Type] {
|
fn inputs(&self) -> &[InterfaceType] {
|
||||||
&self.inputs
|
&self.inputs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outputs(&self) -> &[Type] {
|
fn outputs(&self) -> &[InterfaceType] {
|
||||||
&self.outputs
|
&self.outputs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&self, arguments: &[Value]) -> Result<Vec<Value>, ()> {
|
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
|
||||||
(self.function)(arguments)
|
(self.function)(arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Memory {
|
||||||
|
data: Vec<Cell<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Memory {
|
||||||
|
fn new(data: Vec<Cell<u8>>) -> Self {
|
||||||
|
Self { data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wasm::Memory for Memory {
|
||||||
|
fn view<V: wasm::ValueType>(&self) -> &[Cell<V>] {
|
||||||
|
let slice = self.data.as_slice();
|
||||||
|
|
||||||
|
unsafe { ::std::slice::from_raw_parts(slice.as_ptr() as *const Cell<V>, slice.len()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct Instance {
|
struct Instance {
|
||||||
exports: HashMap<String, Export>,
|
exports: HashMap<String, Export>,
|
||||||
|
memory: Memory,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
@ -228,27 +298,32 @@ mod tests {
|
|||||||
hashmap.insert(
|
hashmap.insert(
|
||||||
"sum".into(),
|
"sum".into(),
|
||||||
Export {
|
Export {
|
||||||
inputs: vec![Type::I32, Type::I32],
|
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
outputs: vec![Type::I32],
|
outputs: vec![InterfaceType::I32],
|
||||||
function: |arguments: &[Value]| {
|
function: |arguments: &[InterfaceValue]| {
|
||||||
let a: i32 = (&arguments[0]).try_into().unwrap();
|
let a: i32 = (&arguments[0]).try_into().unwrap();
|
||||||
let b: i32 = (&arguments[1]).try_into().unwrap();
|
let b: i32 = (&arguments[1]).try_into().unwrap();
|
||||||
|
|
||||||
Ok(vec![Value::I32(a + b)])
|
Ok(vec![InterfaceValue::I32(a + b)])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
hashmap
|
hashmap
|
||||||
},
|
},
|
||||||
|
memory: Memory::new(vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl wasm::Instance<Export> for Instance {
|
impl wasm::Instance<Export, Memory> for Instance {
|
||||||
fn export(&self, export_name: &str) -> Option<&Export> {
|
fn export(&self, export_name: &str) -> Option<&Export> {
|
||||||
self.exports.get(export_name)
|
self.exports.get(export_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn memory(&self, _index: usize) -> Option<&Memory> {
|
||||||
|
Some(&self.memory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -260,17 +335,17 @@ mod tests {
|
|||||||
Instruction::ReadUtf8,
|
Instruction::ReadUtf8,
|
||||||
Instruction::Call(7),
|
Instruction::Call(7),
|
||||||
];
|
];
|
||||||
let interpreter: Interpreter<(), ()> = (&instructions).try_into().unwrap();
|
let interpreter: Interpreter<(), (), ()> = (&instructions).try_into().unwrap();
|
||||||
|
|
||||||
assert_eq!(interpreter.executable_instructions.len(), 5);
|
assert_eq!(interpreter.executable_instructions.len(), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_argument_get() {
|
fn test_interpreter_argument_get() {
|
||||||
let interpreter: Interpreter<Instance, Export> =
|
let interpreter: Interpreter<Instance, Export, Memory> =
|
||||||
(&vec![Instruction::ArgumentGet(0)]).try_into().unwrap();
|
(&vec![Instruction::ArgumentGet(0)]).try_into().unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(42)];
|
let invocation_inputs = vec![InterfaceValue::I32(42)];
|
||||||
let instance = Instance::new();
|
let instance = Instance::new();
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
@ -278,15 +353,15 @@ mod tests {
|
|||||||
|
|
||||||
let stack = run.unwrap();
|
let stack = run.unwrap();
|
||||||
|
|
||||||
assert_eq!(stack.as_slice(), &[Value::I32(42)]);
|
assert_eq!(stack.as_slice(), &[InterfaceValue::I32(42)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_argument_get_invalid_index() {
|
fn test_interpreter_argument_get_invalid_index() {
|
||||||
let interpreter: Interpreter<Instance, Export> =
|
let interpreter: Interpreter<Instance, Export, Memory> =
|
||||||
(&vec![Instruction::ArgumentGet(1)]).try_into().unwrap();
|
(&vec![Instruction::ArgumentGet(1)]).try_into().unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(42)];
|
let invocation_inputs = vec![InterfaceValue::I32(42)];
|
||||||
let instance = Instance::new();
|
let instance = Instance::new();
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
@ -302,12 +377,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_argument_get_argument_get() {
|
fn test_interpreter_argument_get_argument_get() {
|
||||||
let interpreter: Interpreter<Instance, Export> =
|
let interpreter: Interpreter<Instance, Export, Memory> =
|
||||||
(&vec![Instruction::ArgumentGet(0), Instruction::ArgumentGet(1)])
|
(&vec![Instruction::ArgumentGet(0), Instruction::ArgumentGet(1)])
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(7), Value::I32(42)];
|
let invocation_inputs = vec![InterfaceValue::I32(7), InterfaceValue::I32(42)];
|
||||||
let instance = Instance::new();
|
let instance = Instance::new();
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
@ -315,12 +390,15 @@ mod tests {
|
|||||||
|
|
||||||
let stack = run.unwrap();
|
let stack = run.unwrap();
|
||||||
|
|
||||||
assert_eq!(stack.as_slice(), &[Value::I32(7), Value::I32(42)]);
|
assert_eq!(
|
||||||
|
stack.as_slice(),
|
||||||
|
&[InterfaceValue::I32(7), InterfaceValue::I32(42)]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_call_export() {
|
fn test_interpreter_call_export() {
|
||||||
let interpreter: Interpreter<Instance, Export> = (&vec![
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
Instruction::ArgumentGet(1),
|
Instruction::ArgumentGet(1),
|
||||||
Instruction::ArgumentGet(0),
|
Instruction::ArgumentGet(0),
|
||||||
Instruction::CallExport("sum"),
|
Instruction::CallExport("sum"),
|
||||||
@ -328,7 +406,7 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(3), Value::I32(4)];
|
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
|
||||||
let instance = Instance::new();
|
let instance = Instance::new();
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
@ -336,12 +414,12 @@ mod tests {
|
|||||||
|
|
||||||
let stack = run.unwrap();
|
let stack = run.unwrap();
|
||||||
|
|
||||||
assert_eq!(stack.as_slice(), &[Value::I32(7)]);
|
assert_eq!(stack.as_slice(), &[InterfaceValue::I32(7)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_call_export_invalid_export_name() {
|
fn test_interpreter_call_export_invalid_export_name() {
|
||||||
let interpreter: Interpreter<Instance, Export> =
|
let interpreter: Interpreter<Instance, Export, Memory> =
|
||||||
(&vec![Instruction::CallExport("bar")]).try_into().unwrap();
|
(&vec![Instruction::CallExport("bar")]).try_into().unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![];
|
let invocation_inputs = vec![];
|
||||||
@ -359,16 +437,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_call_export_too_small_stack() {
|
fn test_interpreter_call_export_stack_is_too_small() {
|
||||||
let interpreter: Interpreter<Instance, Export> = (&vec![
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
Instruction::ArgumentGet(0),
|
Instruction::ArgumentGet(0),
|
||||||
Instruction::CallExport("sum"),
|
Instruction::CallExport("sum"),
|
||||||
// ^^^ `sum` expects 2 values in the stack, only one is present
|
// ^^^ `sum` expects 2 values on the stack, only one is present
|
||||||
])
|
])
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(3), Value::I32(4)];
|
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
|
||||||
let instance = Instance::new();
|
let instance = Instance::new();
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
@ -378,13 +456,13 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error,
|
error,
|
||||||
String::from(r#"`call-export "sum"` cannot call the exported function `sum` because there is no enought data in the stack for the arguments (needs 2)."#)
|
String::from(r#"`call-export "sum"` cannot call the exported function `sum` because there is no enough data on the stack for the arguments (needs 2)."#)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_call_export_invalid_types_in_the_stack() {
|
fn test_interpreter_call_export_invalid_types_in_the_stack() {
|
||||||
let interpreter: Interpreter<Instance, Export> = (&vec![
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
Instruction::ArgumentGet(1),
|
Instruction::ArgumentGet(1),
|
||||||
Instruction::ArgumentGet(0),
|
Instruction::ArgumentGet(0),
|
||||||
Instruction::CallExport("sum"),
|
Instruction::CallExport("sum"),
|
||||||
@ -392,7 +470,7 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(3), Value::I64(4)];
|
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I64(4)];
|
||||||
// ^^^ mismatch with `sum` signature
|
// ^^^ mismatch with `sum` signature
|
||||||
let instance = Instance::new();
|
let instance = Instance::new();
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
@ -403,13 +481,13 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error,
|
error,
|
||||||
String::from(r#"`call-export "sum"` cannot call the exported function `sum` because the value types in the stack mismatch the function signature (expects [I32, I32])."#)
|
String::from(r#"`call-export "sum"` cannot call the exported function `sum` because the value types on the stack mismatch the function signature (expects [I32, I32])."#)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_call_export_failed_when_calling() {
|
fn test_interpreter_call_export_failed_when_calling() {
|
||||||
let interpreter: Interpreter<Instance, Export> = (&vec![
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
Instruction::ArgumentGet(1),
|
Instruction::ArgumentGet(1),
|
||||||
Instruction::ArgumentGet(0),
|
Instruction::ArgumentGet(0),
|
||||||
Instruction::CallExport("sum"),
|
Instruction::CallExport("sum"),
|
||||||
@ -417,15 +495,15 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(3), Value::I32(4)];
|
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
|
||||||
let instance = Instance {
|
let instance = Instance {
|
||||||
exports: {
|
exports: {
|
||||||
let mut hashmap = HashMap::new();
|
let mut hashmap = HashMap::new();
|
||||||
hashmap.insert(
|
hashmap.insert(
|
||||||
"sum".into(),
|
"sum".into(),
|
||||||
Export {
|
Export {
|
||||||
inputs: vec![Type::I32, Type::I32],
|
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
outputs: vec![Type::I32],
|
outputs: vec![InterfaceType::I32],
|
||||||
function: |_| Err(()),
|
function: |_| Err(()),
|
||||||
// ^^^^^^^ function fails
|
// ^^^^^^^ function fails
|
||||||
},
|
},
|
||||||
@ -433,6 +511,7 @@ mod tests {
|
|||||||
|
|
||||||
hashmap
|
hashmap
|
||||||
},
|
},
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
@ -448,7 +527,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interpreter_call_export_that_returns_nothing() {
|
fn test_interpreter_call_export_that_returns_nothing() {
|
||||||
let interpreter: Interpreter<Instance, Export> = (&vec![
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
Instruction::ArgumentGet(1),
|
Instruction::ArgumentGet(1),
|
||||||
Instruction::ArgumentGet(0),
|
Instruction::ArgumentGet(0),
|
||||||
Instruction::CallExport("sum"),
|
Instruction::CallExport("sum"),
|
||||||
@ -456,15 +535,15 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let invocation_inputs = vec![Value::I32(3), Value::I32(4)];
|
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
|
||||||
let instance = Instance {
|
let instance = Instance {
|
||||||
exports: {
|
exports: {
|
||||||
let mut hashmap = HashMap::new();
|
let mut hashmap = HashMap::new();
|
||||||
hashmap.insert(
|
hashmap.insert(
|
||||||
"sum".into(),
|
"sum".into(),
|
||||||
Export {
|
Export {
|
||||||
inputs: vec![Type::I32, Type::I32],
|
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
outputs: vec![Type::I32],
|
outputs: vec![InterfaceType::I32],
|
||||||
function: |_| Ok(vec![]),
|
function: |_| Ok(vec![]),
|
||||||
// ^^^^^^^^^^ void function
|
// ^^^^^^^^^^ void function
|
||||||
},
|
},
|
||||||
@ -472,6 +551,7 @@ mod tests {
|
|||||||
|
|
||||||
hashmap
|
hashmap
|
||||||
},
|
},
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
let run = interpreter.run(&invocation_inputs, &instance);
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
@ -481,4 +561,128 @@ mod tests {
|
|||||||
|
|
||||||
assert!(stack.is_empty());
|
assert!(stack.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interpreter_read_utf8() {
|
||||||
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
|
Instruction::ArgumentGet(1),
|
||||||
|
Instruction::ArgumentGet(0),
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
])
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let invocation_inputs = vec![InterfaceValue::I32(13), InterfaceValue::I32(0)];
|
||||||
|
// ^^^^^^^ length ^^^^^^ pointer
|
||||||
|
let instance = Instance {
|
||||||
|
memory: Memory::new(
|
||||||
|
"Hello, World!"
|
||||||
|
.as_bytes()
|
||||||
|
.iter()
|
||||||
|
.map(|u| Cell::new(*u))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
|
assert!(run.is_ok());
|
||||||
|
|
||||||
|
let stack = run.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
stack.as_slice(),
|
||||||
|
&[InterfaceValue::String("Hello, World!".into())]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interpreter_read_utf8_out_of_memory() {
|
||||||
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
|
Instruction::ArgumentGet(1),
|
||||||
|
Instruction::ArgumentGet(0),
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
])
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let invocation_inputs = vec![InterfaceValue::I32(13), InterfaceValue::I32(0)];
|
||||||
|
// ^^^^^^^ length ^^^^^^ pointer
|
||||||
|
// is too long
|
||||||
|
let instance = Instance {
|
||||||
|
memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
|
assert!(run.is_err());
|
||||||
|
|
||||||
|
let error = run.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
String::from(
|
||||||
|
r#"`read-utf8` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interpreter_read_utf8_invalid_encoding() {
|
||||||
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
|
Instruction::ArgumentGet(1),
|
||||||
|
Instruction::ArgumentGet(0),
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
])
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let invocation_inputs = vec![InterfaceValue::I32(4), InterfaceValue::I32(0)];
|
||||||
|
// ^^^^^^ length ^^^^^^ pointer
|
||||||
|
let instance = Instance {
|
||||||
|
memory: Memory::new(
|
||||||
|
vec![0, 159, 146, 150]
|
||||||
|
.iter()
|
||||||
|
.map(|b| Cell::new(*b))
|
||||||
|
.collect::<Vec<Cell<u8>>>(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
|
assert!(run.is_err());
|
||||||
|
|
||||||
|
let error = run.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
String::from(r#"`read-utf8` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interpreter_read_utf8_stack_is_too_small() {
|
||||||
|
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||||
|
Instruction::ArgumentGet(0),
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
// ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present.
|
||||||
|
])
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
|
||||||
|
let instance = Instance::new();
|
||||||
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
|
assert!(run.is_err());
|
||||||
|
|
||||||
|
let error = run.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
String::from(
|
||||||
|
r#"`read-utf8` failed because there is no enough data on the stack (needs 2)."#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,14 @@ pub trait Stackable {
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Stack<T>
|
pub struct Stack<T>
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default + Clone,
|
||||||
{
|
{
|
||||||
inner: Vec<T>,
|
inner: Vec<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Stack<T>
|
impl<T> Stack<T>
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default + Clone,
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -29,7 +29,7 @@ where
|
|||||||
|
|
||||||
impl<T> Stackable for Stack<T>
|
impl<T> Stackable for Stack<T>
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default + Clone,
|
||||||
{
|
{
|
||||||
type Item = T;
|
type Item = T;
|
||||||
|
|
||||||
|
@ -1,55 +1,56 @@
|
|||||||
use std::convert::TryFrom;
|
use std::{cell::Cell, convert::TryFrom};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
pub use crate::ast::InterfaceType;
|
||||||
pub enum Type {
|
|
||||||
I32,
|
|
||||||
I64,
|
|
||||||
F32,
|
|
||||||
F64,
|
|
||||||
V128,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Value {
|
pub enum InterfaceValue {
|
||||||
|
Int(isize),
|
||||||
|
Float(f64),
|
||||||
|
Any(isize),
|
||||||
|
String(String),
|
||||||
|
// Seq(…),
|
||||||
I32(i32),
|
I32(i32),
|
||||||
I64(i64),
|
I64(i64),
|
||||||
F32(f32),
|
F32(f32),
|
||||||
F64(f64),
|
F64(f64),
|
||||||
V128(u128),
|
// AnyRef(…),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Value> for Type {
|
impl From<&InterfaceValue> for InterfaceType {
|
||||||
fn from(value: &Value) -> Self {
|
fn from(value: &InterfaceValue) -> Self {
|
||||||
match value {
|
match value {
|
||||||
Value::I32(_) => Type::I32,
|
InterfaceValue::Int(_) => Self::Int,
|
||||||
Value::I64(_) => Type::I64,
|
InterfaceValue::Float(_) => Self::Float,
|
||||||
Value::F32(_) => Type::F32,
|
InterfaceValue::Any(_) => Self::Any,
|
||||||
Value::F64(_) => Type::F64,
|
InterfaceValue::String(_) => Self::String,
|
||||||
Value::V128(_) => Type::V128,
|
InterfaceValue::I32(_) => Self::I32,
|
||||||
|
InterfaceValue::I64(_) => Self::I64,
|
||||||
|
InterfaceValue::F32(_) => Self::F32,
|
||||||
|
InterfaceValue::F64(_) => Self::F64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Value {
|
impl Default for InterfaceValue {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::I32(0)
|
Self::I32(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! from_x_for_value {
|
macro_rules! from_x_for_interface_value {
|
||||||
($native_type:ty, $value_variant:ident) => {
|
($native_type:ty, $value_variant:ident) => {
|
||||||
impl From<$native_type> for Value {
|
impl From<$native_type> for InterfaceValue {
|
||||||
fn from(n: $native_type) -> Self {
|
fn from(n: $native_type) -> Self {
|
||||||
Self::$value_variant(n)
|
Self::$value_variant(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&Value> for $native_type {
|
impl TryFrom<&InterfaceValue> for $native_type {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
||||||
fn try_from(w: &Value) -> Result<Self, Self::Error> {
|
fn try_from(w: &InterfaceValue) -> Result<Self, Self::Error> {
|
||||||
match *w {
|
match *w {
|
||||||
Value::$value_variant(n) => Ok(n),
|
InterfaceValue::$value_variant(n) => Ok(n),
|
||||||
_ => Err("Invalid cast."),
|
_ => Err("Invalid cast."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,25 +58,46 @@ macro_rules! from_x_for_value {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
from_x_for_value!(i32, I32);
|
from_x_for_interface_value!(i32, I32);
|
||||||
from_x_for_value!(i64, I64);
|
from_x_for_interface_value!(i64, I64);
|
||||||
from_x_for_value!(f32, F32);
|
from_x_for_interface_value!(f32, F32);
|
||||||
from_x_for_value!(f64, F64);
|
from_x_for_interface_value!(f64, F64);
|
||||||
from_x_for_value!(u128, V128);
|
|
||||||
|
pub trait ValueType: Copy + Sized {}
|
||||||
|
|
||||||
|
macro_rules! value_type {
|
||||||
|
($native_type:ty) => {
|
||||||
|
impl ValueType for $native_type {}
|
||||||
|
};
|
||||||
|
|
||||||
|
($($native_type:ty),*) => {
|
||||||
|
$(
|
||||||
|
value_type!($native_type);
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64);
|
||||||
|
|
||||||
pub trait Export {
|
pub trait Export {
|
||||||
fn inputs_cardinality(&self) -> usize;
|
fn inputs_cardinality(&self) -> usize;
|
||||||
fn outputs_cardinality(&self) -> usize;
|
fn outputs_cardinality(&self) -> usize;
|
||||||
fn inputs(&self) -> &[Type];
|
fn inputs(&self) -> &[InterfaceType];
|
||||||
fn outputs(&self) -> &[Type];
|
fn outputs(&self) -> &[InterfaceType];
|
||||||
fn call(&self, arguments: &[Value]) -> Result<Vec<Value>, ()>;
|
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Instance<E>
|
pub trait Memory {
|
||||||
|
fn view<V: ValueType>(&self) -> &[Cell<V>];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Instance<E, M>
|
||||||
where
|
where
|
||||||
E: Export,
|
E: Export,
|
||||||
|
M: Memory,
|
||||||
{
|
{
|
||||||
fn export(&self, export_name: &str) -> Option<&E>;
|
fn export(&self, export_name: &str) -> Option<&E>;
|
||||||
|
fn memory(&self, index: usize) -> Option<&M>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Export for () {
|
impl Export for () {
|
||||||
@ -87,24 +109,35 @@ impl Export for () {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inputs(&self) -> &[Type] {
|
fn inputs(&self) -> &[InterfaceType] {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outputs(&self) -> &[Type] {
|
fn outputs(&self) -> &[InterfaceType] {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&self, _arguments: &[Value]) -> Result<Vec<Value>, ()> {
|
fn call(&self, _arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> Instance<E> for ()
|
impl Memory for () {
|
||||||
|
fn view<V: ValueType>(&self) -> &[Cell<V>] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, M> Instance<E, M> for ()
|
||||||
where
|
where
|
||||||
E: Export,
|
E: Export,
|
||||||
|
M: Memory,
|
||||||
{
|
{
|
||||||
fn export(&self, _export_name: &str) -> Option<&E> {
|
fn export(&self, _export_name: &str) -> Option<&E> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn memory(&self, _: usize) -> Option<&M> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user