diff --git a/README.md b/README.md index 242b14e..f8f3490 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,68 @@ more](https://github.com/wasmerio/wasmer). This crate is an implementation of [the living WebAssembly Interface Types standard](https://github.com/WebAssembly/interface-types). + +## Encoders and decoders + +The `wasmer-interface-types` crate comes with an encoder and a decoder +for the WAT format, and the binary format, for the WebAssembly +Interface Types. An encoder writes an AST into another format, like +WAT or binary. A decoder reads an AST from another format, like WAT or +binary. + +## Instructions + +Very basically, WebAssembly Interface Types defines a set of +instructions, used by adapters to transform the data between +WebAssembly core and the outside world ([learn +mode](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md)). + +Here is the instructions that are implemented: + +| Instruction | WAT encoder | Binary encoder | WAT decoder | Binary decoder | Interpreter | +|-|-|-|-|-|-| +| `arg.get` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `call-core` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `memory-to-string` | ❌ | ❌ | ❌ | ❌ | ❌ | +| `string-to-memory` | ❌ | ❌ | ❌ | ❌ | ❌ | +| `call-adapter` | ❌ | ❌ | ❌ | ❌ | ❌ | +| `defer-call-core` | ❌ | ❌ | ❌ | ❌ | ❌ | +| `i32-to-s8` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i32-to-s8x` | ✅ | ✅ | ✅ | ✅ | ❌ | +| `i32-to-u8` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i32-to-s16` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i32-to-s16x` | ✅ | ✅ | ✅ | ✅ | ❌ | +| `i32-to-u16` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i32-to-s32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i32-to-u32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i32-to-s64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i32-to-u64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-s8` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-s8x` | ✅ | ✅ | ✅ | ✅ | ❌ | +| `i64-to-u8` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-s16` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-s16x` | ✅ | ✅ | ✅ | ✅ | ❌ | +| `i64-to-u16` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-s32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-s32x` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-u32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-s64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `i64-to-u64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s8-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u8-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s16-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u16-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s32-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u32-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s64-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s64-to-i32x` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u64-to-i32` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u64-to-i32x` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s8-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u8-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s16-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u16-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s32-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u32-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `s64-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `u64-to-i64` | ✅ | ✅ | ✅ | ✅ | ✅ | diff --git a/src/ast.rs b/src/ast.rs index 20a6719..7b86c64 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -51,12 +51,16 @@ pub enum InterfaceType { } /// Represents a type signature. +/// +/// ```wasm,ignore +/// (@interface type (param i32 i32) (result string)) +/// ``` #[derive(PartialEq, Debug)] pub struct Type { - /// Types for the parameters. + /// Types for the parameters (`(param …)`). pub inputs: Vec, - /// Types for the results. + /// Types for the results (`(result …)`). pub outputs: Vec, } diff --git a/src/decoders/binary.rs b/src/decoders/binary.rs index befb3dd..9dc0f8c 100644 --- a/src/decoders/binary.rs +++ b/src/decoders/binary.rs @@ -168,22 +168,12 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( consume!((input, argument_0) = uleb(input)?); ( input, - Instruction::Call { + Instruction::CallCore { function_index: argument_0 as usize, }, ) } - 0x02 => { - consume!((input, argument_0) = string(input)?); - ( - input, - Instruction::CallExport { - export_name: argument_0, - }, - ) - } - 0x03 => (input, Instruction::MemoryToString), 0x04 => { @@ -637,10 +627,9 @@ mod tests { #[test] fn test_instructions() { let input = &[ - 0x2c, // list of 44 items + 0x2b, // list of 43 items 0x00, 0x01, // ArgumentGet { index: 1 } - 0x01, 0x01, // Call { function_index: 1 } - 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" } + 0x01, 0x01, // CallCore { function_index: 1 } 0x03, // MemoryToString 0x04, 0x01, // StringToMemory { allocator_index: 1 } 0x07, // I32ToS8 @@ -688,8 +677,7 @@ mod tests { &[0x0a][..], vec![ Instruction::ArgumentGet { index: 1 }, - Instruction::Call { function_index: 1 }, - Instruction::CallExport { export_name: "abc" }, + Instruction::CallCore { function_index: 1 }, Instruction::MemoryToString, Instruction::StringToMemory { allocator_index: 1 }, Instruction::I32ToS8, diff --git a/src/decoders/wat.rs b/src/decoders/wat.rs index 3cdf5c1..069db8d 100644 --- a/src/decoders/wat.rs +++ b/src/decoders/wat.rs @@ -27,8 +27,7 @@ mod keyword { // Instructions. custom_keyword!(argument_get = "arg.get"); - custom_keyword!(call); - custom_keyword!(call_export = "call-export"); + custom_keyword!(call_core = "call-core"); custom_keyword!(memory_to_string = "memory-to-string"); custom_keyword!(string_to_memory = "string-to-memory"); custom_keyword!(i32_to_s8 = "i32-to-s8"); @@ -149,18 +148,12 @@ impl<'a> Parse<'a> for Instruction<'a> { Ok(Instruction::ArgumentGet { index: parser.parse()?, }) - } else if lookahead.peek::() { - parser.parse::()?; + } else if lookahead.peek::() { + parser.parse::()?; - Ok(Instruction::Call { + Ok(Instruction::CallCore { function_index: parser.parse::()? as usize, }) - } else if lookahead.peek::() { - parser.parse::()?; - - Ok(Instruction::CallExport { - export_name: parser.parse()?, - }) } else if lookahead.peek::() { parser.parse::()?; @@ -673,8 +666,7 @@ mod tests { fn test_instructions() { let inputs = vec![ "arg.get 7", - "call 7", - r#"call-export "foo""#, + "call-core 7", "memory-to-string", "string-to-memory 42", "i32-to-s8", @@ -719,8 +711,7 @@ mod tests { ]; let outputs = vec![ Instruction::ArgumentGet { index: 7 }, - Instruction::Call { function_index: 7 }, - Instruction::CallExport { export_name: "foo" }, + Instruction::CallCore { function_index: 7 }, Instruction::MemoryToString, Instruction::StringToMemory { allocator_index: 42, diff --git a/src/encoders/binary.rs b/src/encoders/binary.rs index 4c0fbe2..b73d527 100644 --- a/src/encoders/binary.rs +++ b/src/encoders/binary.rs @@ -255,16 +255,11 @@ where (*index as u64).to_bytes(writer)?; } - Instruction::Call { function_index } => { + Instruction::CallCore { function_index } => { 0x01_u8.to_bytes(writer)?; (*function_index as u64).to_bytes(writer)?; } - Instruction::CallExport { export_name } => { - 0x02_u8.to_bytes(writer)?; - export_name.to_bytes(writer)?; - } - Instruction::MemoryToString => 0x03_u8.to_bytes(writer)?, Instruction::StringToMemory { allocator_index } => { @@ -554,8 +549,7 @@ mod tests { assert_to_bytes!( vec![ Instruction::ArgumentGet { index: 1 }, - Instruction::Call { function_index: 1 }, - Instruction::CallExport { export_name: "abc" }, + Instruction::CallCore { function_index: 1 }, Instruction::MemoryToString, Instruction::StringToMemory { allocator_index: 1 }, Instruction::I32ToS8, @@ -599,10 +593,9 @@ mod tests { Instruction::U64ToI64, ], &[ - 0x2c, // list of 44 items + 0x2b, // list of 43 items 0x00, 0x01, // ArgumentGet { index: 1 } - 0x01, 0x01, // Call { function_index: 1 } - 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" } + 0x01, 0x01, // CallCore { function_index: 1 } 0x03, // MemoryToString 0x04, 0x01, // StringToMemory { allocator_index: 1 } 0x07, // I32ToS8 diff --git a/src/encoders/wat.rs b/src/encoders/wat.rs index 4f0c434..ef5a091 100644 --- a/src/encoders/wat.rs +++ b/src/encoders/wat.rs @@ -84,8 +84,7 @@ impl<'input> ToString for &Instruction<'input> { fn to_string(&self) -> String { match self { Instruction::ArgumentGet { index } => format!("arg.get {}", index), - Instruction::Call { function_index } => format!("call {}", function_index), - Instruction::CallExport { export_name } => format!(r#"call-export "{}""#, export_name), + Instruction::CallCore { function_index } => format!("call-core {}", function_index), Instruction::MemoryToString => "memory-to-string".into(), Instruction::StringToMemory { allocator_index } => { format!(r#"string-to-memory {}"#, allocator_index) @@ -361,8 +360,7 @@ mod tests { fn test_instructions() { let inputs: Vec = vec![ (&Instruction::ArgumentGet { index: 7 }).to_string(), - (&Instruction::Call { function_index: 7 }).to_string(), - (&Instruction::CallExport { export_name: "foo" }).to_string(), + (&Instruction::CallCore { function_index: 7 }).to_string(), (&Instruction::MemoryToString).to_string(), (&Instruction::StringToMemory { allocator_index: 42, @@ -410,8 +408,7 @@ mod tests { ]; let outputs = vec![ "arg.get 7", - "call 7", - r#"call-export "foo""#, + "call-core 7", "memory-to-string", "string-to-memory 42", "i32-to-s8", diff --git a/src/interpreter/instruction.rs b/src/interpreter/instruction.rs index 70818e6..9f7cc39 100644 --- a/src/interpreter/instruction.rs +++ b/src/interpreter/instruction.rs @@ -9,18 +9,12 @@ pub enum Instruction<'input> { index: u32, }, - /// The `call` instruction. - Call { + /// The `call-core` instruction. + CallCore { /// The function index. function_index: usize, }, - /// The `call-export` instruction. - CallExport { - /// The exported function name. - export_name: &'input str, - }, - /// The `memory-to-string` instruction. MemoryToString, diff --git a/src/interpreter/instructions/call.rs b/src/interpreter/instructions/call_core.rs similarity index 81% rename from src/interpreter/instructions/call.rs rename to src/interpreter/instructions/call_core.rs index 5fe896b..3179c90 100644 --- a/src/interpreter/instructions/call.rs +++ b/src/interpreter/instructions/call_core.rs @@ -4,7 +4,7 @@ use crate::interpreter::wasm::{ }; executable_instruction!( - call(function_index: usize, instruction_name: String) -> _ { + call_core(function_index: usize, instruction_name: String) -> _ { move |runtime| -> _ { let instance = &mut runtime.wasm_instance; let index = FunctionIndex::new(function_index); @@ -65,11 +65,11 @@ executable_instruction!( #[cfg(test)] mod tests { test_executable_instruction!( - test_call = + test_call_core = instructions: [ Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 0 }, - Instruction::Call { function_index: 42 }, + Instruction::CallCore { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -80,39 +80,39 @@ mod tests { ); test_executable_instruction!( - test_call__invalid_local_import_index = + test_call_core__invalid_local_import_index = instructions: [ - Instruction::Call { function_index: 42 }, + Instruction::CallCore { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), InterfaceValue::I32(4), ], instance: Default::default(), - error: r#"`call 42` cannot call the local or imported function `42` because it doesn't exist."#, + error: r#"`call-core 42` cannot call the local or imported function `42` because it doesn't exist."#, ); test_executable_instruction!( - test_call__stack_is_too_small = + test_call_core__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 + Instruction::CallCore { 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)."#, + error: r#"`call-core 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 = + test_call_core__invalid_types_in_the_stack = instructions: [ Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 0 }, - Instruction::Call { function_index: 42 }, + Instruction::CallCore { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -120,15 +120,15 @@ mod tests { // ^^^ 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])."#, + error: r#"`call-core 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 = + test_call_core__failure_when_calling = instructions: [ Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 0 }, - Instruction::Call { function_index: 42 }, + Instruction::CallCore { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -151,15 +151,15 @@ mod tests { }, ..Default::default() }, - error: r#"`call 42` failed when calling the local or imported function `42`."#, + error: r#"`call-core 42` failed when calling the local or imported function `42`."#, ); test_executable_instruction!( - test_call__void = + test_call_core__void = instructions: [ Instruction::ArgumentGet { index: 1 }, Instruction::ArgumentGet { index: 0 }, - Instruction::Call { function_index: 42 }, + Instruction::CallCore { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), diff --git a/src/interpreter/instructions/call_export.rs b/src/interpreter/instructions/call_export.rs deleted file mode 100644 index 9afe984..0000000 --- a/src/interpreter/instructions/call_export.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::interpreter::wasm::values::InterfaceType; - -executable_instruction!( - call_export(export_name: String, instruction_name: String) -> _ { - move |runtime| -> _ { - let instance = &mut 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(Into::into) - .collect::>(); - - 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: [], - ); -} diff --git a/src/interpreter/instructions/mod.rs b/src/interpreter/instructions/mod.rs index e8912a5..8466b99 100644 --- a/src/interpreter/instructions/mod.rs +++ b/src/interpreter/instructions/mod.rs @@ -1,13 +1,11 @@ mod argument_get; -mod call; -mod call_export; +mod call_core; mod lowering_lifting; mod memory_to_string; mod string_to_memory; pub(crate) use argument_get::argument_get; -pub(crate) use call::call; -pub(crate) use call_export::call_export; +pub(crate) use call_core::call_core; pub(crate) use lowering_lifting::*; pub(crate) use memory_to_string::memory_to_string; pub(crate) use string_to_memory::string_to_memory; diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 29e1194..2cf6b21 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -71,7 +71,7 @@ pub(crate) type ExecutableInstruction = (&vec![ /// Instruction::ArgumentGet { index: 1 }, /// Instruction::ArgumentGet { index: 0 }, -/// Instruction::CallExport { export_name: "sum" }, +/// Instruction::CallCore { function_index: 42 }, /// ]) /// .try_into() /// .unwrap(); @@ -81,12 +81,12 @@ pub(crate) type ExecutableInstruction i32 { a + b }`. -/// exports: { +/// // 3.1. Defines one function: `fn sum(a: i32, b: i32) -> i32 { a + b }`. +/// locals_or_imports: { /// let mut hashmap = HashMap::new(); /// hashmap.insert( -/// "sum".into(), -/// Export { +/// 42, +/// LocalImport { /// // Defines the argument types of the function. /// inputs: vec![InterfaceType::I32, InterfaceType::I32], /// @@ -196,11 +196,8 @@ where Instruction::ArgumentGet { index } => { instructions::argument_get(*index, instruction_name) } - Instruction::Call { function_index } => { - instructions::call(*function_index, instruction_name) - } - Instruction::CallExport { export_name } => { - instructions::call_export((*export_name).to_owned(), instruction_name) + Instruction::CallCore { function_index } => { + instructions::call_core(*function_index, instruction_name) } Instruction::MemoryToString => instructions::memory_to_string(instruction_name), Instruction::StringToMemory { allocator_index } => { @@ -253,24 +250,3 @@ where }) } } - -#[cfg(test)] -mod tests { - use super::{wasm::structures::EmptyMemoryView, 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::MemoryToString, - Instruction::Call { function_index: 7 }, - ]; - let interpreter: Interpreter<(), (), (), (), EmptyMemoryView> = - (&instructions).try_into().unwrap(); - - assert_eq!(interpreter.executable_instructions.len(), 5); - } -}