mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-28 08:01:33 +00:00
feat(interface-types) Split the interpreter into multiple modules/files.
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
use crate::instructions::Instruction;
|
use crate::interpreter::Instruction;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{ast::*, instructions::Instruction};
|
use crate::{ast::*, interpreter::Instruction};
|
||||||
use nom::{
|
use nom::{
|
||||||
error::{make_error, ErrorKind, ParseError},
|
error::{make_error, ErrorKind, ParseError},
|
||||||
Err, IResult,
|
Err, IResult,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ast::{Adapter, Export, Forward, ImportedFunction, InterfaceType, Interfaces, Type},
|
ast::{Adapter, Export, Forward, ImportedFunction, InterfaceType, Interfaces, Type},
|
||||||
instructions::Instruction,
|
interpreter::Instruction,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl From<&InterfaceType> for String {
|
impl From<&InterfaceType> for String {
|
||||||
@ -270,7 +270,7 @@ impl<'input> From<&Interfaces<'input>> for String {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{ast::*, instructions::Instruction};
|
use crate::{ast::*, interpreter::Instruction};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_interface_types() {
|
fn test_interface_types() {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,5 @@
|
|||||||
use crate::ast::InterfaceType;
|
use crate::ast::InterfaceType;
|
||||||
|
|
||||||
pub mod interpreter;
|
|
||||||
mod stack;
|
|
||||||
pub mod wasm;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum Instruction<'input> {
|
pub enum Instruction<'input> {
|
||||||
ArgumentGet { index: u64 },
|
ArgumentGet { index: u64 },
|
@ -0,0 +1,54 @@
|
|||||||
|
executable_instruction!(
|
||||||
|
argument_get(index: u64, instruction_name: String) -> _ {
|
||||||
|
Box::new(move |runtime| -> _ {
|
||||||
|
let invocation_inputs = runtime.invocation_inputs;
|
||||||
|
|
||||||
|
if index >= (invocation_inputs.len() as u64) {
|
||||||
|
return Err(format!(
|
||||||
|
"`{}` cannot access argument #{} because it doesn't exist.",
|
||||||
|
instruction_name, index
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.stack.push(invocation_inputs[index as usize].clone());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_argument_get =
|
||||||
|
instructions: [Instruction::ArgumentGet { index: 0 }],
|
||||||
|
invocation_inputs: [InterfaceValue::I32(42)],
|
||||||
|
instance: Instance::new(),
|
||||||
|
stack: [InterfaceValue::I32(42)],
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_argument_get__twice =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(7),
|
||||||
|
InterfaceValue::I32(42),
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
stack: [
|
||||||
|
InterfaceValue::I32(7),
|
||||||
|
InterfaceValue::I32(42),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_argument_get__invalid_index =
|
||||||
|
instructions: [Instruction::ArgumentGet { index: 1 }],
|
||||||
|
invocation_inputs: [InterfaceValue::I32(42)],
|
||||||
|
instance: Instance::new(),
|
||||||
|
error: "`arg.get 1` cannot access argument #1 because it doesn't exist."
|
||||||
|
);
|
||||||
|
}
|
187
lib/interface-types/src/interpreter/instructions/call.rs
Normal file
187
lib/interface-types/src/interpreter/instructions/call.rs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
use crate::interpreter::wasm::{
|
||||||
|
structures::{FunctionIndex, TypedIndex},
|
||||||
|
values::InterfaceType,
|
||||||
|
};
|
||||||
|
|
||||||
|
executable_instruction!(
|
||||||
|
call(function_index: usize, instruction_name: String) -> _ {
|
||||||
|
Box::new(move |runtime| -> _ {
|
||||||
|
let instance = runtime.wasm_instance;
|
||||||
|
let index = FunctionIndex::new(function_index);
|
||||||
|
|
||||||
|
match instance.local_or_import(index) {
|
||||||
|
Some(local_or_import) => {
|
||||||
|
let inputs_cardinality = local_or_import.inputs_cardinality();
|
||||||
|
|
||||||
|
match runtime.stack.pop(inputs_cardinality) {
|
||||||
|
Some(inputs) => {
|
||||||
|
let input_types = inputs
|
||||||
|
.iter()
|
||||||
|
.map(|input| input.into())
|
||||||
|
.collect::<Vec<InterfaceType>>();
|
||||||
|
|
||||||
|
if input_types != local_or_import.inputs() {
|
||||||
|
return Err(format!(
|
||||||
|
"`{}` cannot call the local or imported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).",
|
||||||
|
instruction_name,
|
||||||
|
function_index,
|
||||||
|
local_or_import.inputs(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
match local_or_import.call(&inputs) {
|
||||||
|
Ok(outputs) => {
|
||||||
|
for output in outputs.iter() {
|
||||||
|
runtime.stack.push(output.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(_) => Err(format!(
|
||||||
|
"`{}` failed when calling the local or imported function `{}`.",
|
||||||
|
instruction_name,
|
||||||
|
function_index
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(format!(
|
||||||
|
"`{}` cannot call the local or imported function `{}` because there is not enough data on the stack for the arguments (needs {}).",
|
||||||
|
instruction_name,
|
||||||
|
function_index,
|
||||||
|
inputs_cardinality,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(format!(
|
||||||
|
"`{}` cannot call the local or imported function `{}` because it doesn't exist.",
|
||||||
|
instruction_name,
|
||||||
|
function_index,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::Call { function_index: 42 },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
stack: [InterfaceValue::I32(12)],
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call__invalid_local_import_index =
|
||||||
|
instructions: [
|
||||||
|
Instruction::Call { function_index: 42 },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance { ..Default::default() },
|
||||||
|
error: r#"`call 42` cannot call the local or imported function `42` because it doesn't exist."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call__stack_is_too_small =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::Call { function_index: 42 },
|
||||||
|
// ^^ `42` expects 2 values on the stack, only one is present
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
error: r#"`call 42` cannot call the local or imported function `42` because there is not enough data on the stack for the arguments (needs 2)."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call__invalid_types_in_the_stack =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::Call { function_index: 42 },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I64(4),
|
||||||
|
// ^^^ mismatch with `42` signature
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
error: r#"`call 42` cannot call the local or imported function `42` because the value types on the stack mismatch the function signature (expects [I32, I32])."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call__failure_when_calling =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::Call { function_index: 42 },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance {
|
||||||
|
locals_or_imports: {
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
hashmap.insert(
|
||||||
|
42,
|
||||||
|
LocalImport {
|
||||||
|
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
|
outputs: vec![InterfaceType::I32],
|
||||||
|
function: |_| Err(()),
|
||||||
|
// ^^^^^^^ function fails
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
hashmap
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
error: r#"`call 42` failed when calling the local or imported function `42`."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call__void =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::Call { function_index: 42 },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance {
|
||||||
|
locals_or_imports: {
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
hashmap.insert(
|
||||||
|
42,
|
||||||
|
LocalImport {
|
||||||
|
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
|
outputs: vec![InterfaceType::I32],
|
||||||
|
function: |_| Ok(vec![]),
|
||||||
|
// ^^^^^^^^^^ void fails
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
hashmap
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
stack: [],
|
||||||
|
);
|
||||||
|
}
|
177
lib/interface-types/src/interpreter/instructions/call_export.rs
Normal file
177
lib/interface-types/src/interpreter/instructions/call_export.rs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
use crate::interpreter::wasm::values::InterfaceType;
|
||||||
|
|
||||||
|
executable_instruction!(
|
||||||
|
call_export(export_name: String, instruction_name: String) -> _ {
|
||||||
|
Box::new(move |runtime| -> _ {
|
||||||
|
let instance = runtime.wasm_instance;
|
||||||
|
|
||||||
|
match instance.export(&export_name) {
|
||||||
|
Some(export) => {
|
||||||
|
let inputs_cardinality = export.inputs_cardinality();
|
||||||
|
|
||||||
|
match runtime.stack.pop(inputs_cardinality) {
|
||||||
|
Some(inputs) => {
|
||||||
|
let input_types = inputs
|
||||||
|
.iter()
|
||||||
|
.map(|input| input.into())
|
||||||
|
.collect::<Vec<InterfaceType>>();
|
||||||
|
|
||||||
|
if input_types != export.inputs() {
|
||||||
|
return Err(format!(
|
||||||
|
"`{}` cannot call the exported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).",
|
||||||
|
instruction_name,
|
||||||
|
export_name,
|
||||||
|
export.inputs(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
match export.call(&inputs) {
|
||||||
|
Ok(outputs) => {
|
||||||
|
for output in outputs.iter() {
|
||||||
|
runtime.stack.push(output.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(_) => Err(format!(
|
||||||
|
"`{}` failed when calling the exported function `{}`.",
|
||||||
|
instruction_name,
|
||||||
|
export_name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(format!(
|
||||||
|
"`{}` cannot call the exported function `{}` because there is not enough data on the stack for the arguments (needs {}).",
|
||||||
|
instruction_name,
|
||||||
|
export_name,
|
||||||
|
inputs_cardinality,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(format!(
|
||||||
|
"`{}` cannot call the exported function `{}` because it doesn't exist.",
|
||||||
|
instruction_name,
|
||||||
|
export_name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call_export =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::CallExport { export_name: "sum" },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
stack: [InterfaceValue::I32(7)],
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call_export__invalid_export_name =
|
||||||
|
instructions: [Instruction::CallExport { export_name: "bar" }],
|
||||||
|
invocation_inputs: [],
|
||||||
|
instance: Instance::new(),
|
||||||
|
error: r#"`call-export "bar"` cannot call the exported function `bar` because it doesn't exist."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call_export__stack_is_too_small =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::CallExport { export_name: "sum" },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
error: r#"`call-export "sum"` cannot call the exported function `sum` because there is not enough data on the stack for the arguments (needs 2)."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call_export__invalid_types_in_the_stack =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::CallExport { export_name: "sum" },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I64(4),
|
||||||
|
// ^^^ mismatch with `sum` signature
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
error: 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_executable_instruction!(
|
||||||
|
test_call_export__failure_when_calling =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::CallExport { export_name: "sum" },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance {
|
||||||
|
exports: {
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
hashmap.insert(
|
||||||
|
"sum".into(),
|
||||||
|
Export {
|
||||||
|
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
|
outputs: vec![InterfaceType::I32],
|
||||||
|
function: |_| Err(()),
|
||||||
|
// ^^^^^^^ function fails
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
hashmap
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
error: r#"`call-export "sum"` failed when calling the exported function `sum`."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_call_export__void =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::CallExport { export_name: "sum" },
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(3),
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
],
|
||||||
|
instance: Instance {
|
||||||
|
exports: {
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
hashmap.insert(
|
||||||
|
"sum".into(),
|
||||||
|
Export {
|
||||||
|
inputs: vec![InterfaceType::I32, InterfaceType::I32],
|
||||||
|
outputs: vec![InterfaceType::I32],
|
||||||
|
function: |_| Ok(vec![]),
|
||||||
|
// ^^^^^^^^^^ void function
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
hashmap
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
stack: [],
|
||||||
|
);
|
||||||
|
}
|
177
lib/interface-types/src/interpreter/instructions/mod.rs
Normal file
177
lib/interface-types/src/interpreter/instructions/mod.rs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
mod argument_get;
|
||||||
|
mod call;
|
||||||
|
mod call_export;
|
||||||
|
mod read_utf8;
|
||||||
|
mod write_utf8;
|
||||||
|
|
||||||
|
pub(crate) use argument_get::argument_get;
|
||||||
|
pub(crate) use call::call;
|
||||||
|
pub(crate) use call_export::call_export;
|
||||||
|
pub(crate) use read_utf8::read_utf8;
|
||||||
|
pub(crate) use write_utf8::write_utf8;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use crate::interpreter::wasm::{
|
||||||
|
self,
|
||||||
|
values::{InterfaceType, InterfaceValue, ValueType},
|
||||||
|
};
|
||||||
|
use std::{cell::Cell, collections::HashMap, convert::TryInto};
|
||||||
|
|
||||||
|
pub(crate) struct Export {
|
||||||
|
pub(crate) inputs: Vec<InterfaceType>,
|
||||||
|
pub(crate) outputs: Vec<InterfaceType>,
|
||||||
|
pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wasm::structures::Export for Export {
|
||||||
|
fn inputs_cardinality(&self) -> usize {
|
||||||
|
self.inputs.len() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs_cardinality(&self) -> usize {
|
||||||
|
self.outputs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(&self) -> &[InterfaceType] {
|
||||||
|
&self.inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(&self) -> &[InterfaceType] {
|
||||||
|
&self.outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
|
||||||
|
(self.function)(arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct LocalImport {
|
||||||
|
pub(crate) inputs: Vec<InterfaceType>,
|
||||||
|
pub(crate) outputs: Vec<InterfaceType>,
|
||||||
|
pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wasm::structures::LocalImport for LocalImport {
|
||||||
|
fn inputs_cardinality(&self) -> usize {
|
||||||
|
self.inputs.len() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs_cardinality(&self) -> usize {
|
||||||
|
self.outputs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(&self) -> &[InterfaceType] {
|
||||||
|
&self.inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(&self) -> &[InterfaceType] {
|
||||||
|
&self.outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
|
||||||
|
(self.function)(arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct Memory {
|
||||||
|
pub(crate) data: Vec<Cell<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Memory {
|
||||||
|
pub(crate) fn new(data: Vec<Cell<u8>>) -> Self {
|
||||||
|
Self { data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wasm::structures::Memory for Memory {
|
||||||
|
fn view<V: ValueType>(&self) -> &[Cell<V>] {
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
let slice = self.data.as_slice();
|
||||||
|
|
||||||
|
unsafe { slice::from_raw_parts(slice.as_ptr() as *const Cell<V>, slice.len()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct Instance {
|
||||||
|
pub(crate) exports: HashMap<String, Export>,
|
||||||
|
pub(crate) locals_or_imports: HashMap<usize, LocalImport>,
|
||||||
|
pub(crate) memory: Memory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
exports: {
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
hashmap.insert(
|
||||||
|
"sum".into(),
|
||||||
|
Export {
|
||||||
|
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![InterfaceValue::I32(a + b)])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
hashmap.insert(
|
||||||
|
"alloc".into(),
|
||||||
|
Export {
|
||||||
|
inputs: vec![InterfaceType::I32],
|
||||||
|
outputs: vec![InterfaceType::I32],
|
||||||
|
function: |arguments: &[InterfaceValue]| {
|
||||||
|
let _size: i32 = (&arguments[0]).try_into().unwrap();
|
||||||
|
|
||||||
|
Ok(vec![InterfaceValue::I32(0)])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
hashmap
|
||||||
|
},
|
||||||
|
locals_or_imports: {
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
hashmap.insert(
|
||||||
|
42,
|
||||||
|
LocalImport {
|
||||||
|
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![InterfaceValue::I32(a * b)])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
hashmap
|
||||||
|
},
|
||||||
|
memory: Memory::new(vec![Cell::new(0); 128]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wasm::structures::Instance<Export, LocalImport, Memory> for Instance {
|
||||||
|
fn export(&self, export_name: &str) -> Option<&Export> {
|
||||||
|
self.exports.get(export_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_or_import<I: wasm::structures::TypedIndex + wasm::structures::LocalImportIndex>(
|
||||||
|
&self,
|
||||||
|
index: I,
|
||||||
|
) -> Option<&LocalImport> {
|
||||||
|
self.locals_or_imports.get(&index.index())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory(&self, _index: usize) -> Option<&Memory> {
|
||||||
|
Some(&self.memory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
lib/interface-types/src/interpreter/instructions/read_utf8.rs
Normal file
131
lib/interface-types/src/interpreter/instructions/read_utf8.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use crate::interpreter::wasm::values::InterfaceValue;
|
||||||
|
use std::{cell::Cell, convert::TryFrom};
|
||||||
|
|
||||||
|
executable_instruction!(
|
||||||
|
read_utf8(instruction_name: String) -> _ {
|
||||||
|
Box::new(move |runtime| -> _ {
|
||||||
|
match runtime.stack.pop(2) {
|
||||||
|
Some(inputs) => match runtime.wasm_instance.memory(0) {
|
||||||
|
Some(memory) => {
|
||||||
|
let length = i32::try_from(&inputs[0])? as usize;
|
||||||
|
let pointer = i32::try_from(&inputs[1])? as usize;
|
||||||
|
let memory_view = memory.view::<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 not enough data on the stack (needs 2).",
|
||||||
|
instruction_name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_read_utf8 =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(13),
|
||||||
|
// ^^^^^^^ length
|
||||||
|
InterfaceValue::I32(0),
|
||||||
|
// ^^^^^^ pointer
|
||||||
|
],
|
||||||
|
instance: Instance {
|
||||||
|
memory: Memory::new("Hello, World!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
stack: [InterfaceValue::String("Hello, World!".into())],
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_read_utf8__read_out_of_memory =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(13),
|
||||||
|
// ^^^^^^^ length is too long
|
||||||
|
InterfaceValue::I32(0),
|
||||||
|
// ^^^^^^ pointer
|
||||||
|
],
|
||||||
|
instance: Instance {
|
||||||
|
memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
error: r#"`read-utf8` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_read_utf8__invalid_encoding =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 1 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(4),
|
||||||
|
// ^^^^^^ length is too long
|
||||||
|
InterfaceValue::I32(0),
|
||||||
|
// ^^^^^^ pointer
|
||||||
|
],
|
||||||
|
instance: Instance {
|
||||||
|
memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::<Vec<Cell<u8>>>()),
|
||||||
|
..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)."#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test_executable_instruction!(
|
||||||
|
test_read_utf8__stack_is_too_small =
|
||||||
|
instructions: [
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
// ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present.
|
||||||
|
],
|
||||||
|
invocation_inputs: [
|
||||||
|
InterfaceValue::I32(13),
|
||||||
|
InterfaceValue::I32(0),
|
||||||
|
],
|
||||||
|
instance: Instance::new(),
|
||||||
|
error: r#"`read-utf8` failed because there is not enough data on the stack (needs 2)."#,
|
||||||
|
);
|
||||||
|
}
|
169
lib/interface-types/src/interpreter/instructions/write_utf8.rs
Normal file
169
lib/interface-types/src/interpreter/instructions/write_utf8.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use crate::interpreter::wasm::values::{InterfaceType, InterfaceValue};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
executable_instruction!(
|
||||||
|
write_utf8(allocator_name: String, instruction_name: String) -> _ {
|
||||||
|
Box::new(move |runtime| -> _ {
|
||||||
|
let instance = 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 runtime.wasm_instance.memory(0) {
|
||||||
|
Some(memory) => match runtime.stack.pop1() {
|
||||||
|
Some(string) => {
|
||||||
|
let memory_view = memory.view::<u8>();
|
||||||
|
|
||||||
|
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])."#,
|
||||||
|
);
|
||||||
|
}
|
143
lib/interface-types/src/interpreter/mod.rs
Normal file
143
lib/interface-types/src/interpreter/mod.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
mod instruction;
|
||||||
|
mod instructions;
|
||||||
|
pub mod stack;
|
||||||
|
pub mod wasm;
|
||||||
|
|
||||||
|
pub use instruction::Instruction;
|
||||||
|
use stack::Stack;
|
||||||
|
use std::{convert::TryFrom, marker::PhantomData};
|
||||||
|
use wasm::values::InterfaceValue;
|
||||||
|
|
||||||
|
pub(crate) struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory>
|
||||||
|
where
|
||||||
|
Export: wasm::structures::Export + 'instance,
|
||||||
|
LocalImport: wasm::structures::LocalImport + 'instance,
|
||||||
|
Memory: wasm::structures::Memory + 'instance,
|
||||||
|
Instance: wasm::structures::Instance<Export, LocalImport, Memory> + 'instance,
|
||||||
|
{
|
||||||
|
invocation_inputs: &'invocation [InterfaceValue],
|
||||||
|
stack: Stack<InterfaceValue>,
|
||||||
|
wasm_instance: &'instance Instance,
|
||||||
|
_wasm_exports: PhantomData<Export>,
|
||||||
|
_wasm_locals_or_imports: PhantomData<LocalImport>,
|
||||||
|
_wasm_memories: PhantomData<Memory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type ExecutableInstruction<Instance, Export, LocalImport, Memory> =
|
||||||
|
Box<dyn Fn(&mut Runtime<Instance, Export, LocalImport, Memory>) -> Result<(), String>>;
|
||||||
|
|
||||||
|
pub struct Interpreter<Instance, Export, LocalImport, Memory>
|
||||||
|
where
|
||||||
|
Export: wasm::structures::Export,
|
||||||
|
LocalImport: wasm::structures::LocalImport,
|
||||||
|
Memory: wasm::structures::Memory,
|
||||||
|
Instance: wasm::structures::Instance<Export, LocalImport, Memory>,
|
||||||
|
{
|
||||||
|
executable_instructions: Vec<ExecutableInstruction<Instance, Export, LocalImport, Memory>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Instance, Export, LocalImport, Memory> Interpreter<Instance, Export, LocalImport, Memory>
|
||||||
|
where
|
||||||
|
Export: wasm::structures::Export,
|
||||||
|
LocalImport: wasm::structures::LocalImport,
|
||||||
|
Memory: wasm::structures::Memory,
|
||||||
|
Instance: wasm::structures::Instance<Export, LocalImport, Memory>,
|
||||||
|
{
|
||||||
|
fn iter(
|
||||||
|
&self,
|
||||||
|
) -> impl Iterator<Item = &ExecutableInstruction<Instance, Export, LocalImport, Memory>> + '_
|
||||||
|
{
|
||||||
|
self.executable_instructions.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
&self,
|
||||||
|
invocation_inputs: &[InterfaceValue],
|
||||||
|
wasm_instance: &Instance,
|
||||||
|
) -> Result<Stack<InterfaceValue>, String> {
|
||||||
|
let mut runtime = Runtime {
|
||||||
|
invocation_inputs,
|
||||||
|
stack: Stack::new(),
|
||||||
|
wasm_instance,
|
||||||
|
_wasm_exports: PhantomData,
|
||||||
|
_wasm_locals_or_imports: PhantomData,
|
||||||
|
_wasm_memories: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
for executable_instruction in self.iter() {
|
||||||
|
match executable_instruction(&mut runtime) {
|
||||||
|
Ok(_) => continue,
|
||||||
|
Err(message) => return Err(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(runtime.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'binary_input, Instance, Export, LocalImport, Memory> TryFrom<&Vec<Instruction<'binary_input>>>
|
||||||
|
for Interpreter<Instance, Export, LocalImport, Memory>
|
||||||
|
where
|
||||||
|
Export: wasm::structures::Export,
|
||||||
|
LocalImport: wasm::structures::LocalImport,
|
||||||
|
Memory: wasm::structures::Memory,
|
||||||
|
Instance: wasm::structures::Instance<Export, LocalImport, Memory>,
|
||||||
|
{
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(instructions: &Vec<Instruction>) -> Result<Self, Self::Error> {
|
||||||
|
let executable_instructions = instructions
|
||||||
|
.iter()
|
||||||
|
.map(
|
||||||
|
|instruction| -> ExecutableInstruction<Instance, Export, LocalImport, Memory> {
|
||||||
|
let instruction_representation: String = instruction.into();
|
||||||
|
|
||||||
|
match instruction {
|
||||||
|
Instruction::ArgumentGet { index } => {
|
||||||
|
instructions::argument_get(*index, instruction_representation)
|
||||||
|
}
|
||||||
|
Instruction::Call { function_index } => {
|
||||||
|
instructions::call(*function_index, instruction_representation)
|
||||||
|
}
|
||||||
|
Instruction::CallExport { export_name } => instructions::call_export(
|
||||||
|
(*export_name).to_owned(),
|
||||||
|
instruction_representation,
|
||||||
|
),
|
||||||
|
Instruction::ReadUtf8 => {
|
||||||
|
instructions::read_utf8(instruction_representation)
|
||||||
|
}
|
||||||
|
Instruction::WriteUtf8 { allocator_name } => instructions::write_utf8(
|
||||||
|
(*allocator_name).to_owned(),
|
||||||
|
instruction_representation,
|
||||||
|
),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Interpreter {
|
||||||
|
executable_instructions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{Instruction, Interpreter};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interpreter_from_instructions() {
|
||||||
|
let instructions = vec![
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::ArgumentGet { index: 0 },
|
||||||
|
Instruction::CallExport { export_name: "foo" },
|
||||||
|
Instruction::ReadUtf8,
|
||||||
|
Instruction::Call { function_index: 7 },
|
||||||
|
];
|
||||||
|
let interpreter: Interpreter<(), (), (), ()> = (&instructions).try_into().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(interpreter.executable_instructions.len(), 5);
|
||||||
|
}
|
||||||
|
}
|
2
lib/interface-types/src/interpreter/wasm/mod.rs
Normal file
2
lib/interface-types/src/interpreter/wasm/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod structures;
|
||||||
|
pub mod values;
|
@ -1,88 +1,5 @@
|
|||||||
use std::{cell::Cell, convert::TryFrom};
|
use super::values::{InterfaceType, InterfaceValue, ValueType};
|
||||||
|
use std::cell::Cell;
|
||||||
pub use crate::ast::InterfaceType;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum InterfaceValue {
|
|
||||||
Int(isize),
|
|
||||||
Float(f64),
|
|
||||||
Any(isize),
|
|
||||||
String(String),
|
|
||||||
// Seq(…),
|
|
||||||
I32(i32),
|
|
||||||
I64(i64),
|
|
||||||
F32(f32),
|
|
||||||
F64(f64),
|
|
||||||
// AnyRef(…),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&InterfaceValue> for InterfaceType {
|
|
||||||
fn from(value: &InterfaceValue) -> Self {
|
|
||||||
match value {
|
|
||||||
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 InterfaceValue {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::I32(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! from_x_for_interface_value {
|
|
||||||
($native_type:ty, $value_variant:ident) => {
|
|
||||||
impl From<$native_type> for InterfaceValue {
|
|
||||||
fn from(n: $native_type) -> Self {
|
|
||||||
Self::$value_variant(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&InterfaceValue> for $native_type {
|
|
||||||
type Error = &'static str;
|
|
||||||
|
|
||||||
fn try_from(w: &InterfaceValue) -> Result<Self, Self::Error> {
|
|
||||||
match w {
|
|
||||||
InterfaceValue::$value_variant(n) => Ok(n.clone()),
|
|
||||||
_ => Err("Invalid cast."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
from_x_for_interface_value!(String, String);
|
|
||||||
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
|
|
||||||
where
|
|
||||||
Self: 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 TypedIndex: Copy + Clone {
|
pub trait TypedIndex: Copy + Clone {
|
||||||
fn new(index: usize) -> Self;
|
fn new(index: usize) -> Self;
|
85
lib/interface-types/src/interpreter/wasm/values.rs
Normal file
85
lib/interface-types/src/interpreter/wasm/values.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
pub use crate::ast::InterfaceType;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum InterfaceValue {
|
||||||
|
Int(isize),
|
||||||
|
Float(f64),
|
||||||
|
Any(isize),
|
||||||
|
String(String),
|
||||||
|
// Seq(…),
|
||||||
|
I32(i32),
|
||||||
|
I64(i64),
|
||||||
|
F32(f32),
|
||||||
|
F64(f64),
|
||||||
|
// AnyRef(…),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&InterfaceValue> for InterfaceType {
|
||||||
|
fn from(value: &InterfaceValue) -> Self {
|
||||||
|
match value {
|
||||||
|
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 InterfaceValue {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::I32(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! from_x_for_interface_value {
|
||||||
|
($native_type:ty, $value_variant:ident) => {
|
||||||
|
impl From<$native_type> for InterfaceValue {
|
||||||
|
fn from(n: $native_type) -> Self {
|
||||||
|
Self::$value_variant(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&InterfaceValue> for $native_type {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(w: &InterfaceValue) -> Result<Self, Self::Error> {
|
||||||
|
match w {
|
||||||
|
InterfaceValue::$value_variant(n) => Ok(n.clone()),
|
||||||
|
_ => Err("Invalid cast."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
from_x_for_interface_value!(String, String);
|
||||||
|
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
|
||||||
|
where
|
||||||
|
Self: 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);
|
@ -3,13 +3,13 @@ pub mod ast;
|
|||||||
mod macros;
|
mod macros;
|
||||||
pub mod decoders;
|
pub mod decoders;
|
||||||
pub mod encoders;
|
pub mod encoders;
|
||||||
pub mod instructions;
|
pub mod interpreter;
|
||||||
|
|
||||||
pub use decoders::binary::parse as parse_binary;
|
pub use decoders::binary::parse as parse_binary;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{ast::*, instructions::Instruction, parse_binary};
|
use crate::{ast::*, interpreter::Instruction, parse_binary};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use wasmer_clif_backend::CraneliftCompiler;
|
use wasmer_clif_backend::CraneliftCompiler;
|
||||||
use wasmer_runtime_core as runtime;
|
use wasmer_runtime_core as runtime;
|
||||||
|
@ -23,3 +23,92 @@ macro_rules! consume {
|
|||||||
$input = next_input;
|
$input = next_input;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! executable_instruction {
|
||||||
|
($name:ident ( $($argument_name:ident: $argument_type:ty),* ) -> _ $implementation:block ) => {
|
||||||
|
use crate::interpreter::{ExecutableInstruction, wasm, stack::Stackable};
|
||||||
|
|
||||||
|
pub(crate) fn $name<Instance, Export, LocalImport, Memory>(
|
||||||
|
$($argument_name: $argument_type),*
|
||||||
|
) -> ExecutableInstruction<Instance, Export, LocalImport, Memory>
|
||||||
|
where
|
||||||
|
Export: wasm::structures::Export,
|
||||||
|
LocalImport: wasm::structures::LocalImport,
|
||||||
|
Memory: wasm::structures::Memory,
|
||||||
|
Instance: wasm::structures::Instance<Export, LocalImport, Memory>,
|
||||||
|
{
|
||||||
|
$implementation
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
macro_rules! test_executable_instruction {
|
||||||
|
(
|
||||||
|
$test_name:ident =
|
||||||
|
instructions: [ $($instructions:expr),* $(,)* ],
|
||||||
|
invocation_inputs: [ $($invocation_inputs:expr),* $(,)* ],
|
||||||
|
instance: $instance:expr,
|
||||||
|
stack: [ $($stack:expr),* $(,)* ]
|
||||||
|
$(,)*
|
||||||
|
) => {
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case, unused)]
|
||||||
|
fn $test_name() {
|
||||||
|
use crate::interpreter::{
|
||||||
|
instructions::tests::{Export, Instance, LocalImport, Memory},
|
||||||
|
stack::Stackable,
|
||||||
|
wasm::values::{InterfaceType, InterfaceValue},
|
||||||
|
Instruction, Interpreter,
|
||||||
|
};
|
||||||
|
use std::{cell::Cell, collections::HashMap, convert::TryInto};
|
||||||
|
|
||||||
|
let interpreter: Interpreter<Instance, Export, LocalImport, Memory> =
|
||||||
|
(&vec![$($instructions),*]).try_into().unwrap();
|
||||||
|
|
||||||
|
let invocation_inputs = vec![$($invocation_inputs),*];
|
||||||
|
let instance = $instance;
|
||||||
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
|
assert!(run.is_ok());
|
||||||
|
|
||||||
|
let stack = run.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(stack.as_slice(), &[$($stack),*]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$test_name:ident =
|
||||||
|
instructions: [ $($instructions:expr),* $(,)* ],
|
||||||
|
invocation_inputs: [ $($invocation_inputs:expr),* $(,)* ],
|
||||||
|
instance: $instance:expr,
|
||||||
|
error: $error:expr
|
||||||
|
$(,)*
|
||||||
|
) => {
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case, unused)]
|
||||||
|
fn $test_name() {
|
||||||
|
use crate::interpreter::{
|
||||||
|
instructions::tests::{Export, Instance, LocalImport, Memory},
|
||||||
|
stack::Stackable,
|
||||||
|
wasm::values::{InterfaceType, InterfaceValue},
|
||||||
|
Instruction, Interpreter,
|
||||||
|
};
|
||||||
|
use std::{cell::Cell, collections::HashMap, convert::TryInto};
|
||||||
|
|
||||||
|
let interpreter: Interpreter<Instance, Export, LocalImport, Memory> =
|
||||||
|
(&vec![$($instructions),*]).try_into().unwrap();
|
||||||
|
|
||||||
|
let invocation_inputs = vec![$($invocation_inputs),*];
|
||||||
|
let instance = $instance;
|
||||||
|
let run = interpreter.run(&invocation_inputs, &instance);
|
||||||
|
|
||||||
|
assert!(run.is_err());
|
||||||
|
|
||||||
|
let error = run.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(error, String::from($error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user