mirror of
https://github.com/fluencelabs/wasmer
synced 2025-03-18 17:10:52 +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::{
|
||||
stack::{Stack, Stackable},
|
||||
wasm::{self, Type, Value},
|
||||
wasm::{self, InterfaceType, InterfaceValue},
|
||||
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
|
||||
Export: wasm::Export + 'instance,
|
||||
Instance: wasm::Instance<Export> + 'instance,
|
||||
Memory: wasm::Memory + 'instance,
|
||||
Instance: wasm::Instance<Export, Memory> + 'instance,
|
||||
{
|
||||
invocation_inputs: &'invocation [Value],
|
||||
stack: Stack<Value>,
|
||||
invocation_inputs: &'invocation [InterfaceValue],
|
||||
stack: Stack<InterfaceValue>,
|
||||
wasm_instance: &'instance Instance,
|
||||
wasm_exports: PhantomData<Export>,
|
||||
wasm_memory: PhantomData<Memory>,
|
||||
}
|
||||
|
||||
type ExecutableInstruction<Instance, Export> =
|
||||
Box<dyn Fn(&mut Runtime<Instance, Export>) -> Result<(), String>>;
|
||||
type ExecutableInstruction<Instance, Export, Memory> =
|
||||
Box<dyn Fn(&mut Runtime<Instance, Export, Memory>) -> Result<(), String>>;
|
||||
|
||||
pub struct Interpreter<Instance, Export>
|
||||
pub struct Interpreter<Instance, Export, Memory>
|
||||
where
|
||||
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
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
&self,
|
||||
invocation_inputs: &[Value],
|
||||
invocation_inputs: &[InterfaceValue],
|
||||
wasm_instance: &Instance,
|
||||
) -> Result<Stack<Value>, String> {
|
||||
) -> Result<Stack<InterfaceValue>, String> {
|
||||
let mut runtime = Runtime {
|
||||
invocation_inputs,
|
||||
stack: Stack::new(),
|
||||
wasm_instance,
|
||||
wasm_exports: PhantomData,
|
||||
wasm_memory: PhantomData,
|
||||
};
|
||||
|
||||
for executable_instruction in self.iter() {
|
||||
@ -59,11 +64,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'binary_input, Instance, Export> TryFrom<&Vec<Instruction<'binary_input>>>
|
||||
for Interpreter<Instance, Export>
|
||||
impl<'binary_input, Instance, Export, Memory> TryFrom<&Vec<Instruction<'binary_input>>>
|
||||
for Interpreter<Instance, Export, Memory>
|
||||
where
|
||||
Export: wasm::Export,
|
||||
Instance: wasm::Instance<Export>,
|
||||
Memory: wasm::Memory,
|
||||
Instance: wasm::Instance<Export, Memory>,
|
||||
{
|
||||
type Error = String;
|
||||
|
||||
@ -71,13 +77,13 @@ where
|
||||
let executable_instructions = instructions
|
||||
.iter()
|
||||
.map(
|
||||
|instruction| -> ExecutableInstruction<Instance, Export> {
|
||||
|instruction| -> ExecutableInstruction<Instance, Export, Memory> {
|
||||
match instruction {
|
||||
Instruction::ArgumentGet(index) => {
|
||||
let index = index.to_owned();
|
||||
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;
|
||||
|
||||
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(())
|
||||
})
|
||||
@ -96,7 +102,7 @@ where
|
||||
let export_name = (*export_name).to_owned();
|
||||
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;
|
||||
|
||||
match instance.export(&export_name) {
|
||||
@ -108,11 +114,11 @@ where
|
||||
let input_types = inputs
|
||||
.iter()
|
||||
.map(|input| input.into())
|
||||
.collect::<Vec<Type>>();
|
||||
.collect::<Vec<InterfaceType>>();
|
||||
|
||||
if input_types != export.inputs() {
|
||||
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,
|
||||
export_name,
|
||||
export.inputs(),
|
||||
@ -122,7 +128,7 @@ where
|
||||
match export.call(&inputs) {
|
||||
Ok(outputs) => {
|
||||
for output in outputs.iter() {
|
||||
runtime.stack.push(*output);
|
||||
runtime.stack.push(output.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -135,7 +141,7 @@ where
|
||||
}
|
||||
}
|
||||
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,
|
||||
export_name,
|
||||
inputs_cardinality,
|
||||
@ -151,16 +157,59 @@ where
|
||||
})
|
||||
}
|
||||
Instruction::ReadUtf8 => {
|
||||
Box::new(|_runtime: &mut Runtime<Instance, Export>| -> Result<(), _> {
|
||||
println!("read utf8");
|
||||
let instruction_name: String = instruction.into();
|
||||
|
||||
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) => {
|
||||
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);
|
||||
|
||||
Ok(())
|
||||
@ -183,15 +232,15 @@ mod tests {
|
||||
use super::Interpreter;
|
||||
use crate::instructions::{
|
||||
stack::Stackable,
|
||||
wasm::{self, Type, Value},
|
||||
wasm::{self, InterfaceType, InterfaceValue},
|
||||
Instruction,
|
||||
};
|
||||
use std::{collections::HashMap, convert::TryInto};
|
||||
use std::{cell::Cell, collections::HashMap, convert::TryInto};
|
||||
|
||||
struct Export {
|
||||
inputs: Vec<Type>,
|
||||
outputs: Vec<Type>,
|
||||
function: fn(arguments: &[Value]) -> Result<Vec<Value>, ()>,
|
||||
inputs: Vec<InterfaceType>,
|
||||
outputs: Vec<InterfaceType>,
|
||||
function: fn(arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>,
|
||||
}
|
||||
|
||||
impl wasm::Export for Export {
|
||||
@ -203,21 +252,42 @@ mod tests {
|
||||
self.outputs.len()
|
||||
}
|
||||
|
||||
fn inputs(&self) -> &[Type] {
|
||||
fn inputs(&self) -> &[InterfaceType] {
|
||||
&self.inputs
|
||||
}
|
||||
|
||||
fn outputs(&self) -> &[Type] {
|
||||
fn outputs(&self) -> &[InterfaceType] {
|
||||
&self.outputs
|
||||
}
|
||||
|
||||
fn call(&self, arguments: &[Value]) -> Result<Vec<Value>, ()> {
|
||||
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
|
||||
(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 {
|
||||
exports: HashMap<String, Export>,
|
||||
memory: Memory,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
@ -228,27 +298,32 @@ mod tests {
|
||||
hashmap.insert(
|
||||
"sum".into(),
|
||||
Export {
|
||||
inputs: vec![Type::I32, Type::I32],
|
||||
outputs: vec![Type::I32],
|
||||
function: |arguments: &[Value]| {
|
||||
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||
outputs: vec![InterfaceType::I32],
|
||||
function: |arguments: &[InterfaceValue]| {
|
||||
let a: i32 = (&arguments[0]).try_into().unwrap();
|
||||
let b: i32 = (&arguments[1]).try_into().unwrap();
|
||||
|
||||
Ok(vec![Value::I32(a + b)])
|
||||
Ok(vec![InterfaceValue::I32(a + b)])
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
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> {
|
||||
self.exports.get(export_name)
|
||||
}
|
||||
|
||||
fn memory(&self, _index: usize) -> Option<&Memory> {
|
||||
Some(&self.memory)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -260,17 +335,17 @@ mod tests {
|
||||
Instruction::ReadUtf8,
|
||||
Instruction::Call(7),
|
||||
];
|
||||
let interpreter: Interpreter<(), ()> = (&instructions).try_into().unwrap();
|
||||
let interpreter: Interpreter<(), (), ()> = (&instructions).try_into().unwrap();
|
||||
|
||||
assert_eq!(interpreter.executable_instructions.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interpreter_argument_get() {
|
||||
let interpreter: Interpreter<Instance, Export> =
|
||||
let interpreter: Interpreter<Instance, Export, Memory> =
|
||||
(&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 run = interpreter.run(&invocation_inputs, &instance);
|
||||
|
||||
@ -278,15 +353,15 @@ mod tests {
|
||||
|
||||
let stack = run.unwrap();
|
||||
|
||||
assert_eq!(stack.as_slice(), &[Value::I32(42)]);
|
||||
assert_eq!(stack.as_slice(), &[InterfaceValue::I32(42)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
|
||||
let invocation_inputs = vec![Value::I32(42)];
|
||||
let invocation_inputs = vec![InterfaceValue::I32(42)];
|
||||
let instance = Instance::new();
|
||||
let run = interpreter.run(&invocation_inputs, &instance);
|
||||
|
||||
@ -302,12 +377,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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)])
|
||||
.try_into()
|
||||
.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 run = interpreter.run(&invocation_inputs, &instance);
|
||||
|
||||
@ -315,12 +390,15 @@ mod tests {
|
||||
|
||||
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]
|
||||
fn test_interpreter_call_export() {
|
||||
let interpreter: Interpreter<Instance, Export> = (&vec![
|
||||
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||
Instruction::ArgumentGet(1),
|
||||
Instruction::ArgumentGet(0),
|
||||
Instruction::CallExport("sum"),
|
||||
@ -328,7 +406,7 @@ mod tests {
|
||||
.try_into()
|
||||
.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 run = interpreter.run(&invocation_inputs, &instance);
|
||||
|
||||
@ -336,12 +414,12 @@ mod tests {
|
||||
|
||||
let stack = run.unwrap();
|
||||
|
||||
assert_eq!(stack.as_slice(), &[Value::I32(7)]);
|
||||
assert_eq!(stack.as_slice(), &[InterfaceValue::I32(7)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
|
||||
let invocation_inputs = vec![];
|
||||
@ -359,16 +437,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interpreter_call_export_too_small_stack() {
|
||||
let interpreter: Interpreter<Instance, Export> = (&vec![
|
||||
fn test_interpreter_call_export_stack_is_too_small() {
|
||||
let interpreter: Interpreter<Instance, Export, Memory> = (&vec![
|
||||
Instruction::ArgumentGet(0),
|
||||
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()
|
||||
.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 run = interpreter.run(&invocation_inputs, &instance);
|
||||
|
||||
@ -378,13 +456,13 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
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]
|
||||
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(0),
|
||||
Instruction::CallExport("sum"),
|
||||
@ -392,7 +470,7 @@ mod tests {
|
||||
.try_into()
|
||||
.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
|
||||
let instance = Instance::new();
|
||||
let run = interpreter.run(&invocation_inputs, &instance);
|
||||
@ -403,13 +481,13 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
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]
|
||||
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(0),
|
||||
Instruction::CallExport("sum"),
|
||||
@ -417,15 +495,15 @@ mod tests {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let invocation_inputs = vec![Value::I32(3), Value::I32(4)];
|
||||
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
|
||||
let instance = Instance {
|
||||
exports: {
|
||||
let mut hashmap = HashMap::new();
|
||||
hashmap.insert(
|
||||
"sum".into(),
|
||||
Export {
|
||||
inputs: vec![Type::I32, Type::I32],
|
||||
outputs: vec![Type::I32],
|
||||
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||
outputs: vec![InterfaceType::I32],
|
||||
function: |_| Err(()),
|
||||
// ^^^^^^^ function fails
|
||||
},
|
||||
@ -433,6 +511,7 @@ mod tests {
|
||||
|
||||
hashmap
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let run = interpreter.run(&invocation_inputs, &instance);
|
||||
|
||||
@ -448,7 +527,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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(0),
|
||||
Instruction::CallExport("sum"),
|
||||
@ -456,15 +535,15 @@ mod tests {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let invocation_inputs = vec![Value::I32(3), Value::I32(4)];
|
||||
let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
|
||||
let instance = Instance {
|
||||
exports: {
|
||||
let mut hashmap = HashMap::new();
|
||||
hashmap.insert(
|
||||
"sum".into(),
|
||||
Export {
|
||||
inputs: vec![Type::I32, Type::I32],
|
||||
outputs: vec![Type::I32],
|
||||
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||
outputs: vec![InterfaceType::I32],
|
||||
function: |_| Ok(vec![]),
|
||||
// ^^^^^^^^^^ void function
|
||||
},
|
||||
@ -472,6 +551,7 @@ mod tests {
|
||||
|
||||
hashmap
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let run = interpreter.run(&invocation_inputs, &instance);
|
||||
|
||||
@ -481,4 +561,128 @@ mod tests {
|
||||
|
||||
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)]
|
||||
pub struct Stack<T>
|
||||
where
|
||||
T: Default,
|
||||
T: Default + Clone,
|
||||
{
|
||||
inner: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Stack<T>
|
||||
where
|
||||
T: Default,
|
||||
T: Default + Clone,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -29,7 +29,7 @@ where
|
||||
|
||||
impl<T> Stackable for Stack<T>
|
||||
where
|
||||
T: Default,
|
||||
T: Default + Clone,
|
||||
{
|
||||
type Item = T;
|
||||
|
||||
|
@ -1,55 +1,56 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::{cell::Cell, convert::TryFrom};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Type {
|
||||
I32,
|
||||
I64,
|
||||
F32,
|
||||
F64,
|
||||
V128,
|
||||
}
|
||||
pub use crate::ast::InterfaceType;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum Value {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum InterfaceValue {
|
||||
Int(isize),
|
||||
Float(f64),
|
||||
Any(isize),
|
||||
String(String),
|
||||
// Seq(…),
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
V128(u128),
|
||||
// AnyRef(…),
|
||||
}
|
||||
|
||||
impl From<&Value> for Type {
|
||||
fn from(value: &Value) -> Self {
|
||||
impl From<&InterfaceValue> for InterfaceType {
|
||||
fn from(value: &InterfaceValue) -> Self {
|
||||
match value {
|
||||
Value::I32(_) => Type::I32,
|
||||
Value::I64(_) => Type::I64,
|
||||
Value::F32(_) => Type::F32,
|
||||
Value::F64(_) => Type::F64,
|
||||
Value::V128(_) => Type::V128,
|
||||
InterfaceValue::Int(_) => Self::Int,
|
||||
InterfaceValue::Float(_) => Self::Float,
|
||||
InterfaceValue::Any(_) => Self::Any,
|
||||
InterfaceValue::String(_) => Self::String,
|
||||
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 {
|
||||
Self::I32(0)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_x_for_value {
|
||||
macro_rules! from_x_for_interface_value {
|
||||
($native_type:ty, $value_variant:ident) => {
|
||||
impl From<$native_type> for Value {
|
||||
impl From<$native_type> for InterfaceValue {
|
||||
fn from(n: $native_type) -> Self {
|
||||
Self::$value_variant(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Value> for $native_type {
|
||||
impl TryFrom<&InterfaceValue> for $native_type {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(w: &Value) -> Result<Self, Self::Error> {
|
||||
fn try_from(w: &InterfaceValue) -> Result<Self, Self::Error> {
|
||||
match *w {
|
||||
Value::$value_variant(n) => Ok(n),
|
||||
InterfaceValue::$value_variant(n) => Ok(n),
|
||||
_ => Err("Invalid cast."),
|
||||
}
|
||||
}
|
||||
@ -57,25 +58,46 @@ macro_rules! from_x_for_value {
|
||||
};
|
||||
}
|
||||
|
||||
from_x_for_value!(i32, I32);
|
||||
from_x_for_value!(i64, I64);
|
||||
from_x_for_value!(f32, F32);
|
||||
from_x_for_value!(f64, F64);
|
||||
from_x_for_value!(u128, V128);
|
||||
from_x_for_interface_value!(i32, I32);
|
||||
from_x_for_interface_value!(i64, I64);
|
||||
from_x_for_interface_value!(f32, F32);
|
||||
from_x_for_interface_value!(f64, F64);
|
||||
|
||||
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 {
|
||||
fn inputs_cardinality(&self) -> usize;
|
||||
fn outputs_cardinality(&self) -> usize;
|
||||
fn inputs(&self) -> &[Type];
|
||||
fn outputs(&self) -> &[Type];
|
||||
fn call(&self, arguments: &[Value]) -> Result<Vec<Value>, ()>;
|
||||
fn inputs(&self) -> &[InterfaceType];
|
||||
fn outputs(&self) -> &[InterfaceType];
|
||||
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
|
||||
E: Export,
|
||||
M: Memory,
|
||||
{
|
||||
fn export(&self, export_name: &str) -> Option<&E>;
|
||||
fn memory(&self, index: usize) -> Option<&M>;
|
||||
}
|
||||
|
||||
impl Export for () {
|
||||
@ -87,24 +109,35 @@ impl Export for () {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Instance<E> for ()
|
||||
impl Memory for () {
|
||||
fn view<V: ValueType>(&self) -> &[Cell<V>] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, M> Instance<E, M> for ()
|
||||
where
|
||||
E: Export,
|
||||
M: Memory,
|
||||
{
|
||||
fn export(&self, _export_name: &str) -> Option<&E> {
|
||||
None
|
||||
}
|
||||
|
||||
fn memory(&self, _: usize) -> Option<&M> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user