feat(interface-types) Split the interpreter into multiple modules/files.

This commit is contained in:
Ivan Enderlin
2019-09-26 14:14:46 +02:00
parent e509f97c2d
commit c042050497
18 changed files with 1222 additions and 1128 deletions

View File

@ -0,0 +1,25 @@
use crate::ast::InterfaceType;
#[derive(PartialEq, Debug)]
pub enum Instruction<'input> {
ArgumentGet { index: u64 },
Call { function_index: usize },
CallExport { export_name: &'input str },
ReadUtf8,
WriteUtf8 { allocator_name: &'input str },
AsWasm(InterfaceType),
AsInterface(InterfaceType),
TableRefAdd,
TableRefGet,
CallMethod(u64),
MakeRecord(InterfaceType),
GetField(InterfaceType, u64),
Const(InterfaceType, u64),
FoldSeq(u64),
Add(InterfaceType),
MemToSeq(InterfaceType, &'input str),
Load(InterfaceType, &'input str),
SeqNew(InterfaceType),
ListPush,
RepeatWhile(u64, u64),
}

View File

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

View 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: [],
);
}

View 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: [],
);
}

View 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)
}
}
}

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

View 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
src/interpreter/mod.rs Normal file
View 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);
}
}

106
src/interpreter/stack.rs Normal file
View File

@ -0,0 +1,106 @@
pub trait Stackable {
type Item;
fn is_empty(&self) -> bool;
fn as_slice(&self) -> &[Self::Item];
fn push(&mut self, item: Self::Item);
fn pop1(&mut self) -> Option<Self::Item>;
fn pop(&mut self, n: usize) -> Option<Vec<Self::Item>>;
}
#[derive(Debug, Default)]
pub struct Stack<T>
where
T: Default + Clone,
{
inner: Vec<T>,
}
impl<T> Stack<T>
where
T: Default + Clone,
{
pub fn new() -> Self {
Self {
..Default::default()
}
}
}
impl<T> Stackable for Stack<T>
where
T: Default + Clone,
{
type Item = T;
fn is_empty(&self) -> bool {
self.inner.is_empty()
}
fn as_slice(&self) -> &[Self::Item] {
self.inner.as_slice()
}
fn push(&mut self, item: Self::Item) {
self.inner.push(item);
}
fn pop1(&mut self) -> Option<Self::Item> {
self.inner.pop()
}
fn pop(&mut self, n: usize) -> Option<Vec<Self::Item>> {
if self.inner.len() < n {
None
} else {
let items = self
.inner
.drain(self.inner.len() - n..)
.rev()
.collect::<Vec<Self::Item>>();
assert!(items.len() == n);
Some(items)
}
}
}
#[cfg(test)]
mod tests {
use super::{Stack, Stackable};
#[test]
fn test_is_empty() {
let mut stack = Stack::new();
assert_eq!(stack.is_empty(), true);
stack.push(1);
assert_eq!(stack.is_empty(), false);
}
#[test]
fn test_push_pop1() {
let mut stack = Stack::new();
stack.push(1);
assert_eq!(stack.pop1(), Some(1));
assert_eq!(stack.is_empty(), true);
}
#[test]
fn test_pop() {
let mut stack = Stack::new();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
stack.push(6);
assert_eq!(stack.pop(1), Some(vec![6]));
assert_eq!(stack.pop(2), Some(vec![5, 4]));
assert_eq!(stack.pop(3), Some(vec![3, 2, 1]));
assert_eq!(stack.is_empty(), true);
}
}

View File

@ -0,0 +1,2 @@
pub mod structures;
pub mod values;

View File

@ -0,0 +1,138 @@
use super::values::{InterfaceType, InterfaceValue, ValueType};
use std::cell::Cell;
pub trait TypedIndex: Copy + Clone {
fn new(index: usize) -> Self;
fn index(&self) -> usize;
}
macro_rules! typed_index {
($type:ident) => {
#[derive(Copy, Clone)]
pub struct $type(usize);
impl TypedIndex for $type {
fn new(index: usize) -> Self {
Self(index)
}
fn index(&self) -> usize {
self.0
}
}
};
}
typed_index!(FunctionIndex);
typed_index!(LocalFunctionIndex);
typed_index!(ImportFunctionIndex);
pub trait LocalImportIndex {
type Local: TypedIndex;
type Import: TypedIndex;
}
impl LocalImportIndex for FunctionIndex {
type Local = LocalFunctionIndex;
type Import = ImportFunctionIndex;
}
pub trait Export {
fn inputs_cardinality(&self) -> usize;
fn outputs_cardinality(&self) -> usize;
fn inputs(&self) -> &[InterfaceType];
fn outputs(&self) -> &[InterfaceType];
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>;
}
pub trait LocalImport {
fn inputs_cardinality(&self) -> usize;
fn outputs_cardinality(&self) -> usize;
fn inputs(&self) -> &[InterfaceType];
fn outputs(&self) -> &[InterfaceType];
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>;
}
pub trait Memory {
fn view<V: ValueType>(&self) -> &[Cell<V>];
}
pub trait Instance<E, LI, M>
where
E: Export,
LI: LocalImport,
M: Memory,
{
fn export(&self, export_name: &str) -> Option<&E>;
fn local_or_import<I: TypedIndex + LocalImportIndex>(&self, index: I) -> Option<&LI>;
fn memory(&self, index: usize) -> Option<&M>;
}
impl Export for () {
fn inputs_cardinality(&self) -> usize {
0
}
fn outputs_cardinality(&self) -> usize {
0
}
fn inputs(&self) -> &[InterfaceType] {
&[]
}
fn outputs(&self) -> &[InterfaceType] {
&[]
}
fn call(&self, _arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
Err(())
}
}
impl LocalImport for () {
fn inputs_cardinality(&self) -> usize {
0
}
fn outputs_cardinality(&self) -> usize {
0
}
fn inputs(&self) -> &[InterfaceType] {
&[]
}
fn outputs(&self) -> &[InterfaceType] {
&[]
}
fn call(&self, _arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
Err(())
}
}
impl Memory for () {
fn view<V: ValueType>(&self) -> &[Cell<V>] {
&[]
}
}
impl<E, LI, M> Instance<E, LI, M> for ()
where
E: Export,
LI: LocalImport,
M: Memory,
{
fn export(&self, _export_name: &str) -> Option<&E> {
None
}
fn memory(&self, _: usize) -> Option<&M> {
None
}
fn local_or_import<I: TypedIndex + LocalImportIndex>(&self, _index: I) -> Option<&LI> {
None
}
}

View 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);