From b3af77c92dd881d516248b1f1f5031f051adbc85 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 11 Sep 2019 23:51:20 +0200 Subject: [PATCH 01/78] feat(wasmer-interface-types) Draft. --- Cargo.lock | 43 ++++ Cargo.toml | 28 +-- lib/interface-types/Cargo.toml | 15 ++ lib/interface-types/src/lib.rs | 213 ++++++++++++++++++ .../tests/assets/hello_world.wasm | Bin 0 -> 444 bytes lib/runtime-core/Cargo.toml | 9 +- lib/runtime/Cargo.toml | 1 + 7 files changed, 292 insertions(+), 17 deletions(-) create mode 100644 lib/interface-types/Cargo.toml create mode 100644 lib/interface-types/src/lib.rs create mode 100644 lib/interface-types/tests/assets/hello_world.wasm diff --git a/Cargo.lock b/Cargo.lock index 11c8633e316..efd30b42c28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -708,6 +708,18 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lexical-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libc" version = "0.2.62" @@ -810,6 +822,16 @@ dependencies = [ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nom" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lexical-core 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -1236,6 +1258,11 @@ name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "static_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "strsim" version = "0.8.0" @@ -1609,6 +1636,15 @@ dependencies = [ "wasmer-singlepass-backend 0.7.0", ] +[[package]] +name = "wasmer-interface-types" +version = "0.6.0" +dependencies = [ + "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-clif-backend 0.6.0", + "wasmer-runtime-core 0.6.0", +] + [[package]] name = "wasmer-kernel-loader" version = "0.1.0" @@ -1933,7 +1969,12 @@ dependencies = [ "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +<<<<<<< HEAD "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +======= +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum lexical-core 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d11f3928ffd249baadf9f083cdea16d7cf317b2a8be6227e1169102432a36d2" +>>>>>>> feat(wasmer-interface-types) Draft. "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" "checksum llvm-sys 80.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2110cd4daf9cd8e39dd3b933b1a2a2ac7315e91f7c92b3a20beab526c63b5978" @@ -1946,6 +1987,7 @@ dependencies = [ "checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" @@ -1999,6 +2041,7 @@ dependencies = [ "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum structopt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ac9d6e93dd792b217bf89cda5c14566e3043960c6f9da890c2ba5d09d07804c" "checksum structopt-derive 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ae9e5165d463a0dea76967d021f8d0f9316057bf5163aa2a4843790e842ff37" diff --git a/Cargo.toml b/Cargo.toml index 31ba526bd0b..7e1e49c14db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,25 +40,26 @@ wasmer-emscripten-tests = { path = "lib/emscripten-tests", optional = true } [workspace] members = [ + "examples/plugin-for-example", "lib/clif-backend", - "lib/singlepass-backend", + "lib/dev-utils", + "lib/emscripten", + "lib/emscripten-tests", + "lib/interface-types", + "lib/kernel-loader", + "lib/kernel-net", + "lib/llvm-backend", + "lib/middleware-common", + "lib/middleware-common-tests", "lib/runtime", - # "lib/runtime-abi", + "lib/runtime-c-api", "lib/runtime-core", - "lib/emscripten", + "lib/singlepass-backend", "lib/spectests", - "lib/win-exception-handler", - "lib/runtime-c-api", - "lib/llvm-backend", "lib/wasi", - "lib/middleware-common", - "lib/kernel-loader", - "lib/kernel-net", - "lib/dev-utils", "lib/wasi-tests", - "lib/emscripten-tests", - "lib/middleware-common-tests", - "examples/plugin-for-example" + "lib/win-exception-handler" + # "lib/runtime-abi", ] [build-dependencies] @@ -100,6 +101,7 @@ backend-singlepass = [ ] wasi = ["wasmer-wasi"] managed = ["backend-singlepass", "wasmer-runtime-core/managed"] +interface-types = ["wasmer-runtime/interface-types"] # vfs = ["wasmer-runtime-abi"] [[example]] diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml new file mode 100644 index 00000000000..0c613198eae --- /dev/null +++ b/lib/interface-types/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "wasmer-interface-types" +version = "0.6.0" +description = "WebAssembly Interface Types library for Wasmer" +license = "MIT" +authors = ["The Wasmer Engineering Team "] +repository = "https://github.com/wasmerio/wasmer" +edition = "2018" + +[dependencies] +nom = "5.0" + +[dev-dependencies] +wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0", features = ["backend-cranelift"] } +wasmer-clif-backend = { path = "../clif-backend", version = "0.6.0" } \ No newline at end of file diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs new file mode 100644 index 00000000000..2ba8a8b3f8e --- /dev/null +++ b/lib/interface-types/src/lib.rs @@ -0,0 +1,213 @@ +use nom::{ + error::{make_error, ErrorKind, ParseError}, + Err, IResult, +}; +use std::{convert::TryFrom, str}; + +macro_rules! d { + ($expression:expr) => { + match $expression { + tmp => { + eprintln!( + "[{}:{}] {} = {:?}", + file!(), + line!(), + stringify!($expression), + &tmp + ); + + tmp + } + } + }; +} + +#[derive(PartialEq, Debug)] +enum Type { + Int, + Float, + Any, + String, + Seq, + + I32, + I64, + F32, + F64, + AnyRef, +} + +#[derive(Debug)] +struct Export<'input> { + name: &'input str, + input_types: Vec, + output_types: Vec, +} + +impl TryFrom for Type { + type Error = &'static str; + + fn try_from(code: u64) -> Result { + Ok(match code { + 0x7fff => Self::Int, + 0x7ffe => Self::Float, + 0x7ffd => Self::Any, + 0x7ffc => Self::String, + 0x7ffb => Self::Seq, + 0x7f => Self::I32, + 0x7e => Self::I64, + 0x7d => Self::F32, + 0x7c => Self::F64, + 0x6f => Self::AnyRef, + + _ => return Err("Unknown type code."), + }) + } +} + +fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let (output, bytes) = match input.iter().position(|&byte| byte & 0x80 == 0) { + Some(position) => (&input[position + 1..], &input[..position + 1]), + None => (&[] as &[u8], input), + }; + + Ok(( + output, + bytes + .iter() + .rev() + .fold(0, |acc, byte| (acc << 7) | (byte & 0x7f) as u64), + )) +} + +fn string<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], &'input str, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let length = input[0] as usize; + let input = &input[1..]; + + if input.len() < length { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + Ok((&input[length..], unsafe { + str::from_utf8_unchecked(&input[..length]) + })) +} + +fn list<'input, I: ::std::fmt::Debug, E: ParseError<&'input [u8]>>( + input: &'input [u8], + item_parser: fn(&'input [u8]) -> IResult<&'input [u8], I, E>, +) -> IResult<&'input [u8], Vec, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let length = input[0] as usize; + let mut input = &input[1..]; + + if input.len() < length { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let mut items = vec![]; + + for _ in 0..length { + let (next_input, item) = item_parser(input)?; + items.push(item); + input = next_input; + } + + Ok((input, items)) +} + +fn ty<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], Type, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let (input, ty) = leb(input)?; + + match Type::try_from(ty) { + Ok(ty) => Ok((input, ty)), + Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))), + } +} + +pub fn parse<'input, E: ParseError<&'input [u8]>>( + bytes: &'input [u8], +) -> IResult<&'input [u8], bool, E> { + let input = bytes; + let (mut input, number_of_exports) = leb(input)?; + d!(number_of_exports); + + let mut exports = vec![]; + + for export_nth in 0..number_of_exports { + let (next_input, export_name) = string(input)?; + input = next_input; + + let (next_input, export_input_types) = list(input, ty)?; + input = next_input; + + let (next_input, export_output_types) = list(input, ty)?; + input = next_input; + + exports.push(Export { + name: export_name, + input_types: export_input_types, + output_types: export_output_types, + }); + } + + d!(exports); + + Ok((&[] as &[u8], true)) +} + +#[cfg(test)] +mod tests { + use super::parse; + use std::fs; + use wasmer_clif_backend::CraneliftCompiler; + use wasmer_runtime_core as runtime; + + fn get_module() -> runtime::Module { + runtime::compile_with( + fs::read("tests/assets/hello_world.wasm") + .expect("Failed to read `tests/assets/hello_world.wasm`.") + .as_slice(), + &CraneliftCompiler::new(), + ) + .expect("Failed to parse the `hello_world.wasm` module.") + } + + #[test] + fn test_has_custom_section() { + let module = get_module(); + let custom_section = module.info().custom_sections.get("interface-types"); + + assert!(custom_section.is_some()); + } + + #[test] + fn test_parse() { + let module = get_module(); + let custom_section_bytes = module + .info() + .custom_sections + .get("interface-types") + .unwrap() + .as_slice(); + + parse::<()>(custom_section_bytes); + } +} diff --git a/lib/interface-types/tests/assets/hello_world.wasm b/lib/interface-types/tests/assets/hello_world.wasm new file mode 100644 index 0000000000000000000000000000000000000000..88d3c079ccadb8ee3722e991a139b542f26d797f GIT binary patch literal 444 zcmZ{gO-{ow5QX22oq}-HMr_!+J0#Q#z@}LsvE&A6NDZWNY?QcES*2IwUP$1kAfcje zW=8X)_vS;<)&zjAk|p(6g8C&8%b2;3k?(|j^=(k4cdfT3xBdkfT9cJ8H??sg4^^-R zB#e6>)u#4M$F$=ei$t;z^6W;+x~LpDDXjG+#HbC8H4bvRZK_~$x3M;V>ViRX@hDTG zK1)-iG=oM8l_1-lt83z3EHvi8j5XiOg{G;}$Y>q+XrE{d1vQCRn${U^5i6xG30bPf z4w(FquF!iUjcbdhlqapPA^ R&?%U`Ad&}Od~AmXoZr-0bans$ literal 0 HcmV?d00001 diff --git a/lib/runtime-core/Cargo.toml b/lib/runtime-core/Cargo.toml index 8ce52ebe048..d475bbbb02d 100644 --- a/lib/runtime-core/Cargo.toml +++ b/lib/runtime-core/Cargo.toml @@ -55,7 +55,8 @@ cc = "1.0" debug = [] trace = ["debug"] # backend flags used in conditional compilation of Backend::variants -"backend-cranelift" = [] -"backend-singlepass" = [] -"backend-llvm" = [] -managed = [] \ No newline at end of file +backend-cranelift = [] +backend-singlepass = [] +backend-llvm = [] +managed = [] +interface-types = [] \ No newline at end of file diff --git a/lib/runtime/Cargo.toml b/lib/runtime/Cargo.toml index 2321cf99607..c99a1a52efa 100644 --- a/lib/runtime/Cargo.toml +++ b/lib/runtime/Cargo.toml @@ -41,6 +41,7 @@ singlepass = ["wasmer-singlepass-backend"] default-backend-singlepass = ["singlepass"] default-backend-llvm = ["llvm"] default-backend-cranelift = ["cranelift"] +interface-types = ["wasmer-runtime-core/interface-types"] [[bench]] name = "nginx" From 7ca546e5c5756d2ad2935ae95b9053431c18c84c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 12 Sep 2019 15:17:55 +0200 Subject: [PATCH 02/78] feat(interface-types) Continue. --- lib/interface-types/src/lib.rs | 178 +++++++++++++++++++++++++++------ 1 file changed, 148 insertions(+), 30 deletions(-) diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 2ba8a8b3f8e..9074697a884 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -22,8 +22,15 @@ macro_rules! d { }; } +macro_rules! consume { + (($input:ident, $parser_output:ident) = $parser_expression:expr) => { + let (next_input, $parser_output) = $parser_expression; + $input = next_input; + }; +} + #[derive(PartialEq, Debug)] -enum Type { +enum InterfaceType { Int, Float, Any, @@ -37,14 +44,7 @@ enum Type { AnyRef, } -#[derive(Debug)] -struct Export<'input> { - name: &'input str, - input_types: Vec, - output_types: Vec, -} - -impl TryFrom for Type { +impl TryFrom for InterfaceType { type Error = &'static str; fn try_from(code: u64) -> Result { @@ -59,12 +59,61 @@ impl TryFrom for Type { 0x7d => Self::F32, 0x7c => Self::F64, 0x6f => Self::AnyRef, + _ => return Err("Unknown interface type code."), + }) + } +} + +#[derive(PartialEq, Debug)] +enum AdapterKind { + Import, + Export, + HelperFunction, +} + +impl TryFrom for AdapterKind { + type Error = &'static str; - _ => return Err("Unknown type code."), + fn try_from(code: u8) -> Result { + Ok(match code { + 0 => Self::Import, + 1 => Self::Export, + 2 => Self::HelperFunction, + _ => return Err("Unknown adapter kind code."), }) } } +#[derive(Debug)] +struct Export<'input> { + name: &'input str, + input_types: Vec, + output_types: Vec, +} + +#[derive(Debug)] +struct Type<'input> { + name: &'input str, + fields: Vec<&'input str>, + types: Vec, +} + +#[derive(Debug)] +struct ImportedFunction<'input> { + namespace: &'input str, + name: &'input str, + input_types: Vec, + output_types: Vec, +} + +fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + Ok((&input[1..], input[0])) +} + fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); @@ -103,7 +152,7 @@ fn string<'input, E: ParseError<&'input [u8]>>( })) } -fn list<'input, I: ::std::fmt::Debug, E: ParseError<&'input [u8]>>( +fn list<'input, I, E: ParseError<&'input [u8]>>( input: &'input [u8], item_parser: fn(&'input [u8]) -> IResult<&'input [u8], I, E>, ) -> IResult<&'input [u8], Vec, E> { @@ -121,22 +170,23 @@ fn list<'input, I: ::std::fmt::Debug, E: ParseError<&'input [u8]>>( let mut items = vec![]; for _ in 0..length { - let (next_input, item) = item_parser(input)?; + consume!((input, item) = item_parser(input)?); items.push(item); - input = next_input; } Ok((input, items)) } -fn ty<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], Type, E> { +fn ty<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], InterfaceType, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); } let (input, ty) = leb(input)?; - match Type::try_from(ty) { + match InterfaceType::try_from(ty) { Ok(ty) => Ok((input, ty)), Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))), } @@ -145,30 +195,98 @@ fn ty<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'inp pub fn parse<'input, E: ParseError<&'input [u8]>>( bytes: &'input [u8], ) -> IResult<&'input [u8], bool, E> { - let input = bytes; - let (mut input, number_of_exports) = leb(input)?; - d!(number_of_exports); + let mut input = bytes; let mut exports = vec![]; + let mut types = vec![]; + let mut imported_functions = vec![]; + + { + consume!((input, number_of_exports) = leb(input)?); + d!(number_of_exports); + + for _ in 0..number_of_exports { + consume!((input, export_name) = string(input)?); + consume!((input, export_input_types) = list(input, ty)?); + consume!((input, export_output_types) = list(input, ty)?); + + exports.push(Export { + name: export_name, + input_types: export_input_types, + output_types: export_output_types, + }); + } + } - for export_nth in 0..number_of_exports { - let (next_input, export_name) = string(input)?; - input = next_input; + { + consume!((input, number_of_types) = leb(input)?); + d!(number_of_types); - let (next_input, export_input_types) = list(input, ty)?; - input = next_input; + for _ in 0..number_of_types { + consume!((input, type_name) = string(input)?); + consume!((input, type_fields) = list(input, string)?); + consume!((input, type_types) = list(input, ty)?); - let (next_input, export_output_types) = list(input, ty)?; - input = next_input; + types.push(Type { + name: type_name, + fields: type_fields, + types: type_types, + }); + } + } + + { + consume!((input, number_of_imported_functions) = leb(input)?); + d!(number_of_imported_functions); + + for _ in 0..number_of_imported_functions { + consume!((input, imported_function_namespace) = string(input)?); + consume!((input, imported_function_name) = string(input)?); + consume!((input, imported_function_input_types) = list(input, ty)?); + consume!((input, imported_function_output_types) = list(input, ty)?); + + imported_functions.push(ImportedFunction { + namespace: imported_function_namespace, + name: imported_function_name, + input_types: imported_function_input_types, + output_types: imported_function_output_types, + }); + } + } + + { + consume!((input, number_of_adapters) = leb(input)?); + d!(number_of_adapters); + + for _ in 0..number_of_adapters { + consume!((input, adapter_kind) = byte(input)?); + let adapter_kind = AdapterKind::try_from(adapter_kind) + .map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?; + d!(&adapter_kind); - exports.push(Export { - name: export_name, - input_types: export_input_types, - output_types: export_output_types, - }); + match adapter_kind { + AdapterKind::Import => { + consume!((input, adapter_namespace) = string(input)?); + d!(adapter_namespace); + + consume!((input, adapter_name) = string(input)?); + d!(adapter_name); + + consume!((input, adapter_input_types) = list(input, ty)?); + d!(adapter_input_types); + + consume!((input, adapter_output_types) = list(input, ty)?); + d!(adapter_output_types); + } + + _ => println!("kind = {:?}", adapter_kind), + } + } } d!(exports); + d!(types); + d!(imported_functions); Ok((&[] as &[u8], true)) } From 45ba77c5e30a9408106cc0d26b888d7e0cd95c11 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 12 Sep 2019 22:33:44 +0200 Subject: [PATCH 03/78] feat(interface-types) Continue. --- lib/interface-types/src/lib.rs | 444 ++++++++++++++++++++++++++------- 1 file changed, 356 insertions(+), 88 deletions(-) diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 9074697a884..a14534e2dff 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -30,7 +30,7 @@ macro_rules! consume { } #[derive(PartialEq, Debug)] -enum InterfaceType { +pub enum InterfaceType { Int, Float, Any, @@ -65,7 +65,7 @@ impl TryFrom for InterfaceType { } #[derive(PartialEq, Debug)] -enum AdapterKind { +pub enum AdapterKind { Import, Export, HelperFunction, @@ -76,36 +76,91 @@ impl TryFrom for AdapterKind { fn try_from(code: u8) -> Result { Ok(match code { - 0 => Self::Import, - 1 => Self::Export, - 2 => Self::HelperFunction, + 0x0 => Self::Import, + 0x1 => Self::Export, + 0x2 => Self::HelperFunction, _ => return Err("Unknown adapter kind code."), }) } } -#[derive(Debug)] -struct Export<'input> { +#[derive(PartialEq, Debug)] +pub enum Instruction<'input> { + ArgumentGet(u64), + Call(u64), + CallExport(&'input str), + ReadUtf8, + WriteUtf8(&'input str), + AsWasm(InterfaceType), + AsInterface(InterfaceType), + TableRefAdd, + TableRefGet, + CallMethod(u64), + MakeRecord(InterfaceType), + GetField(u64, u64), + Const(InterfaceType, u64), + FoldSeq(u64), +} + +#[derive(PartialEq, Debug)] +pub struct Export<'input> { name: &'input str, input_types: Vec, output_types: Vec, } -#[derive(Debug)] -struct Type<'input> { +#[derive(PartialEq, Debug)] +pub struct Type<'input> { name: &'input str, fields: Vec<&'input str>, types: Vec, } -#[derive(Debug)] -struct ImportedFunction<'input> { +#[derive(PartialEq, Debug)] +pub struct ImportedFunction<'input> { namespace: &'input str, name: &'input str, input_types: Vec, output_types: Vec, } +#[derive(PartialEq, Debug)] +pub enum Adapter<'input> { + Import { + namespace: &'input str, + name: &'input str, + input_types: Vec, + output_types: Vec, + instructions: Vec>, + }, + Export { + name: &'input str, + input_types: Vec, + output_types: Vec, + instructions: Vec>, + }, + HelperFunction { + name: &'input str, + input_types: Vec, + output_types: Vec, + instructions: Vec>, + }, +} + +#[derive(PartialEq, Debug)] +pub struct Forward<'input> { + name: &'input str, +} + +#[derive(PartialEq, Debug)] +pub struct Interfaces<'input> { + exports: Vec>, + types: Vec>, + imported_functions: Vec>, + adapters: Vec>, + forwards: Vec>, +} + fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); @@ -184,116 +239,264 @@ fn ty<'input, E: ParseError<&'input [u8]>>( return Err(Err::Error(make_error(input, ErrorKind::Eof))); } - let (input, ty) = leb(input)?; + let (output, ty) = leb(input)?; match InterfaceType::try_from(ty) { - Ok(ty) => Ok((input, ty)), + Ok(ty) => Ok((output, ty)), Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))), } } -pub fn parse<'input, E: ParseError<&'input [u8]>>( - bytes: &'input [u8], -) -> IResult<&'input [u8], bool, E> { - let mut input = bytes; +fn instructions<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Instruction, E> { + let (mut input, opcode) = byte(input)?; - let mut exports = vec![]; - let mut types = vec![]; - let mut imported_functions = vec![]; + Ok(match opcode { + 0x00 => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::ArgumentGet(argument_0)) + } + + 0x01 => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::Call(argument_0)) + } - { - consume!((input, number_of_exports) = leb(input)?); - d!(number_of_exports); + 0x02 => { + consume!((input, argument_0) = string(input)?); + (input, Instruction::CallExport(argument_0)) + } - for _ in 0..number_of_exports { - consume!((input, export_name) = string(input)?); - consume!((input, export_input_types) = list(input, ty)?); - consume!((input, export_output_types) = list(input, ty)?); + 0x03 => (input, Instruction::ReadUtf8), - exports.push(Export { - name: export_name, - input_types: export_input_types, - output_types: export_output_types, - }); + 0x04 => { + consume!((input, argument_0) = string(input)?); + (input, Instruction::WriteUtf8(argument_0)) } - } - { - consume!((input, number_of_types) = leb(input)?); - d!(number_of_types); + 0x05 => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::AsWasm(argument_0)) + } + + 0x06 => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::AsInterface(argument_0)) + } - for _ in 0..number_of_types { - consume!((input, type_name) = string(input)?); - consume!((input, type_fields) = list(input, string)?); - consume!((input, type_types) = list(input, ty)?); + 0x07 => (input, Instruction::TableRefAdd), - types.push(Type { - name: type_name, - fields: type_fields, - types: type_types, - }); + 0x08 => (input, Instruction::TableRefGet), + + 0x09 => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::CallMethod(argument_0)) } - } - { - consume!((input, number_of_imported_functions) = leb(input)?); - d!(number_of_imported_functions); - - for _ in 0..number_of_imported_functions { - consume!((input, imported_function_namespace) = string(input)?); - consume!((input, imported_function_name) = string(input)?); - consume!((input, imported_function_input_types) = list(input, ty)?); - consume!((input, imported_function_output_types) = list(input, ty)?); - - imported_functions.push(ImportedFunction { - namespace: imported_function_namespace, - name: imported_function_name, - input_types: imported_function_input_types, - output_types: imported_function_output_types, - }); + 0x0a => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::MakeRecord(argument_0)) } + + 0x0c => { + consume!((input, argument_0) = leb(input)?); + consume!((input, argument_1) = leb(input)?); + (input, Instruction::GetField(argument_0, argument_1)) + } + + 0x0d => { + consume!((input, argument_0) = ty(input)?); + consume!((input, argument_1) = leb(input)?); + (input, Instruction::Const(argument_0, argument_1)) + } + + 0x0e => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::FoldSeq(argument_0)) + } + + _ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))), + }) +} + +pub fn exports<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut exports = vec![]; + + consume!((input, number_of_exports) = leb(input)?); + + for _ in 0..number_of_exports { + consume!((input, export_name) = string(input)?); + consume!((input, export_input_types) = list(input, ty)?); + consume!((input, export_output_types) = list(input, ty)?); + + exports.push(Export { + name: export_name, + input_types: export_input_types, + output_types: export_output_types, + }); + } + + Ok((input, exports)) +} + +pub fn types<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut types = vec![]; + + consume!((input, number_of_types) = leb(input)?); + + for _ in 0..number_of_types { + consume!((input, type_name) = string(input)?); + consume!((input, type_fields) = list(input, string)?); + consume!((input, type_types) = list(input, ty)?); + + types.push(Type { + name: type_name, + fields: type_fields, + types: type_types, + }); } - { - consume!((input, number_of_adapters) = leb(input)?); - d!(number_of_adapters); + Ok((input, types)) +} - for _ in 0..number_of_adapters { - consume!((input, adapter_kind) = byte(input)?); - let adapter_kind = AdapterKind::try_from(adapter_kind) - .map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?; - d!(&adapter_kind); +pub fn imported_functions<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut imported_functions = vec![]; - match adapter_kind { - AdapterKind::Import => { - consume!((input, adapter_namespace) = string(input)?); - d!(adapter_namespace); + consume!((input, number_of_imported_functions) = leb(input)?); - consume!((input, adapter_name) = string(input)?); - d!(adapter_name); + for _ in 0..number_of_imported_functions { + consume!((input, imported_function_namespace) = string(input)?); + consume!((input, imported_function_name) = string(input)?); + consume!((input, imported_function_input_types) = list(input, ty)?); + consume!((input, imported_function_output_types) = list(input, ty)?); - consume!((input, adapter_input_types) = list(input, ty)?); - d!(adapter_input_types); + imported_functions.push(ImportedFunction { + namespace: imported_function_namespace, + name: imported_function_name, + input_types: imported_function_input_types, + output_types: imported_function_output_types, + }); + } - consume!((input, adapter_output_types) = list(input, ty)?); - d!(adapter_output_types); - } + Ok((input, imported_functions)) +} - _ => println!("kind = {:?}", adapter_kind), +pub fn adapters<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut adapters = vec![]; + + consume!((input, number_of_adapters) = leb(input)?); + + for _ in 0..number_of_adapters { + consume!((input, adapter_kind) = byte(input)?); + let adapter_kind = AdapterKind::try_from(adapter_kind) + .map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?; + + match adapter_kind { + AdapterKind::Import => { + consume!((input, adapter_namespace) = string(input)?); + consume!((input, adapter_name) = string(input)?); + consume!((input, adapter_input_types) = list(input, ty)?); + consume!((input, adapter_output_types) = list(input, ty)?); + consume!((input, adapter_instructions) = list(input, instructions)?); + + adapters.push(Adapter::Import { + namespace: adapter_namespace, + name: adapter_name, + input_types: adapter_input_types, + output_types: adapter_output_types, + instructions: adapter_instructions, + }); + } + + AdapterKind::Export => { + consume!((input, adapter_name) = string(input)?); + consume!((input, adapter_input_types) = list(input, ty)?); + consume!((input, adapter_output_types) = list(input, ty)?); + consume!((input, adapter_instructions) = list(input, instructions)?); + + adapters.push(Adapter::Export { + name: adapter_name, + input_types: adapter_input_types, + output_types: adapter_output_types, + instructions: adapter_instructions, + }); + } + + AdapterKind::HelperFunction => { + consume!((input, adapter_name) = string(input)?); + consume!((input, adapter_input_types) = list(input, ty)?); + consume!((input, adapter_output_types) = list(input, ty)?); + consume!((input, adapter_instructions) = list(input, instructions)?); + + adapters.push(Adapter::HelperFunction { + name: adapter_name, + input_types: adapter_input_types, + output_types: adapter_output_types, + instructions: adapter_instructions, + }); } } } - d!(exports); - d!(types); - d!(imported_functions); + Ok((input, adapters)) +} + +pub fn forwards<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut forwards = vec![]; + + consume!((input, number_of_forwards) = leb(input)?); + + for _ in 0..number_of_forwards { + consume!((input, forward_name) = string(input)?); - Ok((&[] as &[u8], true)) + forwards.push(Forward { name: forward_name }); + } + + Ok((input, forwards)) +} + +pub fn parse<'input, E: ParseError<&'input [u8]>>( + bytes: &'input [u8], +) -> IResult<&'input [u8], Interfaces, E> { + let mut input = bytes; + + consume!((input, exports) = exports(input)?); + consume!((input, types) = types(input)?); + consume!((input, imported_functions) = imported_functions(input)?); + consume!((input, adapters) = adapters(input)?); + consume!((input, forwards) = forwards(input)?); + + Ok(( + input, + Interfaces { + exports, + types, + imported_functions, + adapters, + forwards, + }, + )) } #[cfg(test)] mod tests { - use super::parse; + use super::*; use std::fs; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core as runtime; @@ -326,6 +529,71 @@ mod tests { .unwrap() .as_slice(); - parse::<()>(custom_section_bytes); + match parse::<()>(custom_section_bytes) { + Ok((remainder, interfaces)) => { + assert!(remainder.is_empty()); + assert_eq!( + interfaces, + Interfaces { + exports: vec![ + Export { + name: "strlen", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I32] + }, + Export { + name: "write_null_byte", + input_types: vec![InterfaceType::I32, InterfaceType::I32], + output_types: vec![InterfaceType::I32], + } + ], + types: vec![], + imported_functions: vec![ + ImportedFunction { + namespace: "host", + name: "console_log", + input_types: vec![InterfaceType::String], + output_types: vec![], + }, + ImportedFunction { + namespace: "host", + name: "document_title", + input_types: vec![], + output_types: vec![InterfaceType::String], + } + ], + adapters: vec![ + Adapter::Import { + namespace: "host", + name: "console_log", + input_types: vec![InterfaceType::I32], + output_types: vec![], + instructions: vec![ + Instruction::ArgumentGet(0), + Instruction::ArgumentGet(0), + Instruction::CallExport("strlen"), + Instruction::ReadUtf8, + Instruction::Call(0), + ] + }, + Adapter::Import { + namespace: "host", + name: "document_title", + input_types: vec![], + output_types: vec![InterfaceType::I32], + instructions: vec![ + Instruction::Call(1), + Instruction::WriteUtf8("alloc"), + Instruction::CallExport("write_null_byte"), + ] + } + ], + forwards: vec![Forward { name: "main" }] + } + ); + } + + Err(_) => assert!(false), + } } } From 1c1b74baa1ff083891dc6bda80e253f825df95a9 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 12 Sep 2019 22:44:20 +0200 Subject: [PATCH 04/78] feat(interface-types) Split into multiple files. --- lib/interface-types/src/ast.rs | 133 ++++++++ lib/interface-types/src/lib.rs | 501 +---------------------------- lib/interface-types/src/macros.rs | 25 ++ lib/interface-types/src/parsers.rs | 339 +++++++++++++++++++ 4 files changed, 503 insertions(+), 495 deletions(-) create mode 100644 lib/interface-types/src/ast.rs create mode 100644 lib/interface-types/src/macros.rs create mode 100644 lib/interface-types/src/parsers.rs diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs new file mode 100644 index 00000000000..764c96ecf00 --- /dev/null +++ b/lib/interface-types/src/ast.rs @@ -0,0 +1,133 @@ +use std::{convert::TryFrom, str}; + +#[derive(PartialEq, Debug)] +pub enum InterfaceType { + Int, + Float, + Any, + String, + Seq, + + I32, + I64, + F32, + F64, + AnyRef, +} + +impl TryFrom for InterfaceType { + type Error = &'static str; + + fn try_from(code: u64) -> Result { + Ok(match code { + 0x7fff => Self::Int, + 0x7ffe => Self::Float, + 0x7ffd => Self::Any, + 0x7ffc => Self::String, + 0x7ffb => Self::Seq, + 0x7f => Self::I32, + 0x7e => Self::I64, + 0x7d => Self::F32, + 0x7c => Self::F64, + 0x6f => Self::AnyRef, + _ => return Err("Unknown interface type code."), + }) + } +} + +#[derive(PartialEq, Debug)] +pub enum AdapterKind { + Import, + Export, + HelperFunction, +} + +impl TryFrom for AdapterKind { + type Error = &'static str; + + fn try_from(code: u8) -> Result { + Ok(match code { + 0x0 => Self::Import, + 0x1 => Self::Export, + 0x2 => Self::HelperFunction, + _ => return Err("Unknown adapter kind code."), + }) + } +} + +#[derive(PartialEq, Debug)] +pub enum Instruction<'input> { + ArgumentGet(u64), + Call(u64), + CallExport(&'input str), + ReadUtf8, + WriteUtf8(&'input str), + AsWasm(InterfaceType), + AsInterface(InterfaceType), + TableRefAdd, + TableRefGet, + CallMethod(u64), + MakeRecord(InterfaceType), + GetField(u64, u64), + Const(InterfaceType, u64), + FoldSeq(u64), +} + +#[derive(PartialEq, Debug)] +pub struct Export<'input> { + pub name: &'input str, + pub input_types: Vec, + pub output_types: Vec, +} + +#[derive(PartialEq, Debug)] +pub struct Type<'input> { + pub name: &'input str, + pub fields: Vec<&'input str>, + pub types: Vec, +} + +#[derive(PartialEq, Debug)] +pub struct ImportedFunction<'input> { + pub namespace: &'input str, + pub name: &'input str, + pub input_types: Vec, + pub output_types: Vec, +} + +#[derive(PartialEq, Debug)] +pub enum Adapter<'input> { + Import { + namespace: &'input str, + name: &'input str, + input_types: Vec, + output_types: Vec, + instructions: Vec>, + }, + Export { + name: &'input str, + input_types: Vec, + output_types: Vec, + instructions: Vec>, + }, + HelperFunction { + name: &'input str, + input_types: Vec, + output_types: Vec, + instructions: Vec>, + }, +} + +#[derive(PartialEq, Debug)] +pub struct Forward<'input> { + pub name: &'input str, +} + +#[derive(PartialEq, Debug)] +pub struct Interfaces<'input> { + pub exports: Vec>, + pub types: Vec>, + pub imported_functions: Vec>, + pub adapters: Vec>, + pub forwards: Vec>, +} diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index a14534e2dff..e2710761bab 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -1,502 +1,13 @@ -use nom::{ - error::{make_error, ErrorKind, ParseError}, - Err, IResult, -}; -use std::{convert::TryFrom, str}; +pub mod ast; +#[macro_use] +mod macros; +pub mod parsers; -macro_rules! d { - ($expression:expr) => { - match $expression { - tmp => { - eprintln!( - "[{}:{}] {} = {:?}", - file!(), - line!(), - stringify!($expression), - &tmp - ); - - tmp - } - } - }; -} - -macro_rules! consume { - (($input:ident, $parser_output:ident) = $parser_expression:expr) => { - let (next_input, $parser_output) = $parser_expression; - $input = next_input; - }; -} - -#[derive(PartialEq, Debug)] -pub enum InterfaceType { - Int, - Float, - Any, - String, - Seq, - - I32, - I64, - F32, - F64, - AnyRef, -} - -impl TryFrom for InterfaceType { - type Error = &'static str; - - fn try_from(code: u64) -> Result { - Ok(match code { - 0x7fff => Self::Int, - 0x7ffe => Self::Float, - 0x7ffd => Self::Any, - 0x7ffc => Self::String, - 0x7ffb => Self::Seq, - 0x7f => Self::I32, - 0x7e => Self::I64, - 0x7d => Self::F32, - 0x7c => Self::F64, - 0x6f => Self::AnyRef, - _ => return Err("Unknown interface type code."), - }) - } -} - -#[derive(PartialEq, Debug)] -pub enum AdapterKind { - Import, - Export, - HelperFunction, -} - -impl TryFrom for AdapterKind { - type Error = &'static str; - - fn try_from(code: u8) -> Result { - Ok(match code { - 0x0 => Self::Import, - 0x1 => Self::Export, - 0x2 => Self::HelperFunction, - _ => return Err("Unknown adapter kind code."), - }) - } -} - -#[derive(PartialEq, Debug)] -pub enum Instruction<'input> { - ArgumentGet(u64), - Call(u64), - CallExport(&'input str), - ReadUtf8, - WriteUtf8(&'input str), - AsWasm(InterfaceType), - AsInterface(InterfaceType), - TableRefAdd, - TableRefGet, - CallMethod(u64), - MakeRecord(InterfaceType), - GetField(u64, u64), - Const(InterfaceType, u64), - FoldSeq(u64), -} - -#[derive(PartialEq, Debug)] -pub struct Export<'input> { - name: &'input str, - input_types: Vec, - output_types: Vec, -} - -#[derive(PartialEq, Debug)] -pub struct Type<'input> { - name: &'input str, - fields: Vec<&'input str>, - types: Vec, -} - -#[derive(PartialEq, Debug)] -pub struct ImportedFunction<'input> { - namespace: &'input str, - name: &'input str, - input_types: Vec, - output_types: Vec, -} - -#[derive(PartialEq, Debug)] -pub enum Adapter<'input> { - Import { - namespace: &'input str, - name: &'input str, - input_types: Vec, - output_types: Vec, - instructions: Vec>, - }, - Export { - name: &'input str, - input_types: Vec, - output_types: Vec, - instructions: Vec>, - }, - HelperFunction { - name: &'input str, - input_types: Vec, - output_types: Vec, - instructions: Vec>, - }, -} - -#[derive(PartialEq, Debug)] -pub struct Forward<'input> { - name: &'input str, -} - -#[derive(PartialEq, Debug)] -pub struct Interfaces<'input> { - exports: Vec>, - types: Vec>, - imported_functions: Vec>, - adapters: Vec>, - forwards: Vec>, -} - -fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> { - if input.is_empty() { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - Ok((&input[1..], input[0])) -} - -fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { - if input.is_empty() { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - let (output, bytes) = match input.iter().position(|&byte| byte & 0x80 == 0) { - Some(position) => (&input[position + 1..], &input[..position + 1]), - None => (&[] as &[u8], input), - }; - - Ok(( - output, - bytes - .iter() - .rev() - .fold(0, |acc, byte| (acc << 7) | (byte & 0x7f) as u64), - )) -} - -fn string<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], &'input str, E> { - if input.is_empty() { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - let length = input[0] as usize; - let input = &input[1..]; - - if input.len() < length { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - Ok((&input[length..], unsafe { - str::from_utf8_unchecked(&input[..length]) - })) -} - -fn list<'input, I, E: ParseError<&'input [u8]>>( - input: &'input [u8], - item_parser: fn(&'input [u8]) -> IResult<&'input [u8], I, E>, -) -> IResult<&'input [u8], Vec, E> { - if input.is_empty() { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - let length = input[0] as usize; - let mut input = &input[1..]; - - if input.len() < length { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - let mut items = vec![]; - - for _ in 0..length { - consume!((input, item) = item_parser(input)?); - items.push(item); - } - - Ok((input, items)) -} - -fn ty<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], InterfaceType, E> { - if input.is_empty() { - return Err(Err::Error(make_error(input, ErrorKind::Eof))); - } - - let (output, ty) = leb(input)?; - - match InterfaceType::try_from(ty) { - Ok(ty) => Ok((output, ty)), - Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))), - } -} - -fn instructions<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], Instruction, E> { - let (mut input, opcode) = byte(input)?; - - Ok(match opcode { - 0x00 => { - consume!((input, argument_0) = leb(input)?); - (input, Instruction::ArgumentGet(argument_0)) - } - - 0x01 => { - consume!((input, argument_0) = leb(input)?); - (input, Instruction::Call(argument_0)) - } - - 0x02 => { - consume!((input, argument_0) = string(input)?); - (input, Instruction::CallExport(argument_0)) - } - - 0x03 => (input, Instruction::ReadUtf8), - - 0x04 => { - consume!((input, argument_0) = string(input)?); - (input, Instruction::WriteUtf8(argument_0)) - } - - 0x05 => { - consume!((input, argument_0) = ty(input)?); - (input, Instruction::AsWasm(argument_0)) - } - - 0x06 => { - consume!((input, argument_0) = ty(input)?); - (input, Instruction::AsInterface(argument_0)) - } - - 0x07 => (input, Instruction::TableRefAdd), - - 0x08 => (input, Instruction::TableRefGet), - - 0x09 => { - consume!((input, argument_0) = leb(input)?); - (input, Instruction::CallMethod(argument_0)) - } - - 0x0a => { - consume!((input, argument_0) = ty(input)?); - (input, Instruction::MakeRecord(argument_0)) - } - - 0x0c => { - consume!((input, argument_0) = leb(input)?); - consume!((input, argument_1) = leb(input)?); - (input, Instruction::GetField(argument_0, argument_1)) - } - - 0x0d => { - consume!((input, argument_0) = ty(input)?); - consume!((input, argument_1) = leb(input)?); - (input, Instruction::Const(argument_0, argument_1)) - } - - 0x0e => { - consume!((input, argument_0) = leb(input)?); - (input, Instruction::FoldSeq(argument_0)) - } - - _ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))), - }) -} - -pub fn exports<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - let mut exports = vec![]; - - consume!((input, number_of_exports) = leb(input)?); - - for _ in 0..number_of_exports { - consume!((input, export_name) = string(input)?); - consume!((input, export_input_types) = list(input, ty)?); - consume!((input, export_output_types) = list(input, ty)?); - - exports.push(Export { - name: export_name, - input_types: export_input_types, - output_types: export_output_types, - }); - } - - Ok((input, exports)) -} - -pub fn types<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - let mut types = vec![]; - - consume!((input, number_of_types) = leb(input)?); - - for _ in 0..number_of_types { - consume!((input, type_name) = string(input)?); - consume!((input, type_fields) = list(input, string)?); - consume!((input, type_types) = list(input, ty)?); - - types.push(Type { - name: type_name, - fields: type_fields, - types: type_types, - }); - } - - Ok((input, types)) -} - -pub fn imported_functions<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - let mut imported_functions = vec![]; - - consume!((input, number_of_imported_functions) = leb(input)?); - - for _ in 0..number_of_imported_functions { - consume!((input, imported_function_namespace) = string(input)?); - consume!((input, imported_function_name) = string(input)?); - consume!((input, imported_function_input_types) = list(input, ty)?); - consume!((input, imported_function_output_types) = list(input, ty)?); - - imported_functions.push(ImportedFunction { - namespace: imported_function_namespace, - name: imported_function_name, - input_types: imported_function_input_types, - output_types: imported_function_output_types, - }); - } - - Ok((input, imported_functions)) -} - -pub fn adapters<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - let mut adapters = vec![]; - - consume!((input, number_of_adapters) = leb(input)?); - - for _ in 0..number_of_adapters { - consume!((input, adapter_kind) = byte(input)?); - let adapter_kind = AdapterKind::try_from(adapter_kind) - .map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?; - - match adapter_kind { - AdapterKind::Import => { - consume!((input, adapter_namespace) = string(input)?); - consume!((input, adapter_name) = string(input)?); - consume!((input, adapter_input_types) = list(input, ty)?); - consume!((input, adapter_output_types) = list(input, ty)?); - consume!((input, adapter_instructions) = list(input, instructions)?); - - adapters.push(Adapter::Import { - namespace: adapter_namespace, - name: adapter_name, - input_types: adapter_input_types, - output_types: adapter_output_types, - instructions: adapter_instructions, - }); - } - - AdapterKind::Export => { - consume!((input, adapter_name) = string(input)?); - consume!((input, adapter_input_types) = list(input, ty)?); - consume!((input, adapter_output_types) = list(input, ty)?); - consume!((input, adapter_instructions) = list(input, instructions)?); - - adapters.push(Adapter::Export { - name: adapter_name, - input_types: adapter_input_types, - output_types: adapter_output_types, - instructions: adapter_instructions, - }); - } - - AdapterKind::HelperFunction => { - consume!((input, adapter_name) = string(input)?); - consume!((input, adapter_input_types) = list(input, ty)?); - consume!((input, adapter_output_types) = list(input, ty)?); - consume!((input, adapter_instructions) = list(input, instructions)?); - - adapters.push(Adapter::HelperFunction { - name: adapter_name, - input_types: adapter_input_types, - output_types: adapter_output_types, - instructions: adapter_instructions, - }); - } - } - } - - Ok((input, adapters)) -} - -pub fn forwards<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], -) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - let mut forwards = vec![]; - - consume!((input, number_of_forwards) = leb(input)?); - - for _ in 0..number_of_forwards { - consume!((input, forward_name) = string(input)?); - - forwards.push(Forward { name: forward_name }); - } - - Ok((input, forwards)) -} - -pub fn parse<'input, E: ParseError<&'input [u8]>>( - bytes: &'input [u8], -) -> IResult<&'input [u8], Interfaces, E> { - let mut input = bytes; - - consume!((input, exports) = exports(input)?); - consume!((input, types) = types(input)?); - consume!((input, imported_functions) = imported_functions(input)?); - consume!((input, adapters) = adapters(input)?); - consume!((input, forwards) = forwards(input)?); - - Ok(( - input, - Interfaces { - exports, - types, - imported_functions, - adapters, - forwards, - }, - )) -} +pub use parsers::parse; #[cfg(test)] mod tests { - use super::*; + use crate::{ast::*, parse}; use std::fs; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core as runtime; diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs new file mode 100644 index 00000000000..7dfc839ca44 --- /dev/null +++ b/lib/interface-types/src/macros.rs @@ -0,0 +1,25 @@ +#[allow(unused)] +macro_rules! d { + ($expression:expr) => { + match $expression { + tmp => { + eprintln!( + "[{}:{}] {} = {:?}", + file!(), + line!(), + stringify!($expression), + &tmp + ); + + tmp + } + } + }; +} + +macro_rules! consume { + (($input:ident, $parser_output:ident) = $parser_expression:expr) => { + let (next_input, $parser_output) = $parser_expression; + $input = next_input; + }; +} diff --git a/lib/interface-types/src/parsers.rs b/lib/interface-types/src/parsers.rs new file mode 100644 index 00000000000..4eb93c4d450 --- /dev/null +++ b/lib/interface-types/src/parsers.rs @@ -0,0 +1,339 @@ +use crate::ast::*; +use nom::{ + error::{make_error, ErrorKind, ParseError}, + Err, IResult, +}; +use std::{convert::TryFrom, str}; + +fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + Ok((&input[1..], input[0])) +} + +fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let (output, bytes) = match input.iter().position(|&byte| byte & 0x80 == 0) { + Some(position) => (&input[position + 1..], &input[..position + 1]), + None => (&[] as &[u8], input), + }; + + Ok(( + output, + bytes + .iter() + .rev() + .fold(0, |acc, byte| (acc << 7) | (byte & 0x7f) as u64), + )) +} + +fn string<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], &'input str, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let length = input[0] as usize; + let input = &input[1..]; + + if input.len() < length { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + Ok((&input[length..], unsafe { + str::from_utf8_unchecked(&input[..length]) + })) +} + +fn list<'input, I, E: ParseError<&'input [u8]>>( + input: &'input [u8], + item_parser: fn(&'input [u8]) -> IResult<&'input [u8], I, E>, +) -> IResult<&'input [u8], Vec, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let length = input[0] as usize; + let mut input = &input[1..]; + + if input.len() < length { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let mut items = vec![]; + + for _ in 0..length { + consume!((input, item) = item_parser(input)?); + items.push(item); + } + + Ok((input, items)) +} + +fn ty<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], InterfaceType, E> { + if input.is_empty() { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + + let (output, ty) = leb(input)?; + + match InterfaceType::try_from(ty) { + Ok(ty) => Ok((output, ty)), + Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))), + } +} + +fn instructions<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Instruction, E> { + let (mut input, opcode) = byte(input)?; + + Ok(match opcode { + 0x00 => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::ArgumentGet(argument_0)) + } + + 0x01 => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::Call(argument_0)) + } + + 0x02 => { + consume!((input, argument_0) = string(input)?); + (input, Instruction::CallExport(argument_0)) + } + + 0x03 => (input, Instruction::ReadUtf8), + + 0x04 => { + consume!((input, argument_0) = string(input)?); + (input, Instruction::WriteUtf8(argument_0)) + } + + 0x05 => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::AsWasm(argument_0)) + } + + 0x06 => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::AsInterface(argument_0)) + } + + 0x07 => (input, Instruction::TableRefAdd), + + 0x08 => (input, Instruction::TableRefGet), + + 0x09 => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::CallMethod(argument_0)) + } + + 0x0a => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::MakeRecord(argument_0)) + } + + 0x0c => { + consume!((input, argument_0) = leb(input)?); + consume!((input, argument_1) = leb(input)?); + (input, Instruction::GetField(argument_0, argument_1)) + } + + 0x0d => { + consume!((input, argument_0) = ty(input)?); + consume!((input, argument_1) = leb(input)?); + (input, Instruction::Const(argument_0, argument_1)) + } + + 0x0e => { + consume!((input, argument_0) = leb(input)?); + (input, Instruction::FoldSeq(argument_0)) + } + + _ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))), + }) +} + +pub fn exports<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut exports = vec![]; + + consume!((input, number_of_exports) = leb(input)?); + + for _ in 0..number_of_exports { + consume!((input, export_name) = string(input)?); + consume!((input, export_input_types) = list(input, ty)?); + consume!((input, export_output_types) = list(input, ty)?); + + exports.push(Export { + name: export_name, + input_types: export_input_types, + output_types: export_output_types, + }); + } + + Ok((input, exports)) +} + +pub fn types<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut types = vec![]; + + consume!((input, number_of_types) = leb(input)?); + + for _ in 0..number_of_types { + consume!((input, type_name) = string(input)?); + consume!((input, type_fields) = list(input, string)?); + consume!((input, type_types) = list(input, ty)?); + + types.push(Type { + name: type_name, + fields: type_fields, + types: type_types, + }); + } + + Ok((input, types)) +} + +pub fn imported_functions<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut imported_functions = vec![]; + + consume!((input, number_of_imported_functions) = leb(input)?); + + for _ in 0..number_of_imported_functions { + consume!((input, imported_function_namespace) = string(input)?); + consume!((input, imported_function_name) = string(input)?); + consume!((input, imported_function_input_types) = list(input, ty)?); + consume!((input, imported_function_output_types) = list(input, ty)?); + + imported_functions.push(ImportedFunction { + namespace: imported_function_namespace, + name: imported_function_name, + input_types: imported_function_input_types, + output_types: imported_function_output_types, + }); + } + + Ok((input, imported_functions)) +} + +pub fn adapters<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut adapters = vec![]; + + consume!((input, number_of_adapters) = leb(input)?); + + for _ in 0..number_of_adapters { + consume!((input, adapter_kind) = byte(input)?); + let adapter_kind = AdapterKind::try_from(adapter_kind) + .map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?; + + match adapter_kind { + AdapterKind::Import => { + consume!((input, adapter_namespace) = string(input)?); + consume!((input, adapter_name) = string(input)?); + consume!((input, adapter_input_types) = list(input, ty)?); + consume!((input, adapter_output_types) = list(input, ty)?); + consume!((input, adapter_instructions) = list(input, instructions)?); + + adapters.push(Adapter::Import { + namespace: adapter_namespace, + name: adapter_name, + input_types: adapter_input_types, + output_types: adapter_output_types, + instructions: adapter_instructions, + }); + } + + AdapterKind::Export => { + consume!((input, adapter_name) = string(input)?); + consume!((input, adapter_input_types) = list(input, ty)?); + consume!((input, adapter_output_types) = list(input, ty)?); + consume!((input, adapter_instructions) = list(input, instructions)?); + + adapters.push(Adapter::Export { + name: adapter_name, + input_types: adapter_input_types, + output_types: adapter_output_types, + instructions: adapter_instructions, + }); + } + + AdapterKind::HelperFunction => { + consume!((input, adapter_name) = string(input)?); + consume!((input, adapter_input_types) = list(input, ty)?); + consume!((input, adapter_output_types) = list(input, ty)?); + consume!((input, adapter_instructions) = list(input, instructions)?); + + adapters.push(Adapter::HelperFunction { + name: adapter_name, + input_types: adapter_input_types, + output_types: adapter_output_types, + instructions: adapter_instructions, + }); + } + } + } + + Ok((input, adapters)) +} + +pub fn forwards<'input, E: ParseError<&'input [u8]>>( + input: &'input [u8], +) -> IResult<&'input [u8], Vec, E> { + let mut input = input; + let mut forwards = vec![]; + + consume!((input, number_of_forwards) = leb(input)?); + + for _ in 0..number_of_forwards { + consume!((input, forward_name) = string(input)?); + + forwards.push(Forward { name: forward_name }); + } + + Ok((input, forwards)) +} + +pub fn parse<'input, E: ParseError<&'input [u8]>>( + bytes: &'input [u8], +) -> IResult<&'input [u8], Interfaces, E> { + let mut input = bytes; + + consume!((input, exports) = exports(input)?); + consume!((input, types) = types(input)?); + consume!((input, imported_functions) = imported_functions(input)?); + consume!((input, adapters) = adapters(input)?); + consume!((input, forwards) = forwards(input)?); + + Ok(( + input, + Interfaces { + exports, + types, + imported_functions, + adapters, + forwards, + }, + )) +} From dc254e084502e802ed8769b8ae7b4c81258c4d7e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 13 Sep 2019 12:00:05 +0200 Subject: [PATCH 05/78] =?UTF-8?q?test(interface-types)=20Add=20test=20case?= =?UTF-8?q?=20for=20=E2=80=9Cunit=E2=80=9D=20parsers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/interface-types/src/parsers.rs | 137 +++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/lib/interface-types/src/parsers.rs b/lib/interface-types/src/parsers.rs index 4eb93c4d450..c5456d3eea6 100644 --- a/lib/interface-types/src/parsers.rs +++ b/lib/interface-types/src/parsers.rs @@ -337,3 +337,140 @@ pub fn parse<'input, E: ParseError<&'input [u8]>>( }, )) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_byte() { + let input = &[0x01, 0x02, 0x03]; + let output = Ok((&[0x02, 0x03][..], 0x01u8)); + + assert_eq!(byte::<()>(input), output); + } + + #[test] + fn test_leb_1_byte() { + let input = &[0x01, 0x02, 0x03]; + let output = Ok((&[0x02, 0x03][..], 0x01u64)); + + assert_eq!(leb::<()>(input), output); + } + + #[test] + fn test_leb_3_bytes() { + let input = &[0xfc, 0xff, 0x01, 0x02]; + let output = Ok((&[0x02][..], 0x7ffcu64)); + + assert_eq!(leb::<()>(input), output); + } + + #[test] + fn test_string() { + let input = &[ + 0x03, // string of 3 bytes + 0x61, // "a" + 0x62, // "b" + 0x63, // "c" + 0x64, 0x65, + ]; + let output = Ok((&[0x64, 0x65][..], "abc")); + + assert_eq!(string::<()>(input), output); + } + + #[test] + fn test_list() { + let input = &[ + 0x02, // list of 2 items + 0x01, // string of 1 byte + 0x61, // "a" + 0x02, // string of 2 bytes + 0x62, // "b" + 0x63, // "c" + 0x07, + ]; + let output = Ok((&[0x07][..], vec!["a", "bc"])); + + assert_eq!(list::<&str, ()>(input, string), output); + } + + #[test] + fn test_ty() { + let input = &[ + 0x0a, // list of 10 items + 0xff, 0xff, 0x01, // Int + 0xfe, 0xff, 0x01, // Float + 0xfd, 0xff, 0x01, // Any + 0xfc, 0xff, 0x01, // String + 0xfb, 0xff, 0x01, // Seq + 0x7f, // I32 + 0x7e, // I64 + 0x7d, // F32 + 0x7c, // F64 + 0x6f, // AnyRef + 0x01, + ]; + let output = Ok(( + &[0x01][..], + vec![ + InterfaceType::Int, + InterfaceType::Float, + InterfaceType::Any, + InterfaceType::String, + InterfaceType::Seq, + InterfaceType::I32, + InterfaceType::I64, + InterfaceType::F32, + InterfaceType::F64, + InterfaceType::AnyRef, + ], + )); + + assert_eq!(list::(input, ty), output); + } + + #[test] + fn test_instructions() { + let input = &[ + 0x0e, // list of 14 items + 0x00, 0x01, // ArgumentGet(1) + 0x01, 0x01, // Call(1) + 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport("abc") + 0x03, // ReadUtf8 + 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8("abc") + 0x05, 0xff, 0xff, 0x01, // AsWasm(Int) + 0x06, 0x7e, // AsInterface(I64) + 0x07, // TableRefAdd + 0x08, // TableRefGet + 0x09, 0x01, // CallMethod(1) + 0x0a, 0x7f, // MakeRecord(I32) + 0x0c, 0x01, 0x02, // GetField(1, 2) + 0x0d, 0x7f, 0x01, // Const(I32, 1) + 0x0e, 0x01, // FoldSeq(1) + 0x0a, + ]; + let output = Ok(( + &[0x0a][..], + vec![ + Instruction::ArgumentGet(1), + Instruction::Call(1), + Instruction::CallExport("abc"), + Instruction::ReadUtf8, + Instruction::WriteUtf8("abc"), + Instruction::AsWasm(InterfaceType::Int), + Instruction::AsInterface(InterfaceType::I64), + Instruction::TableRefAdd, + Instruction::TableRefGet, + Instruction::CallMethod(1), + Instruction::MakeRecord(InterfaceType::I32), + Instruction::GetField(1, 2), + Instruction::Const(InterfaceType::I32, 1), + Instruction::FoldSeq(1), + ], + )); + + assert_eq!(list::(input, instructions), output); + } +} From 24ac7a6c41f2294d8cdbd1f5eef63246ca0b59fb Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 13 Sep 2019 14:24:03 +0200 Subject: [PATCH 06/78] test(interface-types) Add test cases for higher-level parsers. --- lib/interface-types/src/parsers.rs | 182 +++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/lib/interface-types/src/parsers.rs b/lib/interface-types/src/parsers.rs index c5456d3eea6..9d0073e5207 100644 --- a/lib/interface-types/src/parsers.rs +++ b/lib/interface-types/src/parsers.rs @@ -473,4 +473,186 @@ mod tests { assert_eq!(list::(input, instructions), output); } + + #[test] + fn test_exports() { + let input = &[ + 0x02, // 2 exports + 0x02, // string of 2 bytes + 0x61, 0x62, // "a", "b" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7f, // I32 + 0x02, // string of 2 bytes + 0x63, 0x64, // "c", "d" + 0x00, // list of 0 item + 0x00, // list of 0 item + ]; + let output = Ok(( + &[] as &[u8], + vec![ + Export { + name: "ab", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I32], + }, + Export { + name: "cd", + input_types: vec![], + output_types: vec![], + }, + ], + )); + + assert_eq!(exports::<()>(input), output); + } + + #[test] + fn test_types() { + let input = &[ + 0x01, // 1 type + 0x02, // string of 2 bytes + 0x61, 0x62, // "a", "b" + 0x02, // list of 2 items + 0x02, // string of 2 bytes + 0x63, 0x64, // "c", "d" + 0x01, // string of 1 byte + 0x65, // "e" + 0x02, // list of 2 items + 0x7f, // I32 + 0x7f, // I32 + ]; + let output = Ok(( + &[] as &[u8], + vec![Type { + name: "ab", + fields: vec!["cd", "e"], + types: vec![InterfaceType::I32, InterfaceType::I32], + }], + )); + + assert_eq!(types::<()>(input), output); + } + + #[test] + fn test_imported_functions() { + let input = &[ + 0x02, // 2 imported functions + 0x01, // string of 1 byte + 0x61, // "a" + 0x01, // string of 1 byte + 0x62, // "b" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7e, // I64 + 0x01, // string of 1 byte + 0x63, // "c" + 0x01, // string of 1 byte + 0x64, // "d" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7e, // I64 + ]; + let output = Ok(( + &[] as &[u8], + vec![ + ImportedFunction { + namespace: "a", + name: "b", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I64], + }, + ImportedFunction { + namespace: "c", + name: "d", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I64], + }, + ], + )); + + assert_eq!(imported_functions::<()>(input), output); + } + + #[test] + fn test_adapters() { + let input = &[ + 0x03, // 3 adapters + 0x00, // adapter kind: import + 0x01, // string of 1 byte + 0x61, // "a" + 0x01, // string of 1 byte + 0x62, // "b" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x00, 0x01, // ArgumentGet(1) + 0x01, // adapter kind: export + 0x01, // string of 1 byte + 0x63, // "c" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x00, 0x01, // ArgumentGet(1) + 0x02, // adapter kind: helper function + 0x01, // string of 1 byte + 0x64, // "d" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x00, 0x01, // ArgumentGet(1) + ]; + let output = Ok(( + &[] as &[u8], + vec![ + Adapter::Import { + namespace: "a", + name: "b", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I32], + instructions: vec![Instruction::ArgumentGet(1)], + }, + Adapter::Export { + name: "c", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I32], + instructions: vec![Instruction::ArgumentGet(1)], + }, + Adapter::HelperFunction { + name: "d", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I32], + instructions: vec![Instruction::ArgumentGet(1)], + }, + ], + )); + + assert_eq!(adapters::<()>(input), output); + } + + #[test] + fn test_forwards() { + let input = &[ + 0x02, // 2 adapters + 0x01, // string of 1 byte + 0x61, // "a" + 0x02, // string of 2 bytes + 0x62, 0x63, // "b", "c" + ]; + let output = Ok(( + &[] as &[u8], + vec![Forward { name: "a" }, Forward { name: "bc" }], + )); + + assert_eq!(forwards::<()>(input), output); + } } From 5a8a2b90ed9daab79099a4c60cd5a83d943d64d7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 13 Sep 2019 14:26:49 +0200 Subject: [PATCH 07/78] fix(interface-types) Fix visibility of various symbols. --- lib/interface-types/src/ast.rs | 2 +- lib/interface-types/src/parsers.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 764c96ecf00..2f5c5b80867 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -36,7 +36,7 @@ impl TryFrom for InterfaceType { } #[derive(PartialEq, Debug)] -pub enum AdapterKind { +pub(crate) enum AdapterKind { Import, Export, HelperFunction, diff --git a/lib/interface-types/src/parsers.rs b/lib/interface-types/src/parsers.rs index 9d0073e5207..9d88ab130ca 100644 --- a/lib/interface-types/src/parsers.rs +++ b/lib/interface-types/src/parsers.rs @@ -164,7 +164,7 @@ fn instructions<'input, E: ParseError<&'input [u8]>>( }) } -pub fn exports<'input, E: ParseError<&'input [u8]>>( +fn exports<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; @@ -187,7 +187,7 @@ pub fn exports<'input, E: ParseError<&'input [u8]>>( Ok((input, exports)) } -pub fn types<'input, E: ParseError<&'input [u8]>>( +fn types<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; @@ -210,7 +210,7 @@ pub fn types<'input, E: ParseError<&'input [u8]>>( Ok((input, types)) } -pub fn imported_functions<'input, E: ParseError<&'input [u8]>>( +fn imported_functions<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; @@ -235,7 +235,7 @@ pub fn imported_functions<'input, E: ParseError<&'input [u8]>>( Ok((input, imported_functions)) } -pub fn adapters<'input, E: ParseError<&'input [u8]>>( +fn adapters<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; @@ -298,7 +298,7 @@ pub fn adapters<'input, E: ParseError<&'input [u8]>>( Ok((input, adapters)) } -pub fn forwards<'input, E: ParseError<&'input [u8]>>( +fn forwards<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; From a7ffffc8b4329fb217c3960cdbfda65a3089a566 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 13 Sep 2019 14:50:17 +0200 Subject: [PATCH 08/78] feat(interface-types) Move `TryFrom` from the `ast` to the `binary` module. --- lib/interface-types/src/ast.rs | 35 +------------------ .../src/{parsers.rs => decoders/binary.rs} | 33 +++++++++++++++++ 2 files changed, 34 insertions(+), 34 deletions(-) rename lib/interface-types/src/{parsers.rs => decoders/binary.rs} (95%) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 2f5c5b80867..8a782217574 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, str}; +use std::str; #[derive(PartialEq, Debug)] pub enum InterfaceType { @@ -15,26 +15,6 @@ pub enum InterfaceType { AnyRef, } -impl TryFrom for InterfaceType { - type Error = &'static str; - - fn try_from(code: u64) -> Result { - Ok(match code { - 0x7fff => Self::Int, - 0x7ffe => Self::Float, - 0x7ffd => Self::Any, - 0x7ffc => Self::String, - 0x7ffb => Self::Seq, - 0x7f => Self::I32, - 0x7e => Self::I64, - 0x7d => Self::F32, - 0x7c => Self::F64, - 0x6f => Self::AnyRef, - _ => return Err("Unknown interface type code."), - }) - } -} - #[derive(PartialEq, Debug)] pub(crate) enum AdapterKind { Import, @@ -42,19 +22,6 @@ pub(crate) enum AdapterKind { HelperFunction, } -impl TryFrom for AdapterKind { - type Error = &'static str; - - fn try_from(code: u8) -> Result { - Ok(match code { - 0x0 => Self::Import, - 0x1 => Self::Export, - 0x2 => Self::HelperFunction, - _ => return Err("Unknown adapter kind code."), - }) - } -} - #[derive(PartialEq, Debug)] pub enum Instruction<'input> { ArgumentGet(u64), diff --git a/lib/interface-types/src/parsers.rs b/lib/interface-types/src/decoders/binary.rs similarity index 95% rename from lib/interface-types/src/parsers.rs rename to lib/interface-types/src/decoders/binary.rs index 9d88ab130ca..614b7b8281c 100644 --- a/lib/interface-types/src/parsers.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -5,6 +5,39 @@ use nom::{ }; use std::{convert::TryFrom, str}; +impl TryFrom for InterfaceType { + type Error = &'static str; + + fn try_from(code: u64) -> Result { + Ok(match code { + 0x7fff => Self::Int, + 0x7ffe => Self::Float, + 0x7ffd => Self::Any, + 0x7ffc => Self::String, + 0x7ffb => Self::Seq, + 0x7f => Self::I32, + 0x7e => Self::I64, + 0x7d => Self::F32, + 0x7c => Self::F64, + 0x6f => Self::AnyRef, + _ => return Err("Unknown interface type code."), + }) + } +} + +impl TryFrom for AdapterKind { + type Error = &'static str; + + fn try_from(code: u8) -> Result { + Ok(match code { + 0x0 => Self::Import, + 0x1 => Self::Export, + 0x2 => Self::HelperFunction, + _ => return Err("Unknown adapter kind code."), + }) + } +} + fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); From 4ba9aace64274e600177c76962bca62d312684ae Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 13 Sep 2019 15:39:46 +0200 Subject: [PATCH 09/78] fix(interface-types) `get-field` #1 argument is of type `InterfaceType`. --- lib/interface-types/src/ast.rs | 5 ++--- lib/interface-types/src/decoders/binary.rs | 6 +++--- lib/interface-types/src/decoders/mod.rs | 1 + 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 lib/interface-types/src/decoders/mod.rs diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 8a782217574..b966f010e4f 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -1,13 +1,12 @@ use std::str; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Clone, Copy, Debug)] pub enum InterfaceType { Int, Float, Any, String, Seq, - I32, I64, F32, @@ -35,7 +34,7 @@ pub enum Instruction<'input> { TableRefGet, CallMethod(u64), MakeRecord(InterfaceType), - GetField(u64, u64), + GetField(InterfaceType, u64), Const(InterfaceType, u64), FoldSeq(u64), } diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 614b7b8281c..f3cedc53b04 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -177,7 +177,7 @@ fn instructions<'input, E: ParseError<&'input [u8]>>( } 0x0c => { - consume!((input, argument_0) = leb(input)?); + consume!((input, argument_0) = ty(input)?); consume!((input, argument_1) = leb(input)?); (input, Instruction::GetField(argument_0, argument_1)) } @@ -479,7 +479,7 @@ mod tests { 0x08, // TableRefGet 0x09, 0x01, // CallMethod(1) 0x0a, 0x7f, // MakeRecord(I32) - 0x0c, 0x01, 0x02, // GetField(1, 2) + 0x0c, 0xff, 0xff, 0x01, 0x02, // GetField(Int, 2) 0x0d, 0x7f, 0x01, // Const(I32, 1) 0x0e, 0x01, // FoldSeq(1) 0x0a, @@ -498,7 +498,7 @@ mod tests { Instruction::TableRefGet, Instruction::CallMethod(1), Instruction::MakeRecord(InterfaceType::I32), - Instruction::GetField(1, 2), + Instruction::GetField(InterfaceType::Int, 2), Instruction::Const(InterfaceType::I32, 1), Instruction::FoldSeq(1), ], diff --git a/lib/interface-types/src/decoders/mod.rs b/lib/interface-types/src/decoders/mod.rs new file mode 100644 index 00000000000..96eab66819c --- /dev/null +++ b/lib/interface-types/src/decoders/mod.rs @@ -0,0 +1 @@ +pub mod binary; From 40613d3d48b393eb3ad321f0deeabf1ea2c7c1cd Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 13 Sep 2019 15:40:23 +0200 Subject: [PATCH 10/78] feat(interface-types) Draft the WAT encoder. --- lib/interface-types/src/encoders/mod.rs | 1 + lib/interface-types/src/encoders/wat.rs | 165 ++++++++++++++++++++++++ lib/interface-types/src/lib.rs | 11 +- 3 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 lib/interface-types/src/encoders/mod.rs create mode 100644 lib/interface-types/src/encoders/wat.rs diff --git a/lib/interface-types/src/encoders/mod.rs b/lib/interface-types/src/encoders/mod.rs new file mode 100644 index 00000000000..070bf5893c0 --- /dev/null +++ b/lib/interface-types/src/encoders/mod.rs @@ -0,0 +1 @@ +pub mod wat; diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs new file mode 100644 index 00000000000..560489f40c6 --- /dev/null +++ b/lib/interface-types/src/encoders/wat.rs @@ -0,0 +1,165 @@ +use crate::ast::{Export, Instruction, InterfaceType}; + +impl From for String { + fn from(interface_type: InterfaceType) -> Self { + match interface_type { + InterfaceType::Int => "Int".into(), + InterfaceType::Float => "Float".into(), + InterfaceType::Any => "Any".into(), + InterfaceType::String => "String".into(), + InterfaceType::Seq => "Seq".into(), + InterfaceType::I32 => "i32".into(), + InterfaceType::I64 => "i64".into(), + InterfaceType::F32 => "f32".into(), + InterfaceType::F64 => "f64".into(), + InterfaceType::AnyRef => "anyref".into(), + } + } +} + +impl<'input> From> for String { + fn from(instruction: Instruction) -> Self { + match instruction { + Instruction::ArgumentGet(index) => format!("arg.get {}", index), + Instruction::Call(index) => format!("call {}", index), + Instruction::CallExport(export_name) => format!("call-export \"{}\"", export_name), + Instruction::ReadUtf8 => "read-utf8".into(), + Instruction::WriteUtf8(string) => format!("write-utf8 \"{}\"", string), + Instruction::AsWasm(interface_type) => { + format!("as-wasm {}", String::from(interface_type)) + } + Instruction::AsInterface(interface_type) => { + format!("as-interface {}", String::from(interface_type)) + } + Instruction::TableRefAdd => "table-ref-add".into(), + Instruction::TableRefGet => "table-ref-get".into(), + Instruction::CallMethod(index) => format!("call-method {}", index), + Instruction::MakeRecord(interface_type) => { + format!("make-record {}", String::from(interface_type)) + } + Instruction::GetField(interface_type, field_index) => { + format!("get-field {} {}", String::from(interface_type), field_index) + } + Instruction::Const(interface_type, value) => { + format!("const {} {}", String::from(interface_type), value) + } + Instruction::FoldSeq(import_index) => format!("fold-seq {}", import_index), + } + } +} + +impl<'input> From> for String { + fn from(export: Export) -> Self { + format!( + "(@interface export \"{}\"{}{})", + export.name, + if export.input_types.is_empty() { + "".into() + } else { + format!( + " (param{})", + export.input_types.iter().fold( + String::new(), + |mut accumulator, interface_type| { + accumulator.push(' '); + accumulator.push_str(&String::from(*interface_type)); + accumulator + } + ) + ) + }, + if export.output_types.is_empty() { + "".into() + } else { + format!( + " (result{})", + export.output_types.iter().fold( + String::new(), + |mut accumulator, interface_type| { + accumulator.push(' '); + accumulator.push_str(&String::from(*interface_type)); + accumulator + } + ) + ) + }, + ) + } +} + +#[cfg(test)] +mod tests { + use crate::ast::*; + + #[test] + fn test_interface_types() { + let inputs: Vec = vec![ + InterfaceType::Int.into(), + InterfaceType::Float.into(), + InterfaceType::Any.into(), + InterfaceType::String.into(), + InterfaceType::Seq.into(), + InterfaceType::I32.into(), + InterfaceType::I64.into(), + InterfaceType::F32.into(), + InterfaceType::F64.into(), + InterfaceType::AnyRef.into(), + ]; + let outputs = vec![ + "Int", "Float", "Any", "String", "Seq", "i32", "i64", "f32", "f64", "anyref", + ]; + + assert_eq!(inputs, outputs); + } + + #[test] + fn test_instructions() { + let inputs: Vec = vec![ + Instruction::ArgumentGet(7).into(), + Instruction::Call(7).into(), + Instruction::CallExport("foo").into(), + Instruction::ReadUtf8.into(), + Instruction::WriteUtf8("foo").into(), + Instruction::AsWasm(InterfaceType::Int).into(), + Instruction::AsInterface(InterfaceType::AnyRef).into(), + Instruction::TableRefAdd.into(), + Instruction::TableRefGet.into(), + Instruction::CallMethod(7).into(), + Instruction::MakeRecord(InterfaceType::Int).into(), + Instruction::GetField(InterfaceType::Int, 7).into(), + Instruction::Const(InterfaceType::I32, 7).into(), + Instruction::FoldSeq(7).into(), + ]; + let outputs = vec![ + "arg.get 7", + "call 7", + "call-export \"foo\"", + "read-utf8", + "write-utf8 \"foo\"", + "as-wasm Int", + "as-interface anyref", + "table-ref-add", + "table-ref-get", + "call-method 7", + "make-record Int", + "get-field Int 7", + "const i32 7", + "fold-seq 7", + ]; + + assert_eq!(inputs, outputs); + } + + #[test] + fn test_exports() { + let inputs: Vec = vec![Export { + name: "foo", + input_types: vec![InterfaceType::I32, InterfaceType::F32], + output_types: vec![InterfaceType::I32], + } + .into()]; + let outputs = vec!["(@interface export \"foo\" (param i32 f32) (result i32))"]; + + assert_eq!(inputs, outputs); + } +} diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index e2710761bab..f6c4656dbbd 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -1,13 +1,14 @@ pub mod ast; #[macro_use] mod macros; -pub mod parsers; +pub mod decoders; +pub mod encoders; -pub use parsers::parse; +pub use decoders::binary::parse as parse_binary; #[cfg(test)] mod tests { - use crate::{ast::*, parse}; + use crate::{ast::*, parse_binary}; use std::fs; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core as runtime; @@ -31,7 +32,7 @@ mod tests { } #[test] - fn test_parse() { + fn test_parse_binary_from_custom_section() { let module = get_module(); let custom_section_bytes = module .info() @@ -40,7 +41,7 @@ mod tests { .unwrap() .as_slice(); - match parse::<()>(custom_section_bytes) { + match parse_binary::<()>(custom_section_bytes) { Ok((remainder, interfaces)) => { assert!(remainder.is_empty()); assert_eq!( From 6279b3e9153d7f7f608de4313af4174e8bae213d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 18 Sep 2019 16:37:57 +0200 Subject: [PATCH 11/78] feat(interface-types) Continue the WAT encoder. --- lib/interface-types/src/ast.rs | 2 +- lib/interface-types/src/encoders/wat.rs | 401 +++++++++++++++++++----- 2 files changed, 328 insertions(+), 75 deletions(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index b966f010e4f..28c8682b113 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -1,6 +1,6 @@ use std::str; -#[derive(PartialEq, Clone, Copy, Debug)] +#[derive(PartialEq, Debug)] pub enum InterfaceType { Int, Float, diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index 560489f40c6..eafca5713fe 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -1,7 +1,7 @@ -use crate::ast::{Export, Instruction, InterfaceType}; +use crate::ast::{Adapter, Export, Forward, ImportedFunction, Instruction, InterfaceType, Type}; -impl From for String { - fn from(interface_type: InterfaceType) -> Self { +impl From<&InterfaceType> for String { + fn from(interface_type: &InterfaceType) -> Self { match interface_type { InterfaceType::Int => "Int".into(), InterfaceType::Float => "Float".into(), @@ -17,14 +17,14 @@ impl From for String { } } -impl<'input> From> for String { - fn from(instruction: Instruction) -> Self { +impl<'input> From<&Instruction<'input>> for String { + fn from(instruction: &Instruction) -> Self { match instruction { Instruction::ArgumentGet(index) => format!("arg.get {}", index), Instruction::Call(index) => format!("call {}", index), - Instruction::CallExport(export_name) => format!("call-export \"{}\"", export_name), + Instruction::CallExport(export_name) => format!(r#"call-export "{}""#, export_name), Instruction::ReadUtf8 => "read-utf8".into(), - Instruction::WriteUtf8(string) => format!("write-utf8 \"{}\"", string), + Instruction::WriteUtf8(string) => format!(r#"write-utf8 "{}""#, string), Instruction::AsWasm(interface_type) => { format!("as-wasm {}", String::from(interface_type)) } @@ -48,41 +48,124 @@ impl<'input> From> for String { } } -impl<'input> From> for String { - fn from(export: Export) -> Self { +fn input_types_to_param(input_types: &Vec) -> String { + if input_types.is_empty() { + "".into() + } else { format!( - "(@interface export \"{}\"{}{})", - export.name, - if export.input_types.is_empty() { - "".into() - } else { - format!( - " (param{})", - export.input_types.iter().fold( - String::new(), - |mut accumulator, interface_type| { - accumulator.push(' '); - accumulator.push_str(&String::from(*interface_type)); - accumulator - } - ) - ) - }, - if export.output_types.is_empty() { - "".into() - } else { - format!( - " (result{})", - export.output_types.iter().fold( - String::new(), - |mut accumulator, interface_type| { - accumulator.push(' '); - accumulator.push_str(&String::from(*interface_type)); - accumulator - } - ) - ) - }, + "\n (param{})", + input_types + .iter() + .fold(String::new(), |mut accumulator, interface_type| { + accumulator.push(' '); + accumulator.push_str(&String::from(interface_type)); + accumulator + }) + ) + } +} + +fn output_types_to_result(output_types: &Vec) -> String { + if output_types.is_empty() { + "".into() + } else { + format!( + "\n (result{})", + output_types + .iter() + .fold(String::new(), |mut accumulator, interface_type| { + accumulator.push(' '); + accumulator.push_str(&String::from(interface_type)); + accumulator + }) + ) + } +} + +impl<'input> From<&Export<'input>> for String { + fn from(export: &Export) -> Self { + format!( + r#"(@interface export "{name}"{inputs}{outputs})"#, + name = export.name, + inputs = input_types_to_param(&export.input_types), + outputs = output_types_to_result(&export.output_types), + ) + } +} + +impl<'input> From<&Type<'input>> for String { + fn from(_ty: &Type) -> Self { + unimplemented!() + } +} + +impl<'input> From<&ImportedFunction<'input>> for String { + fn from(imported_function: &ImportedFunction) -> Self { + format!( + r#"(@interface func ${namespace}_{name} (import "{namespace}" "{name}"){inputs}{outputs})"#, + namespace = imported_function.namespace, + name = imported_function.name, + inputs = input_types_to_param(&imported_function.input_types), + outputs = output_types_to_result(&imported_function.output_types), + ) + } +} + +impl<'input> From<&Adapter<'input>> for String { + fn from(adapter: &Adapter) -> Self { + match adapter { + Adapter::Import { + namespace, + name, + input_types, + output_types, + instructions, + } => format!( + r#"(@interface adapt (import "{namespace}" "{name}"){inputs}{outputs}{instructions})"#, + namespace = namespace, + name = name, + inputs = input_types_to_param(&input_types), + outputs = output_types_to_result(&output_types), + instructions = instructions.iter().fold( + String::new(), + |mut accumulator, instruction| { + accumulator.push_str("\n "); + accumulator.push_str(&String::from(instruction)); + accumulator + } + ), + ), + + Adapter::Export { + name, + input_types, + output_types, + instructions, + } => format!( + r#"(@interface adapt (export "{name}"){inputs}{outputs}{instructions})"#, + name = name, + inputs = input_types_to_param(&input_types), + outputs = output_types_to_result(&output_types), + instructions = instructions.iter().fold( + String::new(), + |mut accumulator, instruction| { + accumulator.push_str("\n "); + accumulator.push_str(&String::from(instruction)); + accumulator + } + ), + ), + + _ => unimplemented!(), + } + } +} + +impl<'input> From<&Forward<'input>> for String { + fn from(forward: &Forward) -> Self { + format!( + r#"(@interface forward (export "{name}"))"#, + name = forward.name, ) } } @@ -94,16 +177,16 @@ mod tests { #[test] fn test_interface_types() { let inputs: Vec = vec![ - InterfaceType::Int.into(), - InterfaceType::Float.into(), - InterfaceType::Any.into(), - InterfaceType::String.into(), - InterfaceType::Seq.into(), - InterfaceType::I32.into(), - InterfaceType::I64.into(), - InterfaceType::F32.into(), - InterfaceType::F64.into(), - InterfaceType::AnyRef.into(), + (&InterfaceType::Int).into(), + (&InterfaceType::Float).into(), + (&InterfaceType::Any).into(), + (&InterfaceType::String).into(), + (&InterfaceType::Seq).into(), + (&InterfaceType::I32).into(), + (&InterfaceType::I64).into(), + (&InterfaceType::F32).into(), + (&InterfaceType::F64).into(), + (&InterfaceType::AnyRef).into(), ]; let outputs = vec![ "Int", "Float", "Any", "String", "Seq", "i32", "i64", "f32", "f64", "anyref", @@ -115,27 +198,27 @@ mod tests { #[test] fn test_instructions() { let inputs: Vec = vec![ - Instruction::ArgumentGet(7).into(), - Instruction::Call(7).into(), - Instruction::CallExport("foo").into(), - Instruction::ReadUtf8.into(), - Instruction::WriteUtf8("foo").into(), - Instruction::AsWasm(InterfaceType::Int).into(), - Instruction::AsInterface(InterfaceType::AnyRef).into(), - Instruction::TableRefAdd.into(), - Instruction::TableRefGet.into(), - Instruction::CallMethod(7).into(), - Instruction::MakeRecord(InterfaceType::Int).into(), - Instruction::GetField(InterfaceType::Int, 7).into(), - Instruction::Const(InterfaceType::I32, 7).into(), - Instruction::FoldSeq(7).into(), + (&Instruction::ArgumentGet(7)).into(), + (&Instruction::Call(7)).into(), + (&Instruction::CallExport("foo")).into(), + (&Instruction::ReadUtf8).into(), + (&Instruction::WriteUtf8("foo")).into(), + (&Instruction::AsWasm(InterfaceType::Int)).into(), + (&Instruction::AsInterface(InterfaceType::AnyRef)).into(), + (&Instruction::TableRefAdd).into(), + (&Instruction::TableRefGet).into(), + (&Instruction::CallMethod(7)).into(), + (&Instruction::MakeRecord(InterfaceType::Int)).into(), + (&Instruction::GetField(InterfaceType::Int, 7)).into(), + (&Instruction::Const(InterfaceType::I32, 7)).into(), + (&Instruction::FoldSeq(7)).into(), ]; let outputs = vec![ "arg.get 7", "call 7", - "call-export \"foo\"", + r#"call-export "foo""#, "read-utf8", - "write-utf8 \"foo\"", + r#"write-utf8 "foo""#, "as-wasm Int", "as-interface anyref", "table-ref-add", @@ -152,14 +235,184 @@ mod tests { #[test] fn test_exports() { - let inputs: Vec = vec![Export { - name: "foo", - input_types: vec![InterfaceType::I32, InterfaceType::F32], - output_types: vec![InterfaceType::I32], - } - .into()]; - let outputs = vec!["(@interface export \"foo\" (param i32 f32) (result i32))"]; + let inputs: Vec = vec![ + (&Export { + name: "foo", + input_types: vec![InterfaceType::I32, InterfaceType::F32], + output_types: vec![InterfaceType::I32], + }) + .into(), + (&Export { + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + }) + .into(), + (&Export { + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::I32], + }) + .into(), + (&Export { + name: "foo", + input_types: vec![], + output_types: vec![], + }) + .into(), + ]; + let outputs = vec![ + r#"(@interface export "foo" + (param i32 f32) + (result i32))"#, + r#"(@interface export "foo" + (param i32))"#, + r#"(@interface export "foo" + (result i32))"#, + r#"(@interface export "foo")"#, + ]; + + assert_eq!(inputs, outputs); + } + + #[test] + fn test_imported_functions() { + let inputs: Vec = vec![ + (&ImportedFunction { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::Int, InterfaceType::String], + output_types: vec![InterfaceType::String], + }) + .into(), + (&ImportedFunction { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::String], + output_types: vec![], + }) + .into(), + (&ImportedFunction { + namespace: "ns", + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::String], + }) + .into(), + (&ImportedFunction { + namespace: "ns", + name: "foo", + input_types: vec![], + output_types: vec![], + }) + .into(), + ]; + let outputs = vec![ + r#"(@interface func $ns_foo (import "ns" "foo") + (param Int String) + (result String))"#, + r#"(@interface func $ns_foo (import "ns" "foo") + (param String))"#, + r#"(@interface func $ns_foo (import "ns" "foo") + (result String))"#, + r#"(@interface func $ns_foo (import "ns" "foo"))"#, + ]; + + assert_eq!(inputs, outputs); + } + + #[test] + fn test_adapters() { + let inputs: Vec = vec![ + (&Adapter::Import { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::I32, InterfaceType::F32], + output_types: vec![InterfaceType::I32], + instructions: vec![ + Instruction::ArgumentGet(0), + Instruction::WriteUtf8("hello"), + Instruction::CallExport("f"), + ], + }) + .into(), + (&Adapter::Import { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + instructions: vec![Instruction::CallExport("f")], + }) + .into(), + (&Adapter::Import { + namespace: "ns", + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::I32], + instructions: vec![Instruction::CallExport("f")], + }) + .into(), + (&Adapter::Export { + name: "foo", + input_types: vec![InterfaceType::I32, InterfaceType::F32], + output_types: vec![InterfaceType::I32], + instructions: vec![ + Instruction::ArgumentGet(0), + Instruction::WriteUtf8("hello"), + Instruction::CallExport("f"), + ], + }) + .into(), + (&Adapter::Export { + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + instructions: vec![Instruction::CallExport("f")], + }) + .into(), + (&Adapter::Export { + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::I32], + instructions: vec![Instruction::CallExport("f")], + }) + .into(), + ]; + let outputs = vec![ + r#"(@interface adapt (import "ns" "foo") + (param i32 f32) + (result i32) + arg.get 0 + write-utf8 "hello" + call-export "f")"#, + r#"(@interface adapt (import "ns" "foo") + (param i32) + call-export "f")"#, + r#"(@interface adapt (import "ns" "foo") + (result i32) + call-export "f")"#, + r#"(@interface adapt (export "foo") + (param i32 f32) + (result i32) + arg.get 0 + write-utf8 "hello" + call-export "f")"#, + r#"(@interface adapt (export "foo") + (param i32) + call-export "f")"#, + r#"(@interface adapt (export "foo") + (result i32) + call-export "f")"#, + ]; assert_eq!(inputs, outputs); } + + #[test] + fn test_forward() { + let input: String = (&Forward { name: "main" }).into(); + let output = r#"(@interface forward (export "main"))"#; + + assert_eq!(input, output); + } } From bd3a8884528f1c8978ff432e8b5bee71889aaeb1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 18 Sep 2019 17:09:18 +0200 Subject: [PATCH 12/78] feat(interface-types) Add new instructions. --- lib/interface-types/src/ast.rs | 6 +++ lib/interface-types/src/decoders/binary.rs | 44 +++++++++++++++++++++- lib/interface-types/src/encoders/wat.rs | 28 ++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 28c8682b113..63cb7789f54 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -37,6 +37,12 @@ pub enum Instruction<'input> { GetField(InterfaceType, u64), Const(InterfaceType, u64), FoldSeq(u64), + Add(InterfaceType), + MemToSeq(InterfaceType, &'input str), + Load(InterfaceType, &'input str), + SeqNew(InterfaceType), + ListPush, + RepeatWhile(u64, u64), } #[derive(PartialEq, Debug)] diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index f3cedc53b04..b86cdc303cf 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -193,6 +193,36 @@ fn instructions<'input, E: ParseError<&'input [u8]>>( (input, Instruction::FoldSeq(argument_0)) } + 0x0f => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::Add(argument_0)) + } + + 0x10 => { + consume!((input, argument_0) = ty(input)?); + consume!((input, argument_1) = string(input)?); + (input, Instruction::MemToSeq(argument_0, argument_1)) + } + + 0x11 => { + consume!((input, argument_0) = ty(input)?); + consume!((input, argument_1) = string(input)?); + (input, Instruction::Load(argument_0, argument_1)) + } + + 0x12 => { + consume!((input, argument_0) = ty(input)?); + (input, Instruction::SeqNew(argument_0)) + } + + 0x13 => (input, Instruction::ListPush), + + 0x14 => { + consume!((input, argument_0) = leb(input)?); + consume!((input, argument_1) = leb(input)?); + (input, Instruction::RepeatWhile(argument_0, argument_1)) + } + _ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))), }) } @@ -467,7 +497,7 @@ mod tests { #[test] fn test_instructions() { let input = &[ - 0x0e, // list of 14 items + 0x14, // list of 20 items 0x00, 0x01, // ArgumentGet(1) 0x01, 0x01, // Call(1) 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport("abc") @@ -482,6 +512,12 @@ mod tests { 0x0c, 0xff, 0xff, 0x01, 0x02, // GetField(Int, 2) 0x0d, 0x7f, 0x01, // Const(I32, 1) 0x0e, 0x01, // FoldSeq(1) + 0x0f, 0x7f, // Add(I32) + 0x10, 0x7f, 0x03, 0x61, 0x62, 0x63, // MemToSeq(I32, "abc") + 0x11, 0x7f, 0x03, 0x61, 0x62, 0x63, // Load(I32, "abc") + 0x12, 0x7f, // SeqNew(I32) + 0x13, // ListPush + 0x14, 0x01, 0x02, // RepeatWhile(1, 2) 0x0a, ]; let output = Ok(( @@ -501,6 +537,12 @@ mod tests { Instruction::GetField(InterfaceType::Int, 2), Instruction::Const(InterfaceType::I32, 1), Instruction::FoldSeq(1), + Instruction::Add(InterfaceType::I32), + Instruction::MemToSeq(InterfaceType::I32, "abc"), + Instruction::Load(InterfaceType::I32, "abc"), + Instruction::SeqNew(InterfaceType::I32), + Instruction::ListPush, + Instruction::RepeatWhile(1, 2), ], )); diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index eafca5713fe..41f73b473dc 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -44,6 +44,22 @@ impl<'input> From<&Instruction<'input>> for String { format!("const {} {}", String::from(interface_type), value) } Instruction::FoldSeq(import_index) => format!("fold-seq {}", import_index), + Instruction::Add(interface_type) => format!("add {}", String::from(interface_type)), + Instruction::MemToSeq(interface_type, memory) => format!( + r#"mem-to-seq {} "{}""#, + String::from(interface_type), + memory + ), + Instruction::Load(interface_type, memory) => { + format!(r#"load {} "{}""#, String::from(interface_type), memory) + } + Instruction::SeqNew(interface_type) => { + format!("seq.new {}", String::from(interface_type)) + } + Instruction::ListPush => "list.push".into(), + Instruction::RepeatWhile(condition_index, step_index) => { + format!("repeat-while {} {}", condition_index, step_index) + } } } } @@ -212,6 +228,12 @@ mod tests { (&Instruction::GetField(InterfaceType::Int, 7)).into(), (&Instruction::Const(InterfaceType::I32, 7)).into(), (&Instruction::FoldSeq(7)).into(), + (&Instruction::Add(InterfaceType::Int)).into(), + (&Instruction::MemToSeq(InterfaceType::Int, "foo")).into(), + (&Instruction::Load(InterfaceType::Int, "foo")).into(), + (&Instruction::SeqNew(InterfaceType::Int)).into(), + (&Instruction::ListPush).into(), + (&Instruction::RepeatWhile(1, 2)).into(), ]; let outputs = vec![ "arg.get 7", @@ -228,6 +250,12 @@ mod tests { "get-field Int 7", "const i32 7", "fold-seq 7", + "add Int", + r#"mem-to-seq Int "foo""#, + r#"load Int "foo""#, + "seq.new Int", + "list.push", + "repeat-while 1 2", ]; assert_eq!(inputs, outputs); From 480fe0fb9bae5546d4f1ca43937d56cef6e96c77 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 18 Sep 2019 17:14:12 +0200 Subject: [PATCH 13/78] chore(interface-types) Update to Wasmer 0.7.0. --- Cargo.lock | 102 ++++++++++++++++----------------- lib/interface-types/Cargo.toml | 4 +- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efd30b42c28..6fd1994d5f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -123,7 +123,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -161,8 +161,8 @@ name = "cargo_toml" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -180,7 +180,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -333,8 +333,8 @@ dependencies = [ "rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -398,7 +398,7 @@ dependencies = [ "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -452,7 +452,7 @@ dependencies = [ [[package]] name = "either" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -481,7 +481,7 @@ name = "erased-serde" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -552,7 +552,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -629,7 +629,7 @@ name = "indexmap" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -637,7 +637,7 @@ name = "inkwell" version = "0.1.0" source = "git+https://github.com/wasmerio/inkwell?branch=llvm8-0#8f480124663b812ee76cd4370f3ee170135b9d0e" dependencies = [ - "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "enum-methods 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "inkwell_internal_macros 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -682,7 +682,7 @@ name = "itertools" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -983,7 +983,7 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1069,7 +1069,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1200,10 +1200,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1212,7 +1212,7 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1220,12 +1220,12 @@ name = "serde_bytes" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1240,7 +1240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1270,16 +1270,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1360,7 +1360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1405,7 +1405,7 @@ name = "tinytemplate" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1414,7 +1414,7 @@ name = "toml" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1422,7 +1422,7 @@ name = "toml" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1438,7 +1438,7 @@ dependencies = [ "erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "typetag-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1497,8 +1497,8 @@ name = "wabt" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "wabt-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1536,8 +1536,8 @@ dependencies = [ "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-backend 0.7.0", @@ -1566,10 +1566,10 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-fork-frontend 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-fork-wasm 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1617,7 +1617,7 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.7.0", ] @@ -1641,8 +1641,8 @@ name = "wasmer-interface-types" version = "0.6.0" dependencies = [ "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.6.0", - "wasmer-runtime-core 0.6.0", + "wasmer-clif-backend 0.7.0", + "wasmer-runtime-core 0.7.0", ] [[package]] @@ -1739,10 +1739,10 @@ dependencies = [ "page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.35.3 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1785,8 +1785,8 @@ dependencies = [ "generational-arena 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.7.0", @@ -1939,7 +1939,7 @@ dependencies = [ "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dynasm 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f36d49ab6f8ecc642d2c6ee10fda04ba68003ef0277300866745cdde160e6b40" "checksum dynasmrt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c408a211e7f5762829f5e46bdff0c14bc3b1517a21a4bb781c716bf88b0c68" -"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum enum-methods 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7798e7da2d4cb0d6d6fc467e8d6b5bf247e9e989f786dde1732d79899c32bb10" "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3beee4bc16478a1b26f2e80ad819a52d24745e292f521a63c16eea5f74b7eb60" @@ -1969,12 +1969,8 @@ dependencies = [ "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -<<<<<<< HEAD "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -======= -"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lexical-core 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d11f3928ffd249baadf9f083cdea16d7cf317b2a8be6227e1169102432a36d2" ->>>>>>> feat(wasmer-interface-types) Draft. "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" "checksum llvm-sys 80.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2110cd4daf9cd8e39dd3b933b1a2a2ac7315e91f7c92b3a20beab526c63b5978" @@ -2006,7 +2002,7 @@ dependencies = [ "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" +"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" @@ -2033,18 +2029,18 @@ dependencies = [ "checksum scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)" = "f4473e8506b213730ff2061073b48fa51dcc66349219e2e7c5608f0296a1d95a" +"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" "checksum serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d733da87e79faaac25616e33d26299a41143fd4cd42746cbb0e91d8feea243fd" "checksum serde_bytes 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "45af0182ff64abaeea290235eb67da3825a576c5d53e642c4d5b652e12e6effc" -"checksum serde_derive 1.0.100 (registry+https://github.com/rust-lang/crates.io-index)" = "11e410fde43e157d789fc290d26bc940778ad0fdd47836426fbac36573710dbb" +"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" "checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum structopt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ac9d6e93dd792b217bf89cda5c14566e3043960c6f9da890c2ba5d09d07804c" -"checksum structopt-derive 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ae9e5165d463a0dea76967d021f8d0f9316057bf5163aa2a4843790e842ff37" +"checksum structopt 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe8d3289b63ef2f196d89e7701f986583c0895e764b78f052a55b9b5d34d84a" +"checksum structopt-derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f3add731f5b4fb85931d362a3c92deb1ad7113649a8d51701fb257673705f122" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml index 0c613198eae..5ea1db48b34 100644 --- a/lib/interface-types/Cargo.toml +++ b/lib/interface-types/Cargo.toml @@ -11,5 +11,5 @@ edition = "2018" nom = "5.0" [dev-dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0", features = ["backend-cranelift"] } -wasmer-clif-backend = { path = "../clif-backend", version = "0.6.0" } \ No newline at end of file +wasmer-runtime-core = { path = "../runtime-core", version = "0.7.0", features = ["backend-cranelift"] } +wasmer-clif-backend = { path = "../clif-backend", version = "0.7.0" } \ No newline at end of file From 6ec35c8bdc8d82b7dc4b4a59edda4b4e2986f603 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 18 Sep 2019 17:14:57 +0200 Subject: [PATCH 14/78] chore(interface-types) Bump to 0.7.0. --- Cargo.lock | 2 +- lib/interface-types/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fd1994d5f1..46019511e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1638,7 +1638,7 @@ dependencies = [ [[package]] name = "wasmer-interface-types" -version = "0.6.0" +version = "0.7.0" dependencies = [ "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-backend 0.7.0", diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml index 5ea1db48b34..19ddeb22b86 100644 --- a/lib/interface-types/Cargo.toml +++ b/lib/interface-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmer-interface-types" -version = "0.6.0" +version = "0.7.0" description = "WebAssembly Interface Types library for Wasmer" license = "MIT" authors = ["The Wasmer Engineering Team "] From fc9389d9321317cfe74b6e5ccbdd1709e291134f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 18 Sep 2019 18:02:05 +0200 Subject: [PATCH 15/78] feat(interface-types) Encode `Interfaces` to WAT. --- lib/interface-types/src/encoders/wat.rs | 160 +++++++++++++++++++++++- lib/interface-types/src/lib.rs | 46 ++++++- 2 files changed, 204 insertions(+), 2 deletions(-) diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index 41f73b473dc..e7e02a3fea6 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -1,4 +1,6 @@ -use crate::ast::{Adapter, Export, Forward, ImportedFunction, Instruction, InterfaceType, Type}; +use crate::ast::{ + Adapter, Export, Forward, ImportedFunction, Instruction, InterfaceType, Interfaces, Type, +}; impl From<&InterfaceType> for String { fn from(interface_type: &InterfaceType) -> Self { @@ -186,6 +188,83 @@ impl<'input> From<&Forward<'input>> for String { } } +impl<'input> From<&Interfaces<'input>> for String { + fn from(interfaces: &Interfaces) -> Self { + let mut output = String::from(";; Interfaces"); + + let exports = interfaces + .exports + .iter() + .fold(String::new(), |mut accumulator, export| { + accumulator.push_str(&format!("\n\n;; Interface, Export {}\n", export.name)); + accumulator.push_str(&String::from(export)); + accumulator + }); + + let types = interfaces + .types + .iter() + .fold(String::new(), |mut accumulator, ty| { + accumulator.push_str(&format!("\n\n;; Interface, Ty {}\n", ty.name)); + accumulator.push_str(&String::from(ty)); + accumulator + }); + + let imported_functions = interfaces.imported_functions.iter().fold( + String::new(), + |mut accumulator, imported_function| { + accumulator.push_str(&format!( + "\n\n;; Interface, Imported function {}.{}\n", + imported_function.namespace, imported_function.name + )); + accumulator.push_str(&String::from(imported_function)); + accumulator + }, + ); + + let adapters = + interfaces + .adapters + .iter() + .fold(String::new(), |mut accumulator, adapter| { + match adapter { + Adapter::Import { + namespace, name, .. + } => accumulator.push_str(&format!( + "\n\n;; Interface, Adapter {}.{}\n", + namespace, name + )), + + Adapter::Export { name, .. } => { + accumulator.push_str(&format!("\n\n;; Interface, Adapter {}\n", name)) + } + + _ => unimplemented!(), + } + accumulator.push_str(&String::from(adapter)); + accumulator + }); + + let forwards = + interfaces + .forwards + .iter() + .fold(String::new(), |mut accumulator, forward| { + accumulator.push_str(&format!("\n\n;; Interface, Forward {}\n", forward.name)); + accumulator.push_str(&String::from(forward)); + accumulator + }); + + output.push_str(&exports); + output.push_str(&types); + output.push_str(&imported_functions); + output.push_str(&adapters); + output.push_str(&forwards); + + output + } +} + #[cfg(test)] mod tests { use crate::ast::*; @@ -443,4 +522,83 @@ mod tests { assert_eq!(input, output); } + + #[test] + fn test_interfaces() { + let input: String = (&Interfaces { + exports: vec![ + Export { + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + }, + Export { + name: "bar", + input_types: vec![], + output_types: vec![], + }, + ], + types: vec![], + imported_functions: vec![ + ImportedFunction { + namespace: "ns", + name: "foo", + input_types: vec![], + output_types: vec![InterfaceType::I32], + }, + ImportedFunction { + namespace: "ns", + name: "bar", + input_types: vec![], + output_types: vec![], + }, + ], + adapters: vec![ + Adapter::Import { + namespace: "ns", + name: "foo", + input_types: vec![InterfaceType::I32], + output_types: vec![], + instructions: vec![Instruction::ArgumentGet(42)], + }, + Adapter::Export { + name: "bar", + input_types: vec![], + output_types: vec![], + instructions: vec![Instruction::ArgumentGet(42)], + }, + ], + forwards: vec![Forward { name: "main" }], + }) + .into(); + let output = r#";; Interfaces + +;; Interface, Export foo +(@interface export "foo" + (param i32)) + +;; Interface, Export bar +(@interface export "bar") + +;; Interface, Imported function ns.foo +(@interface func $ns_foo (import "ns" "foo") + (result i32)) + +;; Interface, Imported function ns.bar +(@interface func $ns_bar (import "ns" "bar")) + +;; Interface, Adapter ns.foo +(@interface adapt (import "ns" "foo") + (param i32) + arg.get 42) + +;; Interface, Adapter bar +(@interface adapt (export "bar") + arg.get 42) + +;; Interface, Forward main +(@interface forward (export "main"))"#; + + assert_eq!(input, output); + } } diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index f6c4656dbbd..d9fc90ea623 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -8,7 +8,7 @@ pub use decoders::binary::parse as parse_binary; #[cfg(test)] mod tests { - use crate::{ast::*, parse_binary}; + use crate::{ast::*, encoders::wat::*, parse_binary}; use std::fs; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core as runtime; @@ -103,6 +103,50 @@ mod tests { forwards: vec![Forward { name: "main" }] } ); + + let wat = String::from(&interfaces); + + assert_eq!( + wat, + r#";; Interfaces + +;; Interface, Export strlen +(@interface export "strlen" + (param i32) + (result i32)) + +;; Interface, Export write_null_byte +(@interface export "write_null_byte" + (param i32 i32) + (result i32)) + +;; Interface, Imported function host.console_log +(@interface func $host_console_log (import "host" "console_log") + (param String)) + +;; Interface, Imported function host.document_title +(@interface func $host_document_title (import "host" "document_title") + (result String)) + +;; Interface, Adapter host.console_log +(@interface adapt (import "host" "console_log") + (param i32) + arg.get 0 + arg.get 0 + call-export "strlen" + read-utf8 + call 0) + +;; Interface, Adapter host.document_title +(@interface adapt (import "host" "document_title") + (result i32) + call 1 + write-utf8 "alloc" + call-export "write_null_byte") + +;; Interface, Forward main +(@interface forward (export "main"))"#, + ); } Err(_) => assert!(false), From dc3c72ea1954ef342cd31a9d9231a0d940ae631b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 19 Sep 2019 00:18:36 +0200 Subject: [PATCH 16/78] feat(interface-types) Draft instruction interpreter. --- lib/interface-types/src/ast.rs | 25 +----- lib/interface-types/src/decoders/binary.rs | 2 +- lib/interface-types/src/encoders/wat.rs | 7 +- .../src/instructions/interpreter.rs | 83 +++++++++++++++++++ lib/interface-types/src/instructions/mod.rs | 28 +++++++ lib/interface-types/src/instructions/stack.rs | 66 +++++++++++++++ lib/interface-types/src/lib.rs | 3 +- 7 files changed, 185 insertions(+), 29 deletions(-) create mode 100644 lib/interface-types/src/instructions/interpreter.rs create mode 100644 lib/interface-types/src/instructions/mod.rs create mode 100644 lib/interface-types/src/instructions/stack.rs diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 63cb7789f54..a3df0d8d8b7 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -1,3 +1,4 @@ +use crate::instructions::Instruction; use std::str; #[derive(PartialEq, Debug)] @@ -21,30 +22,6 @@ pub(crate) enum AdapterKind { HelperFunction, } -#[derive(PartialEq, Debug)] -pub enum Instruction<'input> { - ArgumentGet(u64), - Call(u64), - CallExport(&'input str), - ReadUtf8, - WriteUtf8(&'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), -} - #[derive(PartialEq, Debug)] pub struct Export<'input> { pub name: &'input str, diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index b86cdc303cf..2fd6a7b4bbc 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -1,4 +1,4 @@ -use crate::ast::*; +use crate::{ast::*, instructions::Instruction}; use nom::{ error::{make_error, ErrorKind, ParseError}, Err, IResult, diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index e7e02a3fea6..b11b65dc2a0 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -1,5 +1,6 @@ -use crate::ast::{ - Adapter, Export, Forward, ImportedFunction, Instruction, InterfaceType, Interfaces, Type, +use crate::{ + ast::{Adapter, Export, Forward, ImportedFunction, InterfaceType, Interfaces, Type}, + instructions::Instruction, }; impl From<&InterfaceType> for String { @@ -267,7 +268,7 @@ impl<'input> From<&Interfaces<'input>> for String { #[cfg(test)] mod tests { - use crate::ast::*; + use crate::{ast::*, instructions::Instruction}; #[test] fn test_interface_types() { diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs new file mode 100644 index 00000000000..61c1133433d --- /dev/null +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -0,0 +1,83 @@ +use crate::instructions::{stack::Stack, Instruction}; +use std::convert::TryFrom; + +type ExecutableInstruction = dyn Fn(&mut Stack); +//trait ExecutableInstruction: Fn(&mut Stack) {} + +pub(crate) struct Interpreter { + stack: Stack, + instructions: Vec>, +} + +impl Interpreter { + pub(crate) fn is_eos(&self) -> bool { + self.stack.is_empty() + } +} + +impl<'binary_input> TryFrom<&Vec>> for Interpreter { + type Error = &'static str; + + fn try_from(instructions: &Vec) -> Result { + let executable_instructions = instructions + .iter() + .map(|instruction| -> Box { + match instruction { + Instruction::ArgumentGet(index) => { + let index = index.to_owned(); + + Box::new(move |stack: &mut Stack| { + println!("argument get {}", index); + }) + } + Instruction::CallExport(export_name) => { + let export_name = (*export_name).to_owned(); + + Box::new(move |stack: &mut Stack| { + println!("call export {}", export_name); + }) + } + Instruction::ReadUtf8 => Box::new(|stack: &mut Stack| { + println!("read utf8"); + }), + Instruction::Call(index) => { + let index = index.to_owned(); + + Box::new(move |stack: &mut Stack| { + println!("call {}", index); + }) + } + _ => unimplemented!(), + } + }) + .collect::>(); + + Ok(Interpreter { + stack: Stack::new(), + instructions: executable_instructions, + }) + } +} + +#[cfg(test)] +mod tests { + use super::Interpreter; + use crate::instructions::Instruction; + use std::convert::TryInto; + + #[test] + fn test_interpreter_from_instructions() { + let instructions = vec![ + Instruction::ArgumentGet(0), + Instruction::ArgumentGet(0), + Instruction::CallExport("strlen"), + Instruction::ReadUtf8, + Instruction::Call(7), + ]; + let interpreter: Result = (&instructions).try_into(); + assert!(interpreter.is_ok()); + + let interpreter = interpreter.unwrap(); + assert_eq!(interpreter.is_eos(), true); + } +} diff --git a/lib/interface-types/src/instructions/mod.rs b/lib/interface-types/src/instructions/mod.rs new file mode 100644 index 00000000000..eb62f43d206 --- /dev/null +++ b/lib/interface-types/src/instructions/mod.rs @@ -0,0 +1,28 @@ +use crate::ast::InterfaceType; + +pub mod interpreter; +mod stack; + +#[derive(PartialEq, Debug)] +pub enum Instruction<'input> { + ArgumentGet(u64), + Call(u64), + CallExport(&'input str), + ReadUtf8, + WriteUtf8(&'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), +} diff --git a/lib/interface-types/src/instructions/stack.rs b/lib/interface-types/src/instructions/stack.rs new file mode 100644 index 00000000000..8a5bce47183 --- /dev/null +++ b/lib/interface-types/src/instructions/stack.rs @@ -0,0 +1,66 @@ +use std::{iter::Rev, vec::Drain}; + +pub(super) struct Stack { + inner: Vec, +} + +impl Stack { + pub(super) fn new() -> Self { + Self { inner: Vec::new() } + } + + pub(super) fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub(super) fn push(&mut self, item: u32) { + self.inner.push(item); + } + + pub(super) fn pop(&mut self) -> Option { + self.inner.pop() + } + + pub(super) fn pop_n(&mut self, n: usize) -> Rev> { + self.inner.drain(self.inner.len() - n..).rev() + } +} + +#[cfg(test)] +mod tests { + use super::Stack; + + #[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_pop() { + let mut stack = Stack::new(); + stack.push(1); + + assert_eq!(stack.pop(), Some(1)); + assert_eq!(stack.is_empty(), true); + } + + #[test] + fn test_pop_n() { + 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_n(1).collect::>(), &[6]); + assert_eq!(stack.pop_n(2).collect::>(), &[5, 4]); + assert_eq!(stack.pop_n(3).collect::>(), &[3, 2, 1]); + assert_eq!(stack.is_empty(), true); + } +} diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index d9fc90ea623..9ba79d7b9f4 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -3,12 +3,13 @@ pub mod ast; mod macros; pub mod decoders; pub mod encoders; +pub mod instructions; pub use decoders::binary::parse as parse_binary; #[cfg(test)] mod tests { - use crate::{ast::*, encoders::wat::*, parse_binary}; + use crate::{ast::*, encoders::wat::*, instructions::Instruction, parse_binary}; use std::fs; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core as runtime; From 2f3c37fbd5ce093fdd46497c976e3030d5277bcc Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 19 Sep 2019 00:25:28 +0200 Subject: [PATCH 17/78] feat(interface-types) Continue. --- lib/interface-types/src/instructions/interpreter.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 61c1133433d..ee1b8c48cf9 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -1,12 +1,11 @@ use crate::instructions::{stack::Stack, Instruction}; use std::convert::TryFrom; -type ExecutableInstruction = dyn Fn(&mut Stack); -//trait ExecutableInstruction: Fn(&mut Stack) {} +type ExecutableInstruction = Box; pub(crate) struct Interpreter { stack: Stack, - instructions: Vec>, + instructions: Vec, } impl Interpreter { @@ -21,7 +20,7 @@ impl<'binary_input> TryFrom<&Vec>> for Interpreter { fn try_from(instructions: &Vec) -> Result { let executable_instructions = instructions .iter() - .map(|instruction| -> Box { + .map(|instruction| -> ExecutableInstruction { match instruction { Instruction::ArgumentGet(index) => { let index = index.to_owned(); @@ -50,7 +49,7 @@ impl<'binary_input> TryFrom<&Vec>> for Interpreter { _ => unimplemented!(), } }) - .collect::>(); + .collect(); Ok(Interpreter { stack: Stack::new(), From c63345029ef17f3f41b698873f2385800fb675d2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 19 Sep 2019 23:05:17 +0200 Subject: [PATCH 18/78] feat(interface-types) Continue. --- .../src/instructions/interpreter.rs | 67 ++++++++++++++----- lib/interface-types/src/instructions/stack.rs | 62 ++++++++++++----- 2 files changed, 95 insertions(+), 34 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index ee1b8c48cf9..11d6ddae645 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -1,16 +1,31 @@ -use crate::instructions::{stack::Stack, Instruction}; +use crate::instructions::{ + stack::{Stack, Stackable}, + Instruction, +}; use std::convert::TryFrom; -type ExecutableInstruction = Box; +type ExecutableInstruction = Box) -> Result<(), &'static str>>; pub(crate) struct Interpreter { - stack: Stack, - instructions: Vec, + executable_instructions: Vec, } impl Interpreter { - pub(crate) fn is_eos(&self) -> bool { - self.stack.is_empty() + fn iter(&self) -> impl Iterator + '_ { + self.executable_instructions.iter() + } + + pub(crate) fn run(&self) -> Result, &'static str> { + let mut stack = Stack::new(); + + for executable_instruction in self.iter() { + match executable_instruction(&mut stack) { + Ok(_) => continue, + Err(message) => return Err(message), + } + } + + Ok(stack) } } @@ -25,25 +40,34 @@ impl<'binary_input> TryFrom<&Vec>> for Interpreter { Instruction::ArgumentGet(index) => { let index = index.to_owned(); - Box::new(move |stack: &mut Stack| { + Box::new(move |stack: &mut Stack| -> Result<(), _> { println!("argument get {}", index); + stack.push(index); + + Ok(()) }) } Instruction::CallExport(export_name) => { let export_name = (*export_name).to_owned(); - Box::new(move |stack: &mut Stack| { + Box::new(move |_stack: &mut Stack| -> Result<(), _> { println!("call export {}", export_name); + + Ok(()) }) } - Instruction::ReadUtf8 => Box::new(|stack: &mut Stack| { + Instruction::ReadUtf8 => Box::new(|_stack: &mut Stack| -> Result<(), _> { println!("read utf8"); + + Ok(()) }), Instruction::Call(index) => { let index = index.to_owned(); - Box::new(move |stack: &mut Stack| { + Box::new(move |_stack: &mut Stack| -> Result<(), _> { println!("call {}", index); + + Ok(()) }) } _ => unimplemented!(), @@ -52,8 +76,7 @@ impl<'binary_input> TryFrom<&Vec>> for Interpreter { .collect(); Ok(Interpreter { - stack: Stack::new(), - instructions: executable_instructions, + executable_instructions, }) } } @@ -61,7 +84,7 @@ impl<'binary_input> TryFrom<&Vec>> for Interpreter { #[cfg(test)] mod tests { use super::Interpreter; - use crate::instructions::Instruction; + use crate::instructions::{stack::Stackable, Instruction}; use std::convert::TryInto; #[test] @@ -73,10 +96,20 @@ mod tests { Instruction::ReadUtf8, Instruction::Call(7), ]; - let interpreter: Result = (&instructions).try_into(); - assert!(interpreter.is_ok()); + let interpreter: Interpreter = (&instructions).try_into().unwrap(); + + assert_eq!(interpreter.executable_instructions.len(), 5); + } + + #[test] + fn test_interpreter_argument_get() { + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(42)]).try_into().unwrap(); + let run = interpreter.run(); + + assert!(run.is_ok()); + + let stack = run.unwrap(); - let interpreter = interpreter.unwrap(); - assert_eq!(interpreter.is_eos(), true); + assert_eq!(stack.as_slice(), &[42]); } } diff --git a/lib/interface-types/src/instructions/stack.rs b/lib/interface-types/src/instructions/stack.rs index 8a5bce47183..ff68d938007 100644 --- a/lib/interface-types/src/instructions/stack.rs +++ b/lib/interface-types/src/instructions/stack.rs @@ -1,34 +1,62 @@ -use std::{iter::Rev, vec::Drain}; +pub(crate) trait Stackable { + type Item; -pub(super) struct Stack { - inner: Vec, + fn is_empty(&self) -> bool; + fn as_slice(&self) -> &[Self::Item]; + fn push(&mut self, item: Self::Item); + fn pop1(&mut self) -> Option; + fn pop(&mut self, n: usize) -> Option>; } -impl Stack { - pub(super) fn new() -> Self { +pub(crate) struct Stack { + inner: Vec, +} + +impl Stack { + pub fn new() -> Self { Self { inner: Vec::new() } } +} + +impl Stackable for Stack { + type Item = T; - pub(super) fn is_empty(&self) -> bool { + fn is_empty(&self) -> bool { self.inner.is_empty() } - pub(super) fn push(&mut self, item: u32) { + fn as_slice(&self) -> &[Self::Item] { + self.inner.as_slice() + } + + fn push(&mut self, item: Self::Item) { self.inner.push(item); } - pub(super) fn pop(&mut self) -> Option { + fn pop1(&mut self) -> Option { self.inner.pop() } - pub(super) fn pop_n(&mut self, n: usize) -> Rev> { - self.inner.drain(self.inner.len() - n..).rev() + fn pop(&mut self, n: usize) -> Option> { + if self.inner.len() < n { + None + } else { + let items = self + .inner + .drain(self.inner.len() - n..) + .rev() + .collect::>(); + + assert!(items.len() == n); + + Some(items) + } } } #[cfg(test)] mod tests { - use super::Stack; + use super::{Stack, Stackable}; #[test] fn test_is_empty() { @@ -40,16 +68,16 @@ mod tests { } #[test] - fn test_push_pop() { + fn test_push_pop1() { let mut stack = Stack::new(); stack.push(1); - assert_eq!(stack.pop(), Some(1)); + assert_eq!(stack.pop1(), Some(1)); assert_eq!(stack.is_empty(), true); } #[test] - fn test_pop_n() { + fn test_pop() { let mut stack = Stack::new(); stack.push(1); stack.push(2); @@ -58,9 +86,9 @@ mod tests { stack.push(5); stack.push(6); - assert_eq!(stack.pop_n(1).collect::>(), &[6]); - assert_eq!(stack.pop_n(2).collect::>(), &[5, 4]); - assert_eq!(stack.pop_n(3).collect::>(), &[3, 2, 1]); + 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); } } From 62e1f7867ba1329350ed6652bf2c09c9c65b2642 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 00:06:15 +0200 Subject: [PATCH 19/78] feat(interface-types) Add an abstract Wasm instance and a runtime to the interpreter. --- .../src/instructions/interpreter.rs | 227 +++++++++++++----- lib/interface-types/src/instructions/mod.rs | 1 + lib/interface-types/src/instructions/stack.rs | 1 + 3 files changed, 174 insertions(+), 55 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 11d6ddae645..2859f806928 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -1,78 +1,117 @@ use crate::instructions::{ stack::{Stack, Stackable}, - Instruction, + wasm, Instruction, }; use std::convert::TryFrom; -type ExecutableInstruction = Box) -> Result<(), &'static str>>; +struct Runtime<'invocation, 'instance, Instance> +where + Instance: wasm::Instance, +{ + invocation_inputs: &'invocation Vec, + stack: Stack, + wasm_instance: &'instance Instance, +} -pub(crate) struct Interpreter { - executable_instructions: Vec, +pub(crate) struct Interpreter +where + Instance: wasm::Instance, +{ + executable_instructions: Vec) -> Result<(), String>>>, } -impl Interpreter { - fn iter(&self) -> impl Iterator + '_ { +impl Interpreter +where + Instance: wasm::Instance, +{ + fn iter( + &self, + ) -> impl Iterator) -> Result<(), String>>> + '_ { self.executable_instructions.iter() } - pub(crate) fn run(&self) -> Result, &'static str> { - let mut stack = Stack::new(); + pub(crate) fn run( + &self, + invocation_inputs: &Vec, + wasm_instance: &Instance, + ) -> Result, String> { + let mut runtime = Runtime { + invocation_inputs, + stack: Stack::new(), + wasm_instance, + }; for executable_instruction in self.iter() { - match executable_instruction(&mut stack) { + match executable_instruction(&mut runtime) { Ok(_) => continue, Err(message) => return Err(message), } } - Ok(stack) + Ok(runtime.stack) } } -impl<'binary_input> TryFrom<&Vec>> for Interpreter { - type Error = &'static str; +impl<'binary_input, Instance> TryFrom<&Vec>> for Interpreter +where + Instance: wasm::Instance, +{ + type Error = String; fn try_from(instructions: &Vec) -> Result { let executable_instructions = instructions .iter() - .map(|instruction| -> ExecutableInstruction { - match instruction { - Instruction::ArgumentGet(index) => { - let index = index.to_owned(); - - Box::new(move |stack: &mut Stack| -> Result<(), _> { - println!("argument get {}", index); - stack.push(index); - - Ok(()) - }) - } - Instruction::CallExport(export_name) => { - let export_name = (*export_name).to_owned(); - - Box::new(move |_stack: &mut Stack| -> Result<(), _> { - println!("call export {}", export_name); - - Ok(()) - }) + .map( + |instruction| -> Box) -> Result<(), String>> { + match instruction { + Instruction::ArgumentGet(index) => { + let index = index.to_owned(); + let instruction_name: String = instruction.into(); + + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + let invocation_inputs = runtime.invocation_inputs; + + if index >= (invocation_inputs.len() as u64) { + return Err(format!( + "`{}` cannot access argument #{} because it does't exist.", + instruction_name, index + )); + } + + runtime.stack.push(invocation_inputs[index as usize]); + + Ok(()) + }) + } + Instruction::CallExport(export_name) => { + let export_name = (*export_name).to_owned(); + + Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { + println!("call export {}", export_name); + + Ok(()) + }) + } + Instruction::ReadUtf8 => { + Box::new(|_runtime: &mut Runtime| -> Result<(), _> { + println!("read utf8"); + + Ok(()) + }) + } + Instruction::Call(index) => { + let index = index.to_owned(); + + Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { + println!("call {}", index); + + Ok(()) + }) + } + _ => unimplemented!(), } - Instruction::ReadUtf8 => Box::new(|_stack: &mut Stack| -> Result<(), _> { - println!("read utf8"); - - Ok(()) - }), - Instruction::Call(index) => { - let index = index.to_owned(); - - Box::new(move |_stack: &mut Stack| -> Result<(), _> { - println!("call {}", index); - - Ok(()) - }) - } - _ => unimplemented!(), - } - }) + }, + ) .collect(); Ok(Interpreter { @@ -84,27 +123,53 @@ impl<'binary_input> TryFrom<&Vec>> for Interpreter { #[cfg(test)] mod tests { use super::Interpreter; - use crate::instructions::{stack::Stackable, Instruction}; - use std::convert::TryInto; + use crate::instructions::{stack::Stackable, wasm, Instruction}; + use std::{collections::HashMap, convert::TryInto}; + + struct Instance { + exports: HashMap, + } + + impl Instance { + fn new() -> Self { + Self { + exports: { + let mut hashmap = HashMap::new(); + hashmap.insert("foo".into(), ()); + + hashmap + }, + } + } + } + + impl wasm::Instance for Instance { + fn export_exists(&self, export_name: &str) -> bool { + self.exports.contains_key(export_name) + } + } #[test] fn test_interpreter_from_instructions() { let instructions = vec![ Instruction::ArgumentGet(0), Instruction::ArgumentGet(0), - Instruction::CallExport("strlen"), + Instruction::CallExport("foo"), Instruction::ReadUtf8, Instruction::Call(7), ]; - let interpreter: Interpreter = (&instructions).try_into().unwrap(); + let interpreter: Interpreter<()> = (&instructions).try_into().unwrap(); assert_eq!(interpreter.executable_instructions.len(), 5); } #[test] fn test_interpreter_argument_get() { - let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(42)]).try_into().unwrap(); - let run = interpreter.run(); + let interpreter: Interpreter = + (&vec![Instruction::ArgumentGet(0)]).try_into().unwrap(); + let invocation_inputs = vec![42]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); assert!(run.is_ok()); @@ -112,4 +177,56 @@ mod tests { assert_eq!(stack.as_slice(), &[42]); } + + #[test] + fn test_interpreter_argument_get_invalid_index() { + let interpreter: Interpreter = + (&vec![Instruction::ArgumentGet(1)]).try_into().unwrap(); + let invocation_inputs = vec![42]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from("`arg.get 1` cannot access argument #1 because it does't exist.") + ); + } + + #[test] + fn test_interpreter_argument_get_argument_get() { + let interpreter: Interpreter = + (&vec![Instruction::ArgumentGet(0), Instruction::ArgumentGet(1)]) + .try_into() + .unwrap(); + let invocation_inputs = vec![7, 42]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_ok()); + + let stack = run.unwrap(); + + assert_eq!(stack.as_slice(), &[7, 42]); + } + + /* + #[test] + fn test_interpreter_call_export() { + let interpreter: Interpreter = + (&vec![Instruction::ArgumentGet(7), Instruction::ArgumentGet(42)]) + .try_into() + .unwrap(); + let run = interpreter.run(&Instance::new()); + + assert!(run.is_ok()); + + let stack = run.unwrap(); + + assert_eq!(stack.as_slice(), &[]); + } + */ } diff --git a/lib/interface-types/src/instructions/mod.rs b/lib/interface-types/src/instructions/mod.rs index eb62f43d206..43e536c4b05 100644 --- a/lib/interface-types/src/instructions/mod.rs +++ b/lib/interface-types/src/instructions/mod.rs @@ -2,6 +2,7 @@ use crate::ast::InterfaceType; pub mod interpreter; mod stack; +pub mod wasm; #[derive(PartialEq, Debug)] pub enum Instruction<'input> { diff --git a/lib/interface-types/src/instructions/stack.rs b/lib/interface-types/src/instructions/stack.rs index ff68d938007..c9b42d6adf0 100644 --- a/lib/interface-types/src/instructions/stack.rs +++ b/lib/interface-types/src/instructions/stack.rs @@ -8,6 +8,7 @@ pub(crate) trait Stackable { fn pop(&mut self, n: usize) -> Option>; } +#[derive(Debug)] pub(crate) struct Stack { inner: Vec, } From 9d4c983206a70f9e3a2d619403095b2c6f596fd0 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 11:37:38 +0200 Subject: [PATCH 20/78] feat(interface-types) Implement `CallExport` executable instruction. It requires to create the `wasm::Export` trait, plus the `wasm::Type` and the `wasm::Value` enums. --- .../src/instructions/interpreter.rs | 191 ++++++++++++++---- lib/interface-types/src/instructions/stack.rs | 4 +- lib/interface-types/src/instructions/wasm.rs | 91 +++++++++ 3 files changed, 245 insertions(+), 41 deletions(-) create mode 100644 lib/interface-types/src/instructions/wasm.rs diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 2859f806928..7da3800f4a1 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -2,35 +2,43 @@ use crate::instructions::{ stack::{Stack, Stackable}, wasm, Instruction, }; -use std::convert::TryFrom; +use std::{ + convert::{TryFrom, TryInto}, + marker::PhantomData, +}; -struct Runtime<'invocation, 'instance, Instance> +struct Runtime<'invocation, 'instance, Instance, Export> where - Instance: wasm::Instance, + Export: wasm::Export + 'instance, + Instance: wasm::Instance + 'instance, { invocation_inputs: &'invocation Vec, stack: Stack, wasm_instance: &'instance Instance, + wasm_exports: PhantomData, } -pub(crate) struct Interpreter +pub struct Interpreter where - Instance: wasm::Instance, + Export: wasm::Export, + Instance: wasm::Instance, { - executable_instructions: Vec) -> Result<(), String>>>, + executable_instructions: Vec) -> Result<(), String>>>, } -impl Interpreter +impl Interpreter where - Instance: wasm::Instance, + Export: wasm::Export, + Instance: wasm::Instance, { fn iter( &self, - ) -> impl Iterator) -> Result<(), String>>> + '_ { + ) -> impl Iterator) -> Result<(), String>>> + '_ + { self.executable_instructions.iter() } - pub(crate) fn run( + pub fn run( &self, invocation_inputs: &Vec, wasm_instance: &Instance, @@ -39,6 +47,7 @@ where invocation_inputs, stack: Stack::new(), wasm_instance, + wasm_exports: PhantomData, }; for executable_instruction in self.iter() { @@ -52,9 +61,11 @@ where } } -impl<'binary_input, Instance> TryFrom<&Vec>> for Interpreter +impl<'binary_input, Instance, Export> TryFrom<&Vec>> + for Interpreter where - Instance: wasm::Instance, + Export: wasm::Export, + Instance: wasm::Instance, { type Error = String; @@ -62,18 +73,18 @@ where let executable_instructions = instructions .iter() .map( - |instruction| -> Box) -> Result<(), String>> { + |instruction| -> Box) -> Result<(), String>> { match instruction { Instruction::ArgumentGet(index) => { let index = index.to_owned(); let instruction_name: String = instruction.into(); - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { let invocation_inputs = runtime.invocation_inputs; if index >= (invocation_inputs.len() as u64) { return Err(format!( - "`{}` cannot access argument #{} because it does't exist.", + "`{}` cannot access argument #{} because it doesn't exist.", instruction_name, index )); } @@ -85,15 +96,51 @@ where } Instruction::CallExport(export_name) => { let export_name = (*export_name).to_owned(); + let instruction_name: String = instruction.into(); - Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { - println!("call export {}", export_name); - - Ok(()) + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + 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 inputs: Vec = inputs.iter().map(|i| wasm::Value::I32(*i as i32)).collect(); + + match export.call(&inputs) { + Ok(outputs) => { + for output in outputs.iter() { + let output: i32 = output.try_into().unwrap(); + + runtime.stack.push(output as u64); + } + + Ok(()) + }, + Err(_) => Err("failed".into()), + } + } + None => Err(format!( + "`{}` cannot call the exported function `{}` because there is no enought data in the stack for the arguments (need {}).", + instruction_name, + export_name, + inputs_cardinality, + )) + } + }, + + None => Err(format!( + "`{}` cannot call the exported function `{}` because it doesn't exist.", + instruction_name, + export_name, + )) + } }) } Instruction::ReadUtf8 => { - Box::new(|_runtime: &mut Runtime| -> Result<(), _> { + Box::new(|_runtime: &mut Runtime| -> Result<(), _> { println!("read utf8"); Ok(()) @@ -102,7 +149,7 @@ where Instruction::Call(index) => { let index = index.to_owned(); - Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { println!("call {}", index); Ok(()) @@ -126,8 +173,36 @@ mod tests { use crate::instructions::{stack::Stackable, wasm, Instruction}; use std::{collections::HashMap, convert::TryInto}; + struct Export { + inputs: Vec, + outputs: Vec, + function: fn(arguments: &[wasm::Value]) -> Result, ()>, + } + + impl wasm::Export for Export { + fn inputs_cardinality(&self) -> usize { + self.inputs.len() as usize + } + + fn outputs_cardinality(&self) -> usize { + self.outputs.len() + } + + fn inputs(&self) -> &[wasm::Type] { + &self.inputs + } + + fn outputs(&self) -> &[wasm::Type] { + &self.outputs + } + + fn call(&self, arguments: &[wasm::Value]) -> Result, ()> { + (self.function)(arguments) + } + } + struct Instance { - exports: HashMap, + exports: HashMap, } impl Instance { @@ -135,7 +210,19 @@ mod tests { Self { exports: { let mut hashmap = HashMap::new(); - hashmap.insert("foo".into(), ()); + hashmap.insert( + "sum".into(), + Export { + inputs: vec![wasm::Type::I32, wasm::Type::I32], + outputs: vec![wasm::Type::I32], + function: |arguments: &[wasm::Value]| { + let a: i32 = (&arguments[0]).try_into().unwrap(); + let b: i32 = (&arguments[1]).try_into().unwrap(); + + Ok(vec![wasm::Value::I32(a + b)]) + }, + }, + ); hashmap }, @@ -143,9 +230,9 @@ mod tests { } } - impl wasm::Instance for Instance { - fn export_exists(&self, export_name: &str) -> bool { - self.exports.contains_key(export_name) + impl wasm::Instance for Instance { + fn export(&self, export_name: &str) -> Option<&Export> { + self.exports.get(export_name) } } @@ -158,15 +245,16 @@ mod tests { Instruction::ReadUtf8, Instruction::Call(7), ]; - let interpreter: Interpreter<()> = (&instructions).try_into().unwrap(); + let interpreter: Interpreter<(), ()> = (&instructions).try_into().unwrap(); assert_eq!(interpreter.executable_instructions.len(), 5); } #[test] fn test_interpreter_argument_get() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(0)]).try_into().unwrap(); + let invocation_inputs = vec![42]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -180,8 +268,9 @@ mod tests { #[test] fn test_interpreter_argument_get_invalid_index() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(1)]).try_into().unwrap(); + let invocation_inputs = vec![42]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -192,16 +281,17 @@ mod tests { assert_eq!( error, - String::from("`arg.get 1` cannot access argument #1 because it does't exist.") + String::from("`arg.get 1` cannot access argument #1 because it doesn't exist.") ); } #[test] fn test_interpreter_argument_get_argument_get() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(0), Instruction::ArgumentGet(1)]) .try_into() .unwrap(); + let invocation_inputs = vec![7, 42]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -213,20 +303,43 @@ mod tests { assert_eq!(stack.as_slice(), &[7, 42]); } - /* #[test] fn test_interpreter_call_export() { - let interpreter: Interpreter = - (&vec![Instruction::ArgumentGet(7), Instruction::ArgumentGet(42)]) - .try_into() - .unwrap(); - let run = interpreter.run(&Instance::new()); + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("sum"), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![3, 4]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); assert!(run.is_ok()); let stack = run.unwrap(); - assert_eq!(stack.as_slice(), &[]); + assert_eq!(stack.as_slice(), &[7]); + } + + #[test] + fn test_interpreter_call_export_invalid_export_name() { + let interpreter: Interpreter = + (&vec![Instruction::CallExport("bar")]).try_into().unwrap(); + + let invocation_inputs = vec![]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`call-export "bar"` cannot call the exported function `bar` because it doesn't exist."#) + ); } - */ } diff --git a/lib/interface-types/src/instructions/stack.rs b/lib/interface-types/src/instructions/stack.rs index c9b42d6adf0..1d139a77cec 100644 --- a/lib/interface-types/src/instructions/stack.rs +++ b/lib/interface-types/src/instructions/stack.rs @@ -1,4 +1,4 @@ -pub(crate) trait Stackable { +pub trait Stackable { type Item; fn is_empty(&self) -> bool; @@ -9,7 +9,7 @@ pub(crate) trait Stackable { } #[derive(Debug)] -pub(crate) struct Stack { +pub struct Stack { inner: Vec, } diff --git a/lib/interface-types/src/instructions/wasm.rs b/lib/interface-types/src/instructions/wasm.rs new file mode 100644 index 00000000000..382fe17bc97 --- /dev/null +++ b/lib/interface-types/src/instructions/wasm.rs @@ -0,0 +1,91 @@ +use std::convert::TryFrom; + +pub enum Type { + I32, + I64, + F32, + F64, + V128, +} + +#[derive(Debug)] +pub enum Value { + I32(i32), + I64(i64), + F32(f32), + F64(f64), + V128(u128), +} + +macro_rules! from_x_for_value { + ($native_type:ty, $value_variant:ident) => { + impl From<$native_type> for Value { + fn from(n: $native_type) -> Self { + Self::$value_variant(n) + } + } + + impl TryFrom<&Value> for $native_type { + type Error = &'static str; + + fn try_from(w: &Value) -> Result { + match *w { + Value::$value_variant(n) => Ok(n), + _ => Err("Invalid cast."), + } + } + } + }; +} + +from_x_for_value!(i32, I32); +from_x_for_value!(i64, I64); +from_x_for_value!(f32, F32); +from_x_for_value!(f64, F64); +from_x_for_value!(u128, V128); + +pub trait Export { + fn inputs_cardinality(&self) -> usize; + fn outputs_cardinality(&self) -> usize; + fn inputs(&self) -> &[Type]; + fn outputs(&self) -> &[Type]; + fn call(&self, arguments: &[Value]) -> Result, ()>; +} + +pub trait Instance +where + E: Export, +{ + fn export(&self, export_name: &str) -> Option<&E>; +} + +impl Export for () { + fn inputs_cardinality(&self) -> usize { + 0 + } + + fn outputs_cardinality(&self) -> usize { + 0 + } + + fn inputs(&self) -> &[Type] { + &[] + } + + fn outputs(&self) -> &[Type] { + &[] + } + + fn call(&self, _arguments: &[Value]) -> Result, ()> { + Err(()) + } +} + +impl Instance for () +where + E: Export, +{ + fn export(&self, _export_name: &str) -> Option<&E> { + None + } +} From 39a817817b67f3c0354a46bb9ae3c40ed4c31485 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 11:55:19 +0200 Subject: [PATCH 21/78] feat(interface-types) `Stack` supports `Default`. --- lib/interface-types/src/instructions/stack.rs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/interface-types/src/instructions/stack.rs b/lib/interface-types/src/instructions/stack.rs index 1d139a77cec..d62d40d7cae 100644 --- a/lib/interface-types/src/instructions/stack.rs +++ b/lib/interface-types/src/instructions/stack.rs @@ -8,18 +8,29 @@ pub trait Stackable { fn pop(&mut self, n: usize) -> Option>; } -#[derive(Debug)] -pub struct Stack { +#[derive(Debug, Default)] +pub struct Stack +where + T: Default, +{ inner: Vec, } -impl Stack { +impl Stack +where + T: Default, +{ pub fn new() -> Self { - Self { inner: Vec::new() } + Self { + ..Default::default() + } } } -impl Stackable for Stack { +impl Stackable for Stack +where + T: Default, +{ type Item = T; fn is_empty(&self) -> bool { From 2237e628ab5686174f6fdc57d0a01f10995ac338 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 12:02:11 +0200 Subject: [PATCH 22/78] chore(interface-types) Fix clippy warnings. --- lib/interface-types/src/decoders/binary.rs | 5 +++-- lib/interface-types/src/encoders/wat.rs | 4 ++-- .../src/instructions/interpreter.rs | 16 ++++++++-------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 2fd6a7b4bbc..a4fc146e3ca 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -52,7 +52,7 @@ fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'in } let (output, bytes) = match input.iter().position(|&byte| byte & 0x80 == 0) { - Some(position) => (&input[position + 1..], &input[..position + 1]), + Some(position) => (&input[position + 1..], &input[..=position]), None => (&[] as &[u8], input), }; @@ -61,7 +61,7 @@ fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'in bytes .iter() .rev() - .fold(0, |acc, byte| (acc << 7) | (byte & 0x7f) as u64), + .fold(0, |acc, byte| (acc << 7) | u64::from(byte & 0x7f)), )) } @@ -84,6 +84,7 @@ fn string<'input, E: ParseError<&'input [u8]>>( })) } +#[allow(clippy::type_complexity)] fn list<'input, I, E: ParseError<&'input [u8]>>( input: &'input [u8], item_parser: fn(&'input [u8]) -> IResult<&'input [u8], I, E>, diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index b11b65dc2a0..f04712a5c11 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -67,7 +67,7 @@ impl<'input> From<&Instruction<'input>> for String { } } -fn input_types_to_param(input_types: &Vec) -> String { +fn input_types_to_param(input_types: &[InterfaceType]) -> String { if input_types.is_empty() { "".into() } else { @@ -84,7 +84,7 @@ fn input_types_to_param(input_types: &Vec) -> String { } } -fn output_types_to_result(output_types: &Vec) -> String { +fn output_types_to_result(output_types: &[InterfaceType]) -> String { if output_types.is_empty() { "".into() } else { diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 7da3800f4a1..5e95cbae6a8 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -7,12 +7,15 @@ use std::{ marker::PhantomData, }; +type ExecutableInstruction = + Box) -> Result<(), String>>; + struct Runtime<'invocation, 'instance, Instance, Export> where Export: wasm::Export + 'instance, Instance: wasm::Instance + 'instance, { - invocation_inputs: &'invocation Vec, + invocation_inputs: &'invocation [u64], stack: Stack, wasm_instance: &'instance Instance, wasm_exports: PhantomData, @@ -23,7 +26,7 @@ where Export: wasm::Export, Instance: wasm::Instance, { - executable_instructions: Vec) -> Result<(), String>>>, + executable_instructions: Vec>, } impl Interpreter @@ -31,16 +34,13 @@ where Export: wasm::Export, Instance: wasm::Instance, { - fn iter( - &self, - ) -> impl Iterator) -> Result<(), String>>> + '_ - { + fn iter(&self) -> impl Iterator> + '_ { self.executable_instructions.iter() } pub fn run( &self, - invocation_inputs: &Vec, + invocation_inputs: &[u64], wasm_instance: &Instance, ) -> Result, String> { let mut runtime = Runtime { @@ -73,7 +73,7 @@ where let executable_instructions = instructions .iter() .map( - |instruction| -> Box) -> Result<(), String>> { + |instruction| -> ExecutableInstruction { match instruction { Instruction::ArgumentGet(index) => { let index = index.to_owned(); From b7b37d2e9952769b15b2a71d85980184532ce5fb Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 14:07:56 +0200 Subject: [PATCH 23/78] feat(interface-types) The interpreter stack contains Wasm values. --- .../src/instructions/interpreter.rs | 71 +++++++++---------- lib/interface-types/src/instructions/wasm.rs | 8 ++- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 5e95cbae6a8..ce86bdf59ef 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -1,26 +1,24 @@ use crate::instructions::{ stack::{Stack, Stackable}, - wasm, Instruction, -}; -use std::{ - convert::{TryFrom, TryInto}, - marker::PhantomData, + wasm::{self, Value}, + Instruction, }; - -type ExecutableInstruction = - Box) -> Result<(), String>>; +use std::{convert::TryFrom, marker::PhantomData}; struct Runtime<'invocation, 'instance, Instance, Export> where Export: wasm::Export + 'instance, Instance: wasm::Instance + 'instance, { - invocation_inputs: &'invocation [u64], - stack: Stack, + invocation_inputs: &'invocation [Value], + stack: Stack, wasm_instance: &'instance Instance, wasm_exports: PhantomData, } +type ExecutableInstruction = + Box) -> Result<(), String>>; + pub struct Interpreter where Export: wasm::Export, @@ -40,9 +38,9 @@ where pub fn run( &self, - invocation_inputs: &[u64], + invocation_inputs: &[Value], wasm_instance: &Instance, - ) -> Result, String> { + ) -> Result, String> { let mut runtime = Runtime { invocation_inputs, stack: Stack::new(), @@ -107,14 +105,10 @@ where match runtime.stack.pop(inputs_cardinality) { Some(inputs) => { - let inputs: Vec = inputs.iter().map(|i| wasm::Value::I32(*i as i32)).collect(); - match export.call(&inputs) { Ok(outputs) => { for output in outputs.iter() { - let output: i32 = output.try_into().unwrap(); - - runtime.stack.push(output as u64); + runtime.stack.push(*output); } Ok(()) @@ -129,8 +123,7 @@ where inputs_cardinality, )) } - }, - + } None => Err(format!( "`{}` cannot call the exported function `{}` because it doesn't exist.", instruction_name, @@ -170,13 +163,17 @@ where #[cfg(test)] mod tests { use super::Interpreter; - use crate::instructions::{stack::Stackable, wasm, Instruction}; + use crate::instructions::{ + stack::Stackable, + wasm::{self, Type, Value}, + Instruction, + }; use std::{collections::HashMap, convert::TryInto}; struct Export { - inputs: Vec, - outputs: Vec, - function: fn(arguments: &[wasm::Value]) -> Result, ()>, + inputs: Vec, + outputs: Vec, + function: fn(arguments: &[Value]) -> Result, ()>, } impl wasm::Export for Export { @@ -188,15 +185,15 @@ mod tests { self.outputs.len() } - fn inputs(&self) -> &[wasm::Type] { + fn inputs(&self) -> &[Type] { &self.inputs } - fn outputs(&self) -> &[wasm::Type] { + fn outputs(&self) -> &[Type] { &self.outputs } - fn call(&self, arguments: &[wasm::Value]) -> Result, ()> { + fn call(&self, arguments: &[Value]) -> Result, ()> { (self.function)(arguments) } } @@ -213,13 +210,13 @@ mod tests { hashmap.insert( "sum".into(), Export { - inputs: vec![wasm::Type::I32, wasm::Type::I32], - outputs: vec![wasm::Type::I32], - function: |arguments: &[wasm::Value]| { + inputs: vec![Type::I32, Type::I32], + outputs: vec![Type::I32], + function: |arguments: &[Value]| { let a: i32 = (&arguments[0]).try_into().unwrap(); let b: i32 = (&arguments[1]).try_into().unwrap(); - Ok(vec![wasm::Value::I32(a + b)]) + Ok(vec![Value::I32(a + b)]) }, }, ); @@ -255,7 +252,7 @@ mod tests { let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(0)]).try_into().unwrap(); - let invocation_inputs = vec![42]; + let invocation_inputs = vec![Value::I32(42)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -263,7 +260,7 @@ mod tests { let stack = run.unwrap(); - assert_eq!(stack.as_slice(), &[42]); + assert_eq!(stack.as_slice(), &[Value::I32(42)]); } #[test] @@ -271,7 +268,7 @@ mod tests { let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(1)]).try_into().unwrap(); - let invocation_inputs = vec![42]; + let invocation_inputs = vec![Value::I32(42)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -292,7 +289,7 @@ mod tests { .try_into() .unwrap(); - let invocation_inputs = vec![7, 42]; + let invocation_inputs = vec![Value::I32(7), Value::I32(42)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -300,7 +297,7 @@ mod tests { let stack = run.unwrap(); - assert_eq!(stack.as_slice(), &[7, 42]); + assert_eq!(stack.as_slice(), &[Value::I32(7), Value::I32(42)]); } #[test] @@ -313,7 +310,7 @@ mod tests { .try_into() .unwrap(); - let invocation_inputs = vec![3, 4]; + let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -321,7 +318,7 @@ mod tests { let stack = run.unwrap(); - assert_eq!(stack.as_slice(), &[7]); + assert_eq!(stack.as_slice(), &[Value::I32(7)]); } #[test] diff --git a/lib/interface-types/src/instructions/wasm.rs b/lib/interface-types/src/instructions/wasm.rs index 382fe17bc97..ab5f0e6f879 100644 --- a/lib/interface-types/src/instructions/wasm.rs +++ b/lib/interface-types/src/instructions/wasm.rs @@ -8,7 +8,7 @@ pub enum Type { V128, } -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum Value { I32(i32), I64(i64), @@ -17,6 +17,12 @@ pub enum Value { V128(u128), } +impl Default for Value { + fn default() -> Self { + Self::I32(0) + } +} + macro_rules! from_x_for_value { ($native_type:ty, $value_variant:ident) => { impl From<$native_type> for Value { From 56afb4da6391a6c92965b7873e951c4e95d257d0 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 14:31:15 +0200 Subject: [PATCH 24/78] feat(interface-types) Check signature of the exported function to call. --- .../src/instructions/interpreter.rs | 67 ++++++++++++++++++- lib/interface-types/src/instructions/wasm.rs | 13 ++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index ce86bdf59ef..fb073765f95 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -1,6 +1,6 @@ use crate::instructions::{ stack::{Stack, Stackable}, - wasm::{self, Value}, + wasm::{self, Type, Value}, Instruction, }; use std::{convert::TryFrom, marker::PhantomData}; @@ -105,6 +105,20 @@ where match runtime.stack.pop(inputs_cardinality) { Some(inputs) => { + let input_types = inputs + .iter() + .map(|input| input.into()) + .collect::>(); + + if input_types != export.inputs() { + return Err(format!( + "`{}` cannot call the exported function `{}` because the value types in the stack mismatch the function signature (expects {:?}).", + instruction_name, + export_name, + export.inputs(), + )) + } + match export.call(&inputs) { Ok(outputs) => { for output in outputs.iter() { @@ -117,7 +131,7 @@ where } } None => Err(format!( - "`{}` cannot call the exported function `{}` because there is no enought data in the stack for the arguments (need {}).", + "`{}` cannot call the exported function `{}` because there is no enought data in the stack for the arguments (needs {}).", instruction_name, export_name, inputs_cardinality, @@ -339,4 +353,53 @@ mod tests { String::from(r#"`call-export "bar"` cannot call the exported function `bar` because it doesn't exist."#) ); } + + #[test] + fn test_interpreter_call_export_too_small_stack() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(0), + Instruction::CallExport("sum"), + // ^^^ `sum` expects 2 values in the stack, only one is present + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`call-export "sum"` cannot call the exported function `sum` because there is no enought data in the stack for the arguments (needs 2)."#) + ); + } + + #[test] + fn test_interpreter_call_export_invalid_types_in_the_stack() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("sum"), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![Value::I32(3), Value::I64(4)]; + // ^^^ mismatch with `sum` signature + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`call-export "sum"` cannot call the exported function `sum` because the value types in the stack mismatch the function signature (expects [I32, I32])."#) + ); + } } diff --git a/lib/interface-types/src/instructions/wasm.rs b/lib/interface-types/src/instructions/wasm.rs index ab5f0e6f879..8857b6db554 100644 --- a/lib/interface-types/src/instructions/wasm.rs +++ b/lib/interface-types/src/instructions/wasm.rs @@ -1,5 +1,6 @@ use std::convert::TryFrom; +#[derive(Debug, PartialEq)] pub enum Type { I32, I64, @@ -17,6 +18,18 @@ pub enum Value { V128(u128), } +impl From<&Value> for Type { + fn from(value: &Value) -> Self { + match value { + Value::I32(_) => Type::I32, + Value::I64(_) => Type::I64, + Value::F32(_) => Type::F32, + Value::F64(_) => Type::F64, + Value::V128(_) => Type::V128, + } + } +} + impl Default for Value { fn default() -> Self { Self::I32(0) From 8557e83ce6b8799d0ee1c4ca83491acee90f85ba Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 14:55:37 +0200 Subject: [PATCH 25/78] feat(interface-types) Better error message when calling an exported function failed. --- .../src/instructions/interpreter.rs | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index fb073765f95..6fb25240a57 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -126,8 +126,12 @@ where } Ok(()) - }, - Err(_) => Err("failed".into()), + } + Err(_) => Err(format!( + "`{}` failed when calling the exported function `{}`.", + instruction_name, + export_name + )) } } None => Err(format!( @@ -402,4 +406,43 @@ mod tests { String::from(r#"`call-export "sum"` cannot call the exported function `sum` because the value types in the stack mismatch the function signature (expects [I32, I32])."#) ); } + + #[test] + fn test_interpreter_call_export_failed_when_calling() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("sum"), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; + let instance = Instance { + exports: { + let mut hashmap = HashMap::new(); + hashmap.insert( + "sum".into(), + Export { + inputs: vec![Type::I32, Type::I32], + outputs: vec![Type::I32], + function: |_| Err(()), + // ^^^^^^ function fails + }, + ); + + hashmap + }, + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`call-export "sum"` failed when calling the exported function `sum`."#) + ); + } } From aea18f6250b721263fee4e16ddf48dd10c567a1b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 20 Sep 2019 14:59:18 +0200 Subject: [PATCH 26/78] test(interface-types) Test calling a void exported function. --- .../src/instructions/interpreter.rs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 6fb25240a57..7f7d75aa0c9 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -427,7 +427,7 @@ mod tests { inputs: vec![Type::I32, Type::I32], outputs: vec![Type::I32], function: |_| Err(()), - // ^^^^^^ function fails + // ^^^^^^^ function fails }, ); @@ -445,4 +445,40 @@ mod tests { String::from(r#"`call-export "sum"` failed when calling the exported function `sum`."#) ); } + + #[test] + fn test_interpreter_call_export_that_returns_nothing() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("sum"), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; + let instance = Instance { + exports: { + let mut hashmap = HashMap::new(); + hashmap.insert( + "sum".into(), + Export { + inputs: vec![Type::I32, Type::I32], + outputs: vec![Type::I32], + function: |_| Ok(vec![]), + // ^^^^^^^^^^ void function + }, + ); + + hashmap + }, + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_ok()); + + let stack = run.unwrap(); + + assert!(stack.is_empty()); + } } From be5624e28bf48cc99aa30852fada92aaee0b7360 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 23 Sep 2019 16:29:01 +0200 Subject: [PATCH 27/78] feat(interface-types) Implement the `read-utf8` instruction. It implies to create the `wasm::Memory` trait. Also, the patch updates `wasm::Type` and `wasm::Value` to `wasm::InterfaceType` and `wasm::InterfaceValue`. It enforces a new rule that is: All values in the stack must be owned by the stack. Any value going in or out must be cloned. --- .../src/instructions/interpreter.rs | 350 ++++++++++++++---- lib/interface-types/src/instructions/stack.rs | 6 +- lib/interface-types/src/instructions/wasm.rs | 111 ++++-- 3 files changed, 352 insertions(+), 115 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 7f7d75aa0c9..4028cd7ade5 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -1,51 +1,56 @@ use crate::instructions::{ stack::{Stack, Stackable}, - wasm::{self, Type, Value}, + wasm::{self, InterfaceType, InterfaceValue}, Instruction, }; -use std::{convert::TryFrom, marker::PhantomData}; +use std::{cell::Cell, convert::TryFrom, marker::PhantomData}; -struct Runtime<'invocation, 'instance, Instance, Export> +struct Runtime<'invocation, 'instance, Instance, Export, Memory> where Export: wasm::Export + 'instance, - Instance: wasm::Instance + 'instance, + Memory: wasm::Memory + 'instance, + Instance: wasm::Instance + 'instance, { - invocation_inputs: &'invocation [Value], - stack: Stack, + invocation_inputs: &'invocation [InterfaceValue], + stack: Stack, wasm_instance: &'instance Instance, wasm_exports: PhantomData, + wasm_memory: PhantomData, } -type ExecutableInstruction = - Box) -> Result<(), String>>; +type ExecutableInstruction = + Box) -> Result<(), String>>; -pub struct Interpreter +pub struct Interpreter where Export: wasm::Export, - Instance: wasm::Instance, + Memory: wasm::Memory, + Instance: wasm::Instance, { - executable_instructions: Vec>, + executable_instructions: Vec>, } -impl Interpreter +impl Interpreter where Export: wasm::Export, - Instance: wasm::Instance, + Memory: wasm::Memory, + Instance: wasm::Instance, { - fn iter(&self) -> impl Iterator> + '_ { + fn iter(&self) -> impl Iterator> + '_ { self.executable_instructions.iter() } pub fn run( &self, - invocation_inputs: &[Value], + invocation_inputs: &[InterfaceValue], wasm_instance: &Instance, - ) -> Result, String> { + ) -> Result, String> { let mut runtime = Runtime { invocation_inputs, stack: Stack::new(), wasm_instance, wasm_exports: PhantomData, + wasm_memory: PhantomData, }; for executable_instruction in self.iter() { @@ -59,11 +64,12 @@ where } } -impl<'binary_input, Instance, Export> TryFrom<&Vec>> - for Interpreter +impl<'binary_input, Instance, Export, Memory> TryFrom<&Vec>> + for Interpreter where Export: wasm::Export, - Instance: wasm::Instance, + Memory: wasm::Memory, + Instance: wasm::Instance, { type Error = String; @@ -71,13 +77,13 @@ where let executable_instructions = instructions .iter() .map( - |instruction| -> ExecutableInstruction { + |instruction| -> ExecutableInstruction { match instruction { Instruction::ArgumentGet(index) => { let index = index.to_owned(); let instruction_name: String = instruction.into(); - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { let invocation_inputs = runtime.invocation_inputs; if index >= (invocation_inputs.len() as u64) { @@ -87,7 +93,7 @@ where )); } - runtime.stack.push(invocation_inputs[index as usize]); + runtime.stack.push(invocation_inputs[index as usize].clone()); Ok(()) }) @@ -96,7 +102,7 @@ where let export_name = (*export_name).to_owned(); let instruction_name: String = instruction.into(); - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { let instance = runtime.wasm_instance; match instance.export(&export_name) { @@ -108,11 +114,11 @@ where let input_types = inputs .iter() .map(|input| input.into()) - .collect::>(); + .collect::>(); if input_types != export.inputs() { return Err(format!( - "`{}` cannot call the exported function `{}` because the value types in the stack mismatch the function signature (expects {:?}).", + "`{}` cannot call the exported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).", instruction_name, export_name, export.inputs(), @@ -122,7 +128,7 @@ where match export.call(&inputs) { Ok(outputs) => { for output in outputs.iter() { - runtime.stack.push(*output); + runtime.stack.push(output.clone()); } Ok(()) @@ -135,7 +141,7 @@ where } } None => Err(format!( - "`{}` cannot call the exported function `{}` because there is no enought data in the stack for the arguments (needs {}).", + "`{}` cannot call the exported function `{}` because there is no enough data on the stack for the arguments (needs {}).", instruction_name, export_name, inputs_cardinality, @@ -151,16 +157,59 @@ where }) } Instruction::ReadUtf8 => { - Box::new(|_runtime: &mut Runtime| -> Result<(), _> { - println!("read utf8"); + let instruction_name: String = instruction.into(); - Ok(()) + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + match runtime.stack.pop(2) { + Some(inputs) => match runtime.wasm_instance.memory(0) { + Some(memory) => { + let length = i32::try_from(&inputs[0])? as usize; + let pointer = i32::try_from(&inputs[1])? as usize; + let memory_view = memory.view::(); + + 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 = (&memory_view[pointer..pointer + length]) + .iter() + .map(Cell::get) + .collect(); + + match String::from_utf8(data) { + Ok(string) => { + runtime.stack.push(InterfaceValue::String(string)); + + Ok(()) + } + Err(utf8_error) => Err(format!( + "`{}` failed because the read string isn't UTF-8 valid ({}).", + instruction_name, + utf8_error, + )) + } + } + None => Err(format!( + "`{}` failed because there is no memory to read.", + instruction_name + )) + } + None => Err(format!( + "`{}` failed because there is no enough data on the stack (needs 2).", + instruction_name, + )) + } }) } Instruction::Call(index) => { let index = index.to_owned(); - Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { println!("call {}", index); Ok(()) @@ -183,15 +232,15 @@ mod tests { use super::Interpreter; use crate::instructions::{ stack::Stackable, - wasm::{self, Type, Value}, + wasm::{self, InterfaceType, InterfaceValue}, Instruction, }; - use std::{collections::HashMap, convert::TryInto}; + use std::{cell::Cell, collections::HashMap, convert::TryInto}; struct Export { - inputs: Vec, - outputs: Vec, - function: fn(arguments: &[Value]) -> Result, ()>, + inputs: Vec, + outputs: Vec, + function: fn(arguments: &[InterfaceValue]) -> Result, ()>, } impl wasm::Export for Export { @@ -203,21 +252,42 @@ mod tests { self.outputs.len() } - fn inputs(&self) -> &[Type] { + fn inputs(&self) -> &[InterfaceType] { &self.inputs } - fn outputs(&self) -> &[Type] { + fn outputs(&self) -> &[InterfaceType] { &self.outputs } - fn call(&self, arguments: &[Value]) -> Result, ()> { + fn call(&self, arguments: &[InterfaceValue]) -> Result, ()> { (self.function)(arguments) } } + #[derive(Default)] + struct Memory { + data: Vec>, + } + + impl Memory { + fn new(data: Vec>) -> Self { + Self { data } + } + } + + impl wasm::Memory for Memory { + fn view(&self) -> &[Cell] { + let slice = self.data.as_slice(); + + unsafe { ::std::slice::from_raw_parts(slice.as_ptr() as *const Cell, slice.len()) } + } + } + + #[derive(Default)] struct Instance { exports: HashMap, + memory: Memory, } impl Instance { @@ -228,27 +298,32 @@ mod tests { hashmap.insert( "sum".into(), Export { - inputs: vec![Type::I32, Type::I32], - outputs: vec![Type::I32], - function: |arguments: &[Value]| { + inputs: vec![InterfaceType::I32, InterfaceType::I32], + outputs: vec![InterfaceType::I32], + function: |arguments: &[InterfaceValue]| { let a: i32 = (&arguments[0]).try_into().unwrap(); let b: i32 = (&arguments[1]).try_into().unwrap(); - Ok(vec![Value::I32(a + b)]) + Ok(vec![InterfaceValue::I32(a + b)]) }, }, ); hashmap }, + memory: Memory::new(vec![]), } } } - impl wasm::Instance for Instance { + impl wasm::Instance for Instance { fn export(&self, export_name: &str) -> Option<&Export> { self.exports.get(export_name) } + + fn memory(&self, _index: usize) -> Option<&Memory> { + Some(&self.memory) + } } #[test] @@ -260,17 +335,17 @@ mod tests { Instruction::ReadUtf8, Instruction::Call(7), ]; - let interpreter: Interpreter<(), ()> = (&instructions).try_into().unwrap(); + let interpreter: Interpreter<(), (), ()> = (&instructions).try_into().unwrap(); assert_eq!(interpreter.executable_instructions.len(), 5); } #[test] fn test_interpreter_argument_get() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(0)]).try_into().unwrap(); - let invocation_inputs = vec![Value::I32(42)]; + let invocation_inputs = vec![InterfaceValue::I32(42)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -278,15 +353,15 @@ mod tests { let stack = run.unwrap(); - assert_eq!(stack.as_slice(), &[Value::I32(42)]); + assert_eq!(stack.as_slice(), &[InterfaceValue::I32(42)]); } #[test] fn test_interpreter_argument_get_invalid_index() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(1)]).try_into().unwrap(); - let invocation_inputs = vec![Value::I32(42)]; + let invocation_inputs = vec![InterfaceValue::I32(42)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -302,12 +377,12 @@ mod tests { #[test] fn test_interpreter_argument_get_argument_get() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(0), Instruction::ArgumentGet(1)]) .try_into() .unwrap(); - let invocation_inputs = vec![Value::I32(7), Value::I32(42)]; + let invocation_inputs = vec![InterfaceValue::I32(7), InterfaceValue::I32(42)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -315,12 +390,15 @@ mod tests { let stack = run.unwrap(); - assert_eq!(stack.as_slice(), &[Value::I32(7), Value::I32(42)]); + assert_eq!( + stack.as_slice(), + &[InterfaceValue::I32(7), InterfaceValue::I32(42)] + ); } #[test] fn test_interpreter_call_export() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -328,7 +406,7 @@ mod tests { .try_into() .unwrap(); - let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -336,12 +414,12 @@ mod tests { let stack = run.unwrap(); - assert_eq!(stack.as_slice(), &[Value::I32(7)]); + assert_eq!(stack.as_slice(), &[InterfaceValue::I32(7)]); } #[test] fn test_interpreter_call_export_invalid_export_name() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::CallExport("bar")]).try_into().unwrap(); let invocation_inputs = vec![]; @@ -359,16 +437,16 @@ mod tests { } #[test] - fn test_interpreter_call_export_too_small_stack() { - let interpreter: Interpreter = (&vec![ + fn test_interpreter_call_export_stack_is_too_small() { + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(0), Instruction::CallExport("sum"), - // ^^^ `sum` expects 2 values in the stack, only one is present + // ^^^ `sum` expects 2 values on the stack, only one is present ]) .try_into() .unwrap(); - let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -378,13 +456,13 @@ mod tests { assert_eq!( error, - String::from(r#"`call-export "sum"` cannot call the exported function `sum` because there is no enought data in the stack for the arguments (needs 2)."#) + String::from(r#"`call-export "sum"` cannot call the exported function `sum` because there is no enough data on the stack for the arguments (needs 2)."#) ); } #[test] fn test_interpreter_call_export_invalid_types_in_the_stack() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -392,7 +470,7 @@ mod tests { .try_into() .unwrap(); - let invocation_inputs = vec![Value::I32(3), Value::I64(4)]; + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I64(4)]; // ^^^ mismatch with `sum` signature let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -403,13 +481,13 @@ mod tests { assert_eq!( error, - String::from(r#"`call-export "sum"` cannot call the exported function `sum` because the value types in the stack mismatch the function signature (expects [I32, I32])."#) + String::from(r#"`call-export "sum"` cannot call the exported function `sum` because the value types on the stack mismatch the function signature (expects [I32, I32])."#) ); } #[test] fn test_interpreter_call_export_failed_when_calling() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -417,15 +495,15 @@ mod tests { .try_into() .unwrap(); - let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; let instance = Instance { exports: { let mut hashmap = HashMap::new(); hashmap.insert( "sum".into(), Export { - inputs: vec![Type::I32, Type::I32], - outputs: vec![Type::I32], + inputs: vec![InterfaceType::I32, InterfaceType::I32], + outputs: vec![InterfaceType::I32], function: |_| Err(()), // ^^^^^^^ function fails }, @@ -433,6 +511,7 @@ mod tests { hashmap }, + ..Default::default() }; let run = interpreter.run(&invocation_inputs, &instance); @@ -448,7 +527,7 @@ mod tests { #[test] fn test_interpreter_call_export_that_returns_nothing() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -456,15 +535,15 @@ mod tests { .try_into() .unwrap(); - let invocation_inputs = vec![Value::I32(3), Value::I32(4)]; + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; let instance = Instance { exports: { let mut hashmap = HashMap::new(); hashmap.insert( "sum".into(), Export { - inputs: vec![Type::I32, Type::I32], - outputs: vec![Type::I32], + inputs: vec![InterfaceType::I32, InterfaceType::I32], + outputs: vec![InterfaceType::I32], function: |_| Ok(vec![]), // ^^^^^^^^^^ void function }, @@ -472,6 +551,7 @@ mod tests { hashmap }, + ..Default::default() }; let run = interpreter.run(&invocation_inputs, &instance); @@ -481,4 +561,128 @@ mod tests { assert!(stack.is_empty()); } + + #[test] + fn test_interpreter_read_utf8() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::ReadUtf8, + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(13), InterfaceValue::I32(0)]; + // ^^^^^^^ length ^^^^^^ pointer + let instance = Instance { + memory: Memory::new( + "Hello, World!" + .as_bytes() + .iter() + .map(|u| Cell::new(*u)) + .collect(), + ), + ..Default::default() + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_ok()); + + let stack = run.unwrap(); + + assert_eq!( + stack.as_slice(), + &[InterfaceValue::String("Hello, World!".into())] + ); + } + + #[test] + fn test_interpreter_read_utf8_out_of_memory() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::ReadUtf8, + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(13), InterfaceValue::I32(0)]; + // ^^^^^^^ length ^^^^^^ pointer + // is too long + let instance = Instance { + memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), + ..Default::default() + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from( + r#"`read-utf8` failed because it has to read out of the memory bounds (index 13 > memory length 6)."# + ) + ); + } + + #[test] + fn test_interpreter_read_utf8_invalid_encoding() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::ReadUtf8, + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(4), InterfaceValue::I32(0)]; + // ^^^^^^ length ^^^^^^ pointer + let instance = Instance { + memory: Memory::new( + vec![0, 159, 146, 150] + .iter() + .map(|b| Cell::new(*b)) + .collect::>>(), + ), + ..Default::default() + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`read-utf8` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#) + ); + } + + #[test] + fn test_interpreter_read_utf8_stack_is_too_small() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(0), + Instruction::ReadUtf8, + // ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present. + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from( + r#"`read-utf8` failed because there is no enough data on the stack (needs 2)."# + ) + ); + } } diff --git a/lib/interface-types/src/instructions/stack.rs b/lib/interface-types/src/instructions/stack.rs index d62d40d7cae..57b999df26d 100644 --- a/lib/interface-types/src/instructions/stack.rs +++ b/lib/interface-types/src/instructions/stack.rs @@ -11,14 +11,14 @@ pub trait Stackable { #[derive(Debug, Default)] pub struct Stack where - T: Default, + T: Default + Clone, { inner: Vec, } impl Stack where - T: Default, + T: Default + Clone, { pub fn new() -> Self { Self { @@ -29,7 +29,7 @@ where impl Stackable for Stack where - T: Default, + T: Default + Clone, { type Item = T; diff --git a/lib/interface-types/src/instructions/wasm.rs b/lib/interface-types/src/instructions/wasm.rs index 8857b6db554..33a25ed0a5c 100644 --- a/lib/interface-types/src/instructions/wasm.rs +++ b/lib/interface-types/src/instructions/wasm.rs @@ -1,55 +1,56 @@ -use std::convert::TryFrom; - -#[derive(Debug, PartialEq)] -pub enum Type { - I32, - I64, - F32, - F64, - V128, -} +use std::{cell::Cell, convert::TryFrom}; + +pub use crate::ast::InterfaceType; -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Value { +#[derive(Debug, Clone, PartialEq)] +pub enum InterfaceValue { + Int(isize), + Float(f64), + Any(isize), + String(String), + // Seq(…), I32(i32), I64(i64), F32(f32), F64(f64), - V128(u128), + // AnyRef(…), } -impl From<&Value> for Type { - fn from(value: &Value) -> Self { +impl From<&InterfaceValue> for InterfaceType { + fn from(value: &InterfaceValue) -> Self { match value { - Value::I32(_) => Type::I32, - Value::I64(_) => Type::I64, - Value::F32(_) => Type::F32, - Value::F64(_) => Type::F64, - Value::V128(_) => Type::V128, + InterfaceValue::Int(_) => Self::Int, + InterfaceValue::Float(_) => Self::Float, + InterfaceValue::Any(_) => Self::Any, + InterfaceValue::String(_) => Self::String, + InterfaceValue::I32(_) => Self::I32, + InterfaceValue::I64(_) => Self::I64, + InterfaceValue::F32(_) => Self::F32, + InterfaceValue::F64(_) => Self::F64, } } } -impl Default for Value { +impl Default for InterfaceValue { fn default() -> Self { Self::I32(0) } } -macro_rules! from_x_for_value { +macro_rules! from_x_for_interface_value { ($native_type:ty, $value_variant:ident) => { - impl From<$native_type> for Value { + impl From<$native_type> for InterfaceValue { fn from(n: $native_type) -> Self { Self::$value_variant(n) } } - impl TryFrom<&Value> for $native_type { + impl TryFrom<&InterfaceValue> for $native_type { type Error = &'static str; - fn try_from(w: &Value) -> Result { + fn try_from(w: &InterfaceValue) -> Result { match *w { - Value::$value_variant(n) => Ok(n), + InterfaceValue::$value_variant(n) => Ok(n), _ => Err("Invalid cast."), } } @@ -57,25 +58,46 @@ macro_rules! from_x_for_value { }; } -from_x_for_value!(i32, I32); -from_x_for_value!(i64, I64); -from_x_for_value!(f32, F32); -from_x_for_value!(f64, F64); -from_x_for_value!(u128, V128); +from_x_for_interface_value!(i32, I32); +from_x_for_interface_value!(i64, I64); +from_x_for_interface_value!(f32, F32); +from_x_for_interface_value!(f64, F64); + +pub trait ValueType: Copy + Sized {} + +macro_rules! value_type { + ($native_type:ty) => { + impl ValueType for $native_type {} + }; + + ($($native_type:ty),*) => { + $( + value_type!($native_type); + )* + }; +} + +value_type!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64); pub trait Export { fn inputs_cardinality(&self) -> usize; fn outputs_cardinality(&self) -> usize; - fn inputs(&self) -> &[Type]; - fn outputs(&self) -> &[Type]; - fn call(&self, arguments: &[Value]) -> Result, ()>; + fn inputs(&self) -> &[InterfaceType]; + fn outputs(&self) -> &[InterfaceType]; + fn call(&self, arguments: &[InterfaceValue]) -> Result, ()>; } -pub trait Instance +pub trait Memory { + fn view(&self) -> &[Cell]; +} + +pub trait Instance where E: Export, + M: Memory, { fn export(&self, export_name: &str) -> Option<&E>; + fn memory(&self, index: usize) -> Option<&M>; } impl Export for () { @@ -87,24 +109,35 @@ impl Export for () { 0 } - fn inputs(&self) -> &[Type] { + fn inputs(&self) -> &[InterfaceType] { &[] } - fn outputs(&self) -> &[Type] { + fn outputs(&self) -> &[InterfaceType] { &[] } - fn call(&self, _arguments: &[Value]) -> Result, ()> { + fn call(&self, _arguments: &[InterfaceValue]) -> Result, ()> { Err(()) } } -impl Instance for () +impl Memory for () { + fn view(&self) -> &[Cell] { + &[] + } +} + +impl Instance for () where E: Export, + M: Memory, { fn export(&self, _export_name: &str) -> Option<&E> { None } + + fn memory(&self, _: usize) -> Option<&M> { + None + } } From 4d9dacb482eb909aca63861792ea9815db26194f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 25 Sep 2019 21:53:23 +0200 Subject: [PATCH 28/78] feat(interface-types) Implement the `call` executable instruction. The patch requires to implement the `wasm::TypedIndex`, `wasm::LocalImportIndex`, and the `wasm::LocalImport` traits. --- lib/interface-types/src/decoders/binary.rs | 2 +- .../src/instructions/interpreter.rs | 359 ++++++++++++++++-- lib/interface-types/src/instructions/mod.rs | 2 +- lib/interface-types/src/instructions/wasm.rs | 83 +++- lib/interface-types/src/lib.rs | 2 +- 5 files changed, 403 insertions(+), 45 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index a4fc146e3ca..cef85ffeda8 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -138,7 +138,7 @@ fn instructions<'input, E: ParseError<&'input [u8]>>( 0x01 => { consume!((input, argument_0) = leb(input)?); - (input, Instruction::Call(argument_0)) + (input, Instruction::Call(argument_0 as usize)) } 0x02 => { diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 4028cd7ade5..e4494c85674 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -1,42 +1,49 @@ use crate::instructions::{ stack::{Stack, Stackable}, - wasm::{self, InterfaceType, InterfaceValue}, + wasm::{self, FunctionIndex, InterfaceType, InterfaceValue, TypedIndex}, Instruction, }; use std::{cell::Cell, convert::TryFrom, marker::PhantomData}; -struct Runtime<'invocation, 'instance, Instance, Export, Memory> +struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory> where Export: wasm::Export + 'instance, + LocalImport: wasm::LocalImport + 'instance, Memory: wasm::Memory + 'instance, - Instance: wasm::Instance + 'instance, + Instance: wasm::Instance + 'instance, { invocation_inputs: &'invocation [InterfaceValue], stack: Stack, wasm_instance: &'instance Instance, wasm_exports: PhantomData, - wasm_memory: PhantomData, + wasm_locals_or_imports: PhantomData, + wasm_memories: PhantomData, } -type ExecutableInstruction = - Box) -> Result<(), String>>; +type ExecutableInstruction = + Box) -> Result<(), String>>; -pub struct Interpreter +pub struct Interpreter where Export: wasm::Export, + LocalImport: wasm::LocalImport, Memory: wasm::Memory, - Instance: wasm::Instance, + Instance: wasm::Instance, { - executable_instructions: Vec>, + executable_instructions: Vec>, } -impl Interpreter +impl Interpreter where Export: wasm::Export, + LocalImport: wasm::LocalImport, Memory: wasm::Memory, - Instance: wasm::Instance, + Instance: wasm::Instance, { - fn iter(&self) -> impl Iterator> + '_ { + fn iter( + &self, + ) -> impl Iterator> + '_ + { self.executable_instructions.iter() } @@ -50,7 +57,8 @@ where stack: Stack::new(), wasm_instance, wasm_exports: PhantomData, - wasm_memory: PhantomData, + wasm_locals_or_imports: PhantomData, + wasm_memories: PhantomData, }; for executable_instruction in self.iter() { @@ -64,12 +72,13 @@ where } } -impl<'binary_input, Instance, Export, Memory> TryFrom<&Vec>> - for Interpreter +impl<'binary_input, Instance, Export, LocalImport, Memory> TryFrom<&Vec>> + for Interpreter where Export: wasm::Export, + LocalImport: wasm::LocalImport, Memory: wasm::Memory, - Instance: wasm::Instance, + Instance: wasm::Instance, { type Error = String; @@ -77,13 +86,13 @@ where let executable_instructions = instructions .iter() .map( - |instruction| -> ExecutableInstruction { + |instruction| -> ExecutableInstruction { match instruction { Instruction::ArgumentGet(index) => { let index = index.to_owned(); let instruction_name: String = instruction.into(); - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { let invocation_inputs = runtime.invocation_inputs; if index >= (invocation_inputs.len() as u64) { @@ -102,7 +111,7 @@ where let export_name = (*export_name).to_owned(); let instruction_name: String = instruction.into(); - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { let instance = runtime.wasm_instance; match instance.export(&export_name) { @@ -159,7 +168,7 @@ where Instruction::ReadUtf8 => { let instruction_name: String = instruction.into(); - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { match runtime.stack.pop(2) { Some(inputs) => match runtime.wasm_instance.memory(0) { Some(memory) => { @@ -208,11 +217,61 @@ where } Instruction::Call(index) => { let index = index.to_owned(); + let instruction_name: String = instruction.into(); - Box::new(move |_runtime: &mut Runtime| -> Result<(), _> { - println!("call {}", index); + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + let instance = runtime.wasm_instance; + let function_index = FunctionIndex::new(index); - Ok(()) + match instance.local_or_import(function_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::>(); + + 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, + 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, + index + )) + } + } + None => Err(format!( + "`{}` cannot call the local or imported function `{}` because there is no enough data on the stack for the arguments (needs {}).", + instruction_name, + index, + inputs_cardinality, + )) + } + } + None => Err(format!( + "`{}` cannot call the local or imported function `{}` because it doesn't exist.", + instruction_name, + index, + )) + } }) } _ => unimplemented!(), @@ -265,6 +324,34 @@ mod tests { } } + struct LocalImport { + inputs: Vec, + outputs: Vec, + function: fn(arguments: &[InterfaceValue]) -> Result, ()>, + } + + impl wasm::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, ()> { + (self.function)(arguments) + } + } + #[derive(Default)] struct Memory { data: Vec>, @@ -287,6 +374,7 @@ mod tests { #[derive(Default)] struct Instance { exports: HashMap, + locals_or_imports: HashMap, memory: Memory, } @@ -311,16 +399,41 @@ mod tests { 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![]), } } } - impl wasm::Instance for Instance { + impl wasm::Instance for Instance { fn export(&self, export_name: &str) -> Option<&Export> { self.exports.get(export_name) } + fn local_or_import( + &self, + index: I, + ) -> Option<&LocalImport> { + self.locals_or_imports.get(&index.index()) + } + fn memory(&self, _index: usize) -> Option<&Memory> { Some(&self.memory) } @@ -335,14 +448,14 @@ mod tests { Instruction::ReadUtf8, Instruction::Call(7), ]; - let interpreter: Interpreter<(), (), ()> = (&instructions).try_into().unwrap(); + let interpreter: Interpreter<(), (), (), ()> = (&instructions).try_into().unwrap(); assert_eq!(interpreter.executable_instructions.len(), 5); } #[test] fn test_interpreter_argument_get() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(0)]).try_into().unwrap(); let invocation_inputs = vec![InterfaceValue::I32(42)]; @@ -358,7 +471,7 @@ mod tests { #[test] fn test_interpreter_argument_get_invalid_index() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(1)]).try_into().unwrap(); let invocation_inputs = vec![InterfaceValue::I32(42)]; @@ -377,7 +490,7 @@ mod tests { #[test] fn test_interpreter_argument_get_argument_get() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::ArgumentGet(0), Instruction::ArgumentGet(1)]) .try_into() .unwrap(); @@ -398,7 +511,7 @@ mod tests { #[test] fn test_interpreter_call_export() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -419,7 +532,7 @@ mod tests { #[test] fn test_interpreter_call_export_invalid_export_name() { - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![Instruction::CallExport("bar")]).try_into().unwrap(); let invocation_inputs = vec![]; @@ -438,7 +551,7 @@ mod tests { #[test] fn test_interpreter_call_export_stack_is_too_small() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(0), Instruction::CallExport("sum"), // ^^^ `sum` expects 2 values on the stack, only one is present @@ -462,7 +575,7 @@ mod tests { #[test] fn test_interpreter_call_export_invalid_types_in_the_stack() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -471,7 +584,7 @@ mod tests { .unwrap(); let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I64(4)]; - // ^^^ mismatch with `sum` signature + // ^^^ mismatch with `sum` signature let instance = Instance::new(); let run = interpreter.run(&invocation_inputs, &instance); @@ -487,7 +600,7 @@ mod tests { #[test] fn test_interpreter_call_export_failed_when_calling() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -527,7 +640,7 @@ mod tests { #[test] fn test_interpreter_call_export_that_returns_nothing() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::CallExport("sum"), @@ -564,7 +677,7 @@ mod tests { #[test] fn test_interpreter_read_utf8() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::ReadUtf8, @@ -598,7 +711,7 @@ mod tests { #[test] fn test_interpreter_read_utf8_out_of_memory() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::ReadUtf8, @@ -629,7 +742,7 @@ mod tests { #[test] fn test_interpreter_read_utf8_invalid_encoding() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(1), Instruction::ArgumentGet(0), Instruction::ReadUtf8, @@ -662,7 +775,7 @@ mod tests { #[test] fn test_interpreter_read_utf8_stack_is_too_small() { - let interpreter: Interpreter = (&vec![ + let interpreter: Interpreter = (&vec![ Instruction::ArgumentGet(0), Instruction::ReadUtf8, // ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present. @@ -685,4 +798,172 @@ mod tests { ) ); } + + #[test] + fn test_interpreter_call() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(42), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_ok()); + + let stack = run.unwrap(); + + assert_eq!(stack.as_slice(), &[InterfaceValue::I32(12)]); + } + + #[test] + fn test_interpreter_call_invalid_local_import_index() { + let interpreter: Interpreter = + (&vec![Instruction::Call(42)]).try_into().unwrap(); + + let invocation_inputs = vec![]; + let instance = Instance { + ..Default::default() + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`call 42` cannot call the local or imported function `42` because it doesn't exist."#) + ); + } + + #[test] + fn test_interpreter_call_stack_is_too_small() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(0), + Instruction::Call(42), + // ^^ `42` expects 2 values on the stack, only one is present + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`call 42` cannot call the local or imported function `42` because there is no enough data on the stack for the arguments (needs 2)."#) + ); + } + + #[test] + fn test_interpreter_call_invalid_types_in_the_stack() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(42), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I64(4)]; + // ^^^ mismatch with `42` signature + let instance = Instance::new(); + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`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] + fn test_interpreter_call_failed_when_calling() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(42), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; + let 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() + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_err()); + + let error = run.unwrap_err(); + + assert_eq!( + error, + String::from(r#"`call 42` failed when calling the local or imported function `42`."#) + ); + } + + #[test] + fn test_interpreter_call_that_returns_nothing() { + let interpreter: Interpreter = (&vec![ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(42), + ]) + .try_into() + .unwrap(); + + let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; + let 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 function + }, + ); + + hashmap + }, + ..Default::default() + }; + let run = interpreter.run(&invocation_inputs, &instance); + + assert!(run.is_ok()); + + let stack = run.unwrap(); + + assert!(stack.is_empty()); + } } diff --git a/lib/interface-types/src/instructions/mod.rs b/lib/interface-types/src/instructions/mod.rs index 43e536c4b05..89b98f9ed41 100644 --- a/lib/interface-types/src/instructions/mod.rs +++ b/lib/interface-types/src/instructions/mod.rs @@ -7,7 +7,7 @@ pub mod wasm; #[derive(PartialEq, Debug)] pub enum Instruction<'input> { ArgumentGet(u64), - Call(u64), + Call(usize), CallExport(&'input str), ReadUtf8, WriteUtf8(&'input str), diff --git a/lib/interface-types/src/instructions/wasm.rs b/lib/interface-types/src/instructions/wasm.rs index 33a25ed0a5c..aa3090f1803 100644 --- a/lib/interface-types/src/instructions/wasm.rs +++ b/lib/interface-types/src/instructions/wasm.rs @@ -63,7 +63,11 @@ from_x_for_interface_value!(i64, I64); from_x_for_interface_value!(f32, F32); from_x_for_interface_value!(f64, F64); -pub trait ValueType: Copy + Sized {} +pub trait ValueType: Copy +where + Self: Sized, +{ +} macro_rules! value_type { ($native_type:ty) => { @@ -79,6 +83,42 @@ macro_rules! value_type { value_type!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64); +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; @@ -87,16 +127,26 @@ pub trait Export { fn call(&self, arguments: &[InterfaceValue]) -> Result, ()>; } +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, ()>; +} + pub trait Memory { fn view(&self) -> &[Cell]; } -pub trait Instance +pub trait Instance where E: Export, + LI: LocalImport, M: Memory, { fn export(&self, export_name: &str) -> Option<&E>; + fn local_or_import(&self, index: I) -> Option<&LI>; fn memory(&self, index: usize) -> Option<&M>; } @@ -122,15 +172,38 @@ impl Export for () { } } +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, ()> { + Err(()) + } +} + impl Memory for () { fn view(&self) -> &[Cell] { &[] } } -impl Instance for () +impl Instance for () where E: Export, + LI: LocalImport, M: Memory, { fn export(&self, _export_name: &str) -> Option<&E> { @@ -140,4 +213,8 @@ where fn memory(&self, _: usize) -> Option<&M> { None } + + fn local_or_import(&self, _index: I) -> Option<&LI> { + None + } } diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 9ba79d7b9f4..3760c923f9d 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -9,7 +9,7 @@ pub use decoders::binary::parse as parse_binary; #[cfg(test)] mod tests { - use crate::{ast::*, encoders::wat::*, instructions::Instruction, parse_binary}; + use crate::{ast::*, instructions::Instruction, parse_binary}; use std::fs; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core as runtime; From 8d75db9018e0756522a0bb52fd365611d4419cb7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 25 Sep 2019 23:13:26 +0200 Subject: [PATCH 29/78] test(interface-types) Use macros to reduce test boilerplate. --- .../src/instructions/interpreter.rs | 888 ++++++++---------- 1 file changed, 386 insertions(+), 502 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index e4494c85674..e0cc0f32a57 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -453,517 +453,401 @@ mod tests { assert_eq!(interpreter.executable_instructions.len(), 5); } - #[test] - fn test_interpreter_argument_get() { - let interpreter: Interpreter = - (&vec![Instruction::ArgumentGet(0)]).try_into().unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(42)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_ok()); - - let stack = run.unwrap(); - - assert_eq!(stack.as_slice(), &[InterfaceValue::I32(42)]); - } - - #[test] - fn test_interpreter_argument_get_invalid_index() { - let interpreter: Interpreter = - (&vec![Instruction::ArgumentGet(1)]).try_into().unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(42)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from("`arg.get 1` cannot access argument #1 because it doesn't exist.") - ); - } - - #[test] - fn test_interpreter_argument_get_argument_get() { - let interpreter: Interpreter = - (&vec![Instruction::ArgumentGet(0), Instruction::ArgumentGet(1)]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(7), InterfaceValue::I32(42)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_ok()); - - let stack = run.unwrap(); - - assert_eq!( - stack.as_slice(), - &[InterfaceValue::I32(7), InterfaceValue::I32(42)] - ); - } - - #[test] - fn test_interpreter_call_export() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_ok()); - - let stack = run.unwrap(); - - assert_eq!(stack.as_slice(), &[InterfaceValue::I32(7)]); - } - - #[test] - fn test_interpreter_call_export_invalid_export_name() { - let interpreter: Interpreter = - (&vec![Instruction::CallExport("bar")]).try_into().unwrap(); - - let invocation_inputs = vec![]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`call-export "bar"` cannot call the exported function `bar` because it doesn't exist."#) - ); - } - - #[test] - fn test_interpreter_call_export_stack_is_too_small() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), - // ^^^ `sum` expects 2 values on the stack, only one is present - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`call-export "sum"` cannot call the exported function `sum` because there is no enough data on the stack for the arguments (needs 2)."#) - ); - } - - #[test] - fn test_interpreter_call_export_invalid_types_in_the_stack() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I64(4)]; - // ^^^ mismatch with `sum` signature - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`call-export "sum"` cannot call the exported function `sum` because the value types on the stack mismatch the function signature (expects [I32, I32])."#) - ); - } - - #[test] - fn test_interpreter_call_export_failed_when_calling() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let instance = Instance { - exports: { - let mut hashmap = HashMap::new(); - hashmap.insert( - "sum".into(), - Export { - inputs: vec![InterfaceType::I32, InterfaceType::I32], - outputs: vec![InterfaceType::I32], - function: |_| Err(()), - // ^^^^^^^ function fails - }, - ); - - hashmap - }, - ..Default::default() - }; - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`call-export "sum"` failed when calling the exported function `sum`."#) - ); - } - - #[test] - fn test_interpreter_call_export_that_returns_nothing() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let instance = Instance { - exports: { - let mut hashmap = HashMap::new(); - hashmap.insert( - "sum".into(), - Export { - inputs: vec![InterfaceType::I32, InterfaceType::I32], - outputs: vec![InterfaceType::I32], - function: |_| Ok(vec![]), - // ^^^^^^^^^^ void function - }, - ); - - hashmap - }, - ..Default::default() - }; - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_ok()); - - let stack = run.unwrap(); - - assert!(stack.is_empty()); - } - - #[test] - fn test_interpreter_read_utf8() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::ReadUtf8, - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(13), InterfaceValue::I32(0)]; - // ^^^^^^^ length ^^^^^^ pointer - let instance = Instance { - memory: Memory::new( - "Hello, World!" - .as_bytes() - .iter() - .map(|u| Cell::new(*u)) - .collect(), - ), - ..Default::default() - }; - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_ok()); - - let stack = run.unwrap(); - - assert_eq!( - stack.as_slice(), - &[InterfaceValue::String("Hello, World!".into())] - ); - } - - #[test] - fn test_interpreter_read_utf8_out_of_memory() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::ReadUtf8, - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(13), InterfaceValue::I32(0)]; - // ^^^^^^^ length ^^^^^^ pointer - // is too long - let instance = Instance { - memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), - ..Default::default() - }; - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from( - r#"`read-utf8` failed because it has to read out of the memory bounds (index 13 > memory length 6)."# - ) - ); - } - - #[test] - fn test_interpreter_read_utf8_invalid_encoding() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::ReadUtf8, - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(4), InterfaceValue::I32(0)]; - // ^^^^^^ length ^^^^^^ pointer - let instance = Instance { - memory: Memory::new( - vec![0, 159, 146, 150] - .iter() - .map(|b| Cell::new(*b)) - .collect::>>(), - ), - ..Default::default() + macro_rules! test { + ( + $test_name:ident = + instructions: [ $($instructions:expr),* $(,)* ], + invocation_inputs: [ $($invocation_inputs:expr),* $(,)* ], + instance: $instance:expr, + stack: [ $($stack:expr),* $(,)* ] + $(,)* + ) => { + #[test] + #[allow(non_snake_case)] + fn $test_name() { + let interpreter: Interpreter = + (&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),*]); + } }; - let run = interpreter.run(&invocation_inputs, &instance); - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`read-utf8` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#) - ); - } - - #[test] - fn test_interpreter_read_utf8_stack_is_too_small() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(0), - Instruction::ReadUtf8, - // ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present. - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from( - r#"`read-utf8` failed because there is no enough data on the stack (needs 2)."# - ) - ); - } - - #[test] - fn test_interpreter_call() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_ok()); - - let stack = run.unwrap(); - - assert_eq!(stack.as_slice(), &[InterfaceValue::I32(12)]); - } - - #[test] - fn test_interpreter_call_invalid_local_import_index() { - let interpreter: Interpreter = - (&vec![Instruction::Call(42)]).try_into().unwrap(); - - let invocation_inputs = vec![]; - let instance = Instance { - ..Default::default() + ( + $test_name:ident = + instructions: [ $($instructions:expr),* $(,)* ], + invocation_inputs: [ $($invocation_inputs:expr),* $(,)* ], + instance: $instance:expr, + error: $error:expr + $(,)* + ) => { + #[test] + #[allow(non_snake_case)] + fn $test_name() { + let interpreter: Interpreter = + (&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)); + } }; - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`call 42` cannot call the local or imported function `42` because it doesn't exist."#) - ); } - #[test] - fn test_interpreter_call_stack_is_too_small() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(0), - Instruction::Call(42), - // ^^ `42` expects 2 values on the stack, only one is present - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`call 42` cannot call the local or imported function `42` because there is no enough data on the stack for the arguments (needs 2)."#) - ); - } - - #[test] - fn test_interpreter_call_invalid_types_in_the_stack() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I64(4)]; - // ^^^ mismatch with `42` signature - let instance = Instance::new(); - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`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!( + test_interpreter_argument_get = + instructions: [Instruction::ArgumentGet(0)], + invocation_inputs: [InterfaceValue::I32(42)], + instance: Instance::new(), + stack: [InterfaceValue::I32(42)], + ); + + test!( + test_interpreter_argument_get__twice = + instructions: [ + Instruction::ArgumentGet(0), + Instruction::ArgumentGet(1), + ], + invocation_inputs: [ + InterfaceValue::I32(7), + InterfaceValue::I32(42), + ], + instance: Instance::new(), + stack: [ + InterfaceValue::I32(7), + InterfaceValue::I32(42), + ], + ); + + test!( + test_interpreter_argument_get__invalid_index = + instructions: [Instruction::ArgumentGet(1)], + invocation_inputs: [InterfaceValue::I32(42)], + instance: Instance::new(), + error: "`arg.get 1` cannot access argument #1 because it doesn't exist." + ); + + test!( + test_interpreter_call_export = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("sum"), + ], + invocation_inputs: [ + InterfaceValue::I32(3), + InterfaceValue::I32(4), + ], + instance: Instance::new(), + stack: [InterfaceValue::I32(7)], + ); + + test!( + test_interpreter_call_export__invalid_export_name = + instructions: [Instruction::CallExport("bar")], + invocation_inputs: [], + instance: Instance::new(), + error: r#"`call-export "bar"` cannot call the exported function `bar` because it doesn't exist."#, + ); + + test!( + test_interpreter_call_export__stack_is_too_small = + instructions: [ + Instruction::ArgumentGet(0), + Instruction::CallExport("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 no enough data on the stack for the arguments (needs 2)."#, + ); + + test!( + test_interpreter_call_export__invalid_types_in_the_stack = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("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!( + test_interpreter_call_export__failure_when_calling = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("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 + }, + ); - #[test] - fn test_interpreter_call_failed_when_calling() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let 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 + hashmap + }, + ..Default::default() }, - ..Default::default() - }; - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_err()); - - let error = run.unwrap_err(); - - assert_eq!( - error, - String::from(r#"`call 42` failed when calling the local or imported function `42`."#) - ); - } + error: r#"`call-export "sum"` failed when calling the exported function `sum`."#, + ); + + test!( + test_interpreter_call_export__void = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::CallExport("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 + }, + ); - #[test] - fn test_interpreter_call_that_returns_nothing() { - let interpreter: Interpreter = (&vec![ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), - ]) - .try_into() - .unwrap(); - - let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; - let 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 function - }, - ); - - hashmap + hashmap + }, + ..Default::default() }, - ..Default::default() - }; - let run = interpreter.run(&invocation_inputs, &instance); - - assert!(run.is_ok()); + stack: [], + ); + + test!( + test_interpreter_read_utf8 = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(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!( + test_interpreter_read_utf8__read_out_of_memory = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(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!( + test_interpreter_read_utf8__invalid_encoding = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(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::>>()), + ..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!( + test_interpreter_read_utf8__stack_is_too_small = + instructions: [ + Instruction::ArgumentGet(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 no enough data on the stack (needs 2)."#, + ); + + test!( + test_interpreter_call = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(42), + ], + invocation_inputs: [ + InterfaceValue::I32(3), + InterfaceValue::I32(4), + ], + instance: Instance::new(), + stack: [InterfaceValue::I32(12)], + ); + + test!( + test_interpreter_call__invalid_local_import_index = + instructions: [ + Instruction::Call(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!( + test_interpreter_call__stack_is_too_small = + instructions: [ + Instruction::ArgumentGet(0), + Instruction::Call(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 no enough data on the stack for the arguments (needs 2)."#, + ); + + test!( + test_interpreter_call__invalid_types_in_the_stack = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(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!( + test_interpreter_call__failure_when_calling = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(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 + }, + ); - let stack = run.unwrap(); + hashmap + }, + ..Default::default() + }, + error: r#"`call 42` failed when calling the local or imported function `42`."#, + ); + + test!( + test_interpreter_call__void = + instructions: [ + Instruction::ArgumentGet(1), + Instruction::ArgumentGet(0), + Instruction::Call(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 + }, + ); - assert!(stack.is_empty()); - } + hashmap + }, + ..Default::default() + }, + stack: [], + ); } From ef568ca8c4110d019db0a141724d0c3d0e049bd5 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 25 Sep 2019 23:29:08 +0200 Subject: [PATCH 30/78] feat(interface-types) Update `Instruction`. --- lib/interface-types/src/decoders/binary.rs | 53 ++++++---- lib/interface-types/src/encoders/wat.rs | 49 ++++++---- .../src/instructions/interpreter.rs | 98 +++++++++---------- lib/interface-types/src/instructions/mod.rs | 8 +- lib/interface-types/src/lib.rs | 20 ++-- 5 files changed, 130 insertions(+), 98 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index cef85ffeda8..2b585dd76a5 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -133,24 +133,39 @@ fn instructions<'input, E: ParseError<&'input [u8]>>( Ok(match opcode { 0x00 => { consume!((input, argument_0) = leb(input)?); - (input, Instruction::ArgumentGet(argument_0)) + (input, Instruction::ArgumentGet { index: argument_0 }) } 0x01 => { consume!((input, argument_0) = leb(input)?); - (input, Instruction::Call(argument_0 as usize)) + ( + input, + Instruction::Call { + function_index: argument_0 as usize, + }, + ) } 0x02 => { consume!((input, argument_0) = string(input)?); - (input, Instruction::CallExport(argument_0)) + ( + input, + Instruction::CallExport { + export_name: argument_0, + }, + ) } 0x03 => (input, Instruction::ReadUtf8), 0x04 => { consume!((input, argument_0) = string(input)?); - (input, Instruction::WriteUtf8(argument_0)) + ( + input, + Instruction::WriteUtf8 { + allocator_name: argument_0, + }, + ) } 0x05 => { @@ -499,11 +514,11 @@ mod tests { fn test_instructions() { let input = &[ 0x14, // list of 20 items - 0x00, 0x01, // ArgumentGet(1) - 0x01, 0x01, // Call(1) - 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport("abc") + 0x00, 0x01, // ArgumentGet { index: 1 } + 0x01, 0x01, // Call { function_index: 1 } + 0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" } 0x03, // ReadUtf8 - 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8("abc") + 0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" } 0x05, 0xff, 0xff, 0x01, // AsWasm(Int) 0x06, 0x7e, // AsInterface(I64) 0x07, // TableRefAdd @@ -524,11 +539,13 @@ mod tests { let output = Ok(( &[0x0a][..], vec![ - Instruction::ArgumentGet(1), - Instruction::Call(1), - Instruction::CallExport("abc"), + Instruction::ArgumentGet { index: 1 }, + Instruction::Call { function_index: 1 }, + Instruction::CallExport { export_name: "abc" }, Instruction::ReadUtf8, - Instruction::WriteUtf8("abc"), + Instruction::WriteUtf8 { + allocator_name: "abc", + }, Instruction::AsWasm(InterfaceType::Int), Instruction::AsInterface(InterfaceType::I64), Instruction::TableRefAdd, @@ -667,7 +684,7 @@ mod tests { 0x01, // list of 1 item 0x7f, // I32 0x01, // list of 1 item - 0x00, 0x01, // ArgumentGet(1) + 0x00, 0x01, // ArgumentGet { index: 1 } 0x01, // adapter kind: export 0x01, // string of 1 byte 0x63, // "c" @@ -676,7 +693,7 @@ mod tests { 0x01, // list of 1 item 0x7f, // I32 0x01, // list of 1 item - 0x00, 0x01, // ArgumentGet(1) + 0x00, 0x01, // ArgumentGet { index: 1 } 0x02, // adapter kind: helper function 0x01, // string of 1 byte 0x64, // "d" @@ -685,7 +702,7 @@ mod tests { 0x01, // list of 1 item 0x7f, // I32 0x01, // list of 1 item - 0x00, 0x01, // ArgumentGet(1) + 0x00, 0x01, // ArgumentGet { index: 1 } ]; let output = Ok(( &[] as &[u8], @@ -695,19 +712,19 @@ mod tests { name: "b", input_types: vec![InterfaceType::I32], output_types: vec![InterfaceType::I32], - instructions: vec![Instruction::ArgumentGet(1)], + instructions: vec![Instruction::ArgumentGet { index: 1 }], }, Adapter::Export { name: "c", input_types: vec![InterfaceType::I32], output_types: vec![InterfaceType::I32], - instructions: vec![Instruction::ArgumentGet(1)], + instructions: vec![Instruction::ArgumentGet { index: 1 }], }, Adapter::HelperFunction { name: "d", input_types: vec![InterfaceType::I32], output_types: vec![InterfaceType::I32], - instructions: vec![Instruction::ArgumentGet(1)], + instructions: vec![Instruction::ArgumentGet { index: 1 }], }, ], )); diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index f04712a5c11..65684d885d2 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -23,11 +23,13 @@ impl From<&InterfaceType> for String { impl<'input> From<&Instruction<'input>> for String { fn from(instruction: &Instruction) -> Self { match instruction { - Instruction::ArgumentGet(index) => format!("arg.get {}", index), - Instruction::Call(index) => format!("call {}", index), - Instruction::CallExport(export_name) => format!(r#"call-export "{}""#, export_name), + 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::ReadUtf8 => "read-utf8".into(), - Instruction::WriteUtf8(string) => format!(r#"write-utf8 "{}""#, string), + Instruction::WriteUtf8 { allocator_name } => { + format!(r#"write-utf8 "{}""#, allocator_name) + } Instruction::AsWasm(interface_type) => { format!("as-wasm {}", String::from(interface_type)) } @@ -294,11 +296,14 @@ mod tests { #[test] fn test_instructions() { let inputs: Vec = vec![ - (&Instruction::ArgumentGet(7)).into(), - (&Instruction::Call(7)).into(), - (&Instruction::CallExport("foo")).into(), + (&Instruction::ArgumentGet { index: 7 }).into(), + (&Instruction::Call { function_index: 7 }).into(), + (&Instruction::CallExport { export_name: "foo" }).into(), (&Instruction::ReadUtf8).into(), - (&Instruction::WriteUtf8("foo")).into(), + (&Instruction::WriteUtf8 { + allocator_name: "foo", + }) + .into(), (&Instruction::AsWasm(InterfaceType::Int)).into(), (&Instruction::AsInterface(InterfaceType::AnyRef)).into(), (&Instruction::TableRefAdd).into(), @@ -438,9 +443,11 @@ mod tests { input_types: vec![InterfaceType::I32, InterfaceType::F32], output_types: vec![InterfaceType::I32], instructions: vec![ - Instruction::ArgumentGet(0), - Instruction::WriteUtf8("hello"), - Instruction::CallExport("f"), + Instruction::ArgumentGet { index: 0 }, + Instruction::WriteUtf8 { + allocator_name: "hello", + }, + Instruction::CallExport { export_name: "f" }, ], }) .into(), @@ -449,7 +456,7 @@ mod tests { name: "foo", input_types: vec![InterfaceType::I32], output_types: vec![], - instructions: vec![Instruction::CallExport("f")], + instructions: vec![Instruction::CallExport { export_name: "f" }], }) .into(), (&Adapter::Import { @@ -457,7 +464,7 @@ mod tests { name: "foo", input_types: vec![], output_types: vec![InterfaceType::I32], - instructions: vec![Instruction::CallExport("f")], + instructions: vec![Instruction::CallExport { export_name: "f" }], }) .into(), (&Adapter::Export { @@ -465,9 +472,11 @@ mod tests { input_types: vec![InterfaceType::I32, InterfaceType::F32], output_types: vec![InterfaceType::I32], instructions: vec![ - Instruction::ArgumentGet(0), - Instruction::WriteUtf8("hello"), - Instruction::CallExport("f"), + Instruction::ArgumentGet { index: 0 }, + Instruction::WriteUtf8 { + allocator_name: "hello", + }, + Instruction::CallExport { export_name: "f" }, ], }) .into(), @@ -475,14 +484,14 @@ mod tests { name: "foo", input_types: vec![InterfaceType::I32], output_types: vec![], - instructions: vec![Instruction::CallExport("f")], + instructions: vec![Instruction::CallExport { export_name: "f" }], }) .into(), (&Adapter::Export { name: "foo", input_types: vec![], output_types: vec![InterfaceType::I32], - instructions: vec![Instruction::CallExport("f")], + instructions: vec![Instruction::CallExport { export_name: "f" }], }) .into(), ]; @@ -560,13 +569,13 @@ mod tests { name: "foo", input_types: vec![InterfaceType::I32], output_types: vec![], - instructions: vec![Instruction::ArgumentGet(42)], + instructions: vec![Instruction::ArgumentGet { index: 42 }], }, Adapter::Export { name: "bar", input_types: vec![], output_types: vec![], - instructions: vec![Instruction::ArgumentGet(42)], + instructions: vec![Instruction::ArgumentGet { index: 42 }], }, ], forwards: vec![Forward { name: "main" }], diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index e0cc0f32a57..5950dcdb2a9 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -88,7 +88,7 @@ where .map( |instruction| -> ExecutableInstruction { match instruction { - Instruction::ArgumentGet(index) => { + Instruction::ArgumentGet { index } => { let index = index.to_owned(); let instruction_name: String = instruction.into(); @@ -107,7 +107,7 @@ where Ok(()) }) } - Instruction::CallExport(export_name) => { + Instruction::CallExport { export_name } => { let export_name = (*export_name).to_owned(); let instruction_name: String = instruction.into(); @@ -215,7 +215,7 @@ where } }) } - Instruction::Call(index) => { + Instruction::Call { function_index: index } => { let index = index.to_owned(); let instruction_name: String = instruction.into(); @@ -442,11 +442,11 @@ mod tests { #[test] fn test_interpreter_from_instructions() { let instructions = vec![ - Instruction::ArgumentGet(0), - Instruction::ArgumentGet(0), - Instruction::CallExport("foo"), + Instruction::ArgumentGet { index: 0 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::CallExport { export_name: "foo" }, Instruction::ReadUtf8, - Instruction::Call(7), + Instruction::Call { function_index: 7 }, ]; let interpreter: Interpreter<(), (), (), ()> = (&instructions).try_into().unwrap(); @@ -509,7 +509,7 @@ mod tests { test!( test_interpreter_argument_get = - instructions: [Instruction::ArgumentGet(0)], + instructions: [Instruction::ArgumentGet { index: 0 }], invocation_inputs: [InterfaceValue::I32(42)], instance: Instance::new(), stack: [InterfaceValue::I32(42)], @@ -518,8 +518,8 @@ mod tests { test!( test_interpreter_argument_get__twice = instructions: [ - Instruction::ArgumentGet(0), - Instruction::ArgumentGet(1), + Instruction::ArgumentGet { index: 0 }, + Instruction::ArgumentGet { index: 1 }, ], invocation_inputs: [ InterfaceValue::I32(7), @@ -534,7 +534,7 @@ mod tests { test!( test_interpreter_argument_get__invalid_index = - instructions: [Instruction::ArgumentGet(1)], + 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." @@ -543,9 +543,9 @@ mod tests { test!( test_interpreter_call_export = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::CallExport { export_name: "sum" }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -557,7 +557,7 @@ mod tests { test!( test_interpreter_call_export__invalid_export_name = - instructions: [Instruction::CallExport("bar")], + 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."#, @@ -566,8 +566,8 @@ mod tests { test!( test_interpreter_call_export__stack_is_too_small = instructions: [ - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), + Instruction::ArgumentGet { index: 0 }, + Instruction::CallExport { export_name: "sum" }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -580,9 +580,9 @@ mod tests { test!( test_interpreter_call_export__invalid_types_in_the_stack = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::CallExport { export_name: "sum" }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -596,9 +596,9 @@ mod tests { test!( test_interpreter_call_export__failure_when_calling = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::CallExport { export_name: "sum" }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -627,9 +627,9 @@ mod tests { test!( test_interpreter_call_export__void = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::CallExport("sum"), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::CallExport { export_name: "sum" }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -658,8 +658,8 @@ mod tests { test!( test_interpreter_read_utf8 = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, Instruction::ReadUtf8, ], invocation_inputs: [ @@ -678,8 +678,8 @@ mod tests { test!( test_interpreter_read_utf8__read_out_of_memory = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, Instruction::ReadUtf8, ], invocation_inputs: [ @@ -698,8 +698,8 @@ mod tests { test!( test_interpreter_read_utf8__invalid_encoding = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, Instruction::ReadUtf8, ], invocation_inputs: [ @@ -718,7 +718,7 @@ mod tests { test!( test_interpreter_read_utf8__stack_is_too_small = instructions: [ - Instruction::ArgumentGet(0), + Instruction::ArgumentGet { index: 0 }, Instruction::ReadUtf8, // ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present. ], @@ -733,9 +733,9 @@ mod tests { test!( test_interpreter_call = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::Call { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -748,7 +748,7 @@ mod tests { test!( test_interpreter_call__invalid_local_import_index = instructions: [ - Instruction::Call(42), + Instruction::Call { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -761,9 +761,9 @@ mod tests { test!( test_interpreter_call__stack_is_too_small = instructions: [ - Instruction::ArgumentGet(0), - Instruction::Call(42), - // ^^ `42` expects 2 values on the stack, only one is present + 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), @@ -776,9 +776,9 @@ mod tests { test!( test_interpreter_call__invalid_types_in_the_stack = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::Call { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -792,9 +792,9 @@ mod tests { test!( test_interpreter_call__failure_when_calling = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::Call { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), @@ -823,9 +823,9 @@ mod tests { test!( test_interpreter_call__void = instructions: [ - Instruction::ArgumentGet(1), - Instruction::ArgumentGet(0), - Instruction::Call(42), + Instruction::ArgumentGet { index: 1 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::Call { function_index: 42 }, ], invocation_inputs: [ InterfaceValue::I32(3), diff --git a/lib/interface-types/src/instructions/mod.rs b/lib/interface-types/src/instructions/mod.rs index 89b98f9ed41..173e4af406e 100644 --- a/lib/interface-types/src/instructions/mod.rs +++ b/lib/interface-types/src/instructions/mod.rs @@ -6,11 +6,11 @@ pub mod wasm; #[derive(PartialEq, Debug)] pub enum Instruction<'input> { - ArgumentGet(u64), - Call(usize), - CallExport(&'input str), + ArgumentGet { index: u64 }, + Call { function_index: usize }, + CallExport { export_name: &'input str }, ReadUtf8, - WriteUtf8(&'input str), + WriteUtf8 { allocator_name: &'input str }, AsWasm(InterfaceType), AsInterface(InterfaceType), TableRefAdd, diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 3760c923f9d..748575ceabc 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -82,11 +82,13 @@ mod tests { input_types: vec![InterfaceType::I32], output_types: vec![], instructions: vec![ - Instruction::ArgumentGet(0), - Instruction::ArgumentGet(0), - Instruction::CallExport("strlen"), + Instruction::ArgumentGet { index: 0 }, + Instruction::ArgumentGet { index: 0 }, + Instruction::CallExport { + export_name: "strlen" + }, Instruction::ReadUtf8, - Instruction::Call(0), + Instruction::Call { function_index: 0 }, ] }, Adapter::Import { @@ -95,9 +97,13 @@ mod tests { input_types: vec![], output_types: vec![InterfaceType::I32], instructions: vec![ - Instruction::Call(1), - Instruction::WriteUtf8("alloc"), - Instruction::CallExport("write_null_byte"), + Instruction::Call { function_index: 1 }, + Instruction::WriteUtf8 { + allocator_name: "alloc" + }, + Instruction::CallExport { + export_name: "write_null_byte" + }, ] } ], From 981692ec15cd946786aba4f3dd361e18743bd1dc Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 25 Sep 2019 23:30:41 +0200 Subject: [PATCH 31/78] chore(interface-types) Re-order match arms. --- .../src/instructions/interpreter.rs | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 5950dcdb2a9..9781d48aaae 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -107,6 +107,65 @@ where Ok(()) }) } + Instruction::Call { function_index: index } => { + let index = index.to_owned(); + let instruction_name: String = instruction.into(); + + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + let instance = runtime.wasm_instance; + let function_index = FunctionIndex::new(index); + + match instance.local_or_import(function_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::>(); + + 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, + 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, + index + )) + } + } + None => Err(format!( + "`{}` cannot call the local or imported function `{}` because there is no enough data on the stack for the arguments (needs {}).", + instruction_name, + index, + inputs_cardinality, + )) + } + } + None => Err(format!( + "`{}` cannot call the local or imported function `{}` because it doesn't exist.", + instruction_name, + index, + )) + } + }) + } Instruction::CallExport { export_name } => { let export_name = (*export_name).to_owned(); let instruction_name: String = instruction.into(); @@ -215,65 +274,6 @@ where } }) } - Instruction::Call { function_index: index } => { - let index = index.to_owned(); - let instruction_name: String = instruction.into(); - - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { - let instance = runtime.wasm_instance; - let function_index = FunctionIndex::new(index); - - match instance.local_or_import(function_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::>(); - - 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, - 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, - index - )) - } - } - None => Err(format!( - "`{}` cannot call the local or imported function `{}` because there is no enough data on the stack for the arguments (needs {}).", - instruction_name, - index, - inputs_cardinality, - )) - } - } - None => Err(format!( - "`{}` cannot call the local or imported function `{}` because it doesn't exist.", - instruction_name, - index, - )) - } - }) - } _ => unimplemented!(), } }, From 49a7587f335c36774af48a524c949bdecc1532ca Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 00:55:26 +0200 Subject: [PATCH 32/78] feat(interface-types) Implement the `write-utf8` executable instruction. --- .../src/instructions/interpreter.rs | 196 +++++++++++++++++- lib/interface-types/src/instructions/wasm.rs | 5 +- 2 files changed, 191 insertions(+), 10 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 9781d48aaae..0c8a55fef86 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -3,7 +3,11 @@ use crate::instructions::{ wasm::{self, FunctionIndex, InterfaceType, InterfaceValue, TypedIndex}, Instruction, }; -use std::{cell::Cell, convert::TryFrom, marker::PhantomData}; +use std::{ + cell::Cell, + convert::{TryFrom, TryInto}, + marker::PhantomData, +}; struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory> where @@ -15,9 +19,9 @@ where invocation_inputs: &'invocation [InterfaceValue], stack: Stack, wasm_instance: &'instance Instance, - wasm_exports: PhantomData, - wasm_locals_or_imports: PhantomData, - wasm_memories: PhantomData, + _wasm_exports: PhantomData, + _wasm_locals_or_imports: PhantomData, + _wasm_memories: PhantomData, } type ExecutableInstruction = @@ -56,9 +60,9 @@ where invocation_inputs, stack: Stack::new(), wasm_instance, - wasm_exports: PhantomData, - wasm_locals_or_imports: PhantomData, - wasm_memories: PhantomData, + _wasm_exports: PhantomData, + _wasm_locals_or_imports: PhantomData, + _wasm_memories: PhantomData, }; for executable_instruction in self.iter() { @@ -274,6 +278,76 @@ where } }) } + Instruction::WriteUtf8 { allocator_name } => { + let allocator_name = (*allocator_name).to_owned(); + let instruction_name: String = instruction.into(); + + Box::new(move |runtime: &mut Runtime| -> Result<(), _> { + 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::(); + + 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 no 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 + )) + } + }) + } _ => unimplemented!(), } }, @@ -396,6 +470,18 @@ mod tests { }, }, ); + 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 }, @@ -417,7 +503,7 @@ mod tests { hashmap }, - memory: Memory::new(vec![]), + memory: Memory::new(vec![Cell::new(0); 128]), } } } @@ -850,4 +936,98 @@ mod tests { }, stack: [], ); + + test!( + test_interpreter_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!( + test_interpreter_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!( + test_interpreter_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!( + test_interpreter_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 no enough data on the stack for the arguments (needs 1)."#, + ); + + test!( + test_interpreter_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!( + test_interpreter_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])."#, + ); } diff --git a/lib/interface-types/src/instructions/wasm.rs b/lib/interface-types/src/instructions/wasm.rs index aa3090f1803..014b95c5c2e 100644 --- a/lib/interface-types/src/instructions/wasm.rs +++ b/lib/interface-types/src/instructions/wasm.rs @@ -49,8 +49,8 @@ macro_rules! from_x_for_interface_value { type Error = &'static str; fn try_from(w: &InterfaceValue) -> Result { - match *w { - InterfaceValue::$value_variant(n) => Ok(n), + match w { + InterfaceValue::$value_variant(n) => Ok(n.clone()), _ => Err("Invalid cast."), } } @@ -58,6 +58,7 @@ macro_rules! from_x_for_interface_value { }; } +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 5ce18fc447f132de067d6469991f00aa91fd3ec4 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 01:00:17 +0200 Subject: [PATCH 33/78] feat(interface-types) Create vectors with specific capacity when possible. --- lib/interface-types/src/decoders/binary.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 2b585dd76a5..0c77e52694e 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -100,7 +100,7 @@ fn list<'input, I, E: ParseError<&'input [u8]>>( return Err(Err::Error(make_error(input, ErrorKind::Eof))); } - let mut items = vec![]; + let mut items = Vec::with_capacity(length as usize); for _ in 0..length { consume!((input, item) = item_parser(input)?); @@ -247,10 +247,11 @@ fn exports<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - let mut exports = vec![]; consume!((input, number_of_exports) = leb(input)?); + let mut exports = Vec::with_capacity(number_of_exports as usize); + for _ in 0..number_of_exports { consume!((input, export_name) = string(input)?); consume!((input, export_input_types) = list(input, ty)?); @@ -270,10 +271,11 @@ fn types<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - let mut types = vec![]; consume!((input, number_of_types) = leb(input)?); + let mut types = Vec::with_capacity(number_of_types as usize); + for _ in 0..number_of_types { consume!((input, type_name) = string(input)?); consume!((input, type_fields) = list(input, string)?); @@ -293,10 +295,11 @@ fn imported_functions<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - let mut imported_functions = vec![]; consume!((input, number_of_imported_functions) = leb(input)?); + let mut imported_functions = Vec::with_capacity(number_of_imported_functions as usize); + for _ in 0..number_of_imported_functions { consume!((input, imported_function_namespace) = string(input)?); consume!((input, imported_function_name) = string(input)?); @@ -318,10 +321,11 @@ fn adapters<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - let mut adapters = vec![]; consume!((input, number_of_adapters) = leb(input)?); + let mut adapters = Vec::with_capacity(number_of_adapters as usize); + for _ in 0..number_of_adapters { consume!((input, adapter_kind) = byte(input)?); let adapter_kind = AdapterKind::try_from(adapter_kind) @@ -381,10 +385,11 @@ fn forwards<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - let mut forwards = vec![]; consume!((input, number_of_forwards) = leb(input)?); + let mut forwards = Vec::with_capacity(number_of_forwards as usize); + for _ in 0..number_of_forwards { consume!((input, forward_name) = string(input)?); From ade098b81544b38fed599491225e23078e286732 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 01:02:29 +0200 Subject: [PATCH 34/78] fix(interface-types) Fix typos in error messages. --- .../src/instructions/interpreter.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs index 0c8a55fef86..79466d7f955 100644 --- a/lib/interface-types/src/instructions/interpreter.rs +++ b/lib/interface-types/src/instructions/interpreter.rs @@ -155,7 +155,7 @@ where } } None => Err(format!( - "`{}` cannot call the local or imported function `{}` because there is no enough data on the stack for the arguments (needs {}).", + "`{}` cannot call the local or imported function `{}` because there is not enough data on the stack for the arguments (needs {}).", instruction_name, index, inputs_cardinality, @@ -213,7 +213,7 @@ where } } None => Err(format!( - "`{}` cannot call the exported function `{}` because there is no enough data on the stack for the arguments (needs {}).", + "`{}` cannot call the exported function `{}` because there is not enough data on the stack for the arguments (needs {}).", instruction_name, export_name, inputs_cardinality, @@ -272,7 +272,7 @@ where )) } None => Err(format!( - "`{}` failed because there is no enough data on the stack (needs 2).", + "`{}` failed because there is not enough data on the stack (needs 2).", instruction_name, )) } @@ -328,7 +328,7 @@ where } } None => Err(format!( - "`{}` cannot call the allocator `{}` because there is no enough data on the stack for the arguments (needs {}).", + "`{}` cannot call the allocator `{}` because there is not enough data on the stack for the arguments (needs {}).", instruction_name, allocator_name, 1 @@ -660,7 +660,7 @@ mod tests { InterfaceValue::I32(4), ], instance: Instance::new(), - error: r#"`call-export "sum"` cannot call the exported function `sum` because there is no enough data on the stack for the arguments (needs 2)."#, + 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!( @@ -813,7 +813,7 @@ mod tests { InterfaceValue::I32(0), ], instance: Instance::new(), - error: r#"`read-utf8` failed because there is no enough data on the stack (needs 2)."#, + error: r#"`read-utf8` failed because there is not enough data on the stack (needs 2)."#, ); test!( @@ -856,7 +856,7 @@ mod tests { InterfaceValue::I32(4), ], instance: Instance::new(), - error: r#"`call 42` cannot call the local or imported function `42` because there is no enough data on the stack for the arguments (needs 2)."#, + 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!( @@ -981,7 +981,7 @@ mod tests { ], invocation_inputs: [InterfaceValue::String("Hello, World!".into())], instance: Instance::new(), - error: r#"`write-utf8 "alloc"` cannot call the allocator `alloc` because there is no enough data on the stack for the arguments (needs 1)."#, + 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!( From fce270a8e74da51b88d146b077d2b1b2bd506d25 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 14:14:46 +0200 Subject: [PATCH 35/78] feat(interface-types) Split the interpreter into multiple modules/files. --- lib/interface-types/src/ast.rs | 2 +- lib/interface-types/src/decoders/binary.rs | 2 +- lib/interface-types/src/encoders/wat.rs | 4 +- .../src/instructions/interpreter.rs | 1033 ----------------- .../mod.rs => interpreter/instruction.rs} | 4 - .../interpreter/instructions/argument_get.rs | 54 + .../src/interpreter/instructions/call.rs | 187 +++ .../interpreter/instructions/call_export.rs | 177 +++ .../src/interpreter/instructions/mod.rs | 177 +++ .../src/interpreter/instructions/read_utf8.rs | 131 +++ .../interpreter/instructions/write_utf8.rs | 169 +++ lib/interface-types/src/interpreter/mod.rs | 143 +++ .../{instructions => interpreter}/stack.rs | 0 .../src/interpreter/wasm/mod.rs | 2 + .../wasm/structures.rs} | 87 +- .../src/interpreter/wasm/values.rs | 85 ++ lib/interface-types/src/lib.rs | 4 +- lib/interface-types/src/macros.rs | 89 ++ 18 files changed, 1222 insertions(+), 1128 deletions(-) delete mode 100644 lib/interface-types/src/instructions/interpreter.rs rename lib/interface-types/src/{instructions/mod.rs => interpreter/instruction.rs} (93%) create mode 100644 lib/interface-types/src/interpreter/instructions/argument_get.rs create mode 100644 lib/interface-types/src/interpreter/instructions/call.rs create mode 100644 lib/interface-types/src/interpreter/instructions/call_export.rs create mode 100644 lib/interface-types/src/interpreter/instructions/mod.rs create mode 100644 lib/interface-types/src/interpreter/instructions/read_utf8.rs create mode 100644 lib/interface-types/src/interpreter/instructions/write_utf8.rs create mode 100644 lib/interface-types/src/interpreter/mod.rs rename lib/interface-types/src/{instructions => interpreter}/stack.rs (100%) create mode 100644 lib/interface-types/src/interpreter/wasm/mod.rs rename lib/interface-types/src/{instructions/wasm.rs => interpreter/wasm/structures.rs} (58%) create mode 100644 lib/interface-types/src/interpreter/wasm/values.rs diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index a3df0d8d8b7..2bcabfb5548 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -1,4 +1,4 @@ -use crate::instructions::Instruction; +use crate::interpreter::Instruction; use std::str; #[derive(PartialEq, Debug)] diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 0c77e52694e..9a1d77e47e1 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -1,4 +1,4 @@ -use crate::{ast::*, instructions::Instruction}; +use crate::{ast::*, interpreter::Instruction}; use nom::{ error::{make_error, ErrorKind, ParseError}, Err, IResult, diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index 65684d885d2..89387ed4fea 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -1,6 +1,6 @@ use crate::{ ast::{Adapter, Export, Forward, ImportedFunction, InterfaceType, Interfaces, Type}, - instructions::Instruction, + interpreter::Instruction, }; impl From<&InterfaceType> for String { @@ -270,7 +270,7 @@ impl<'input> From<&Interfaces<'input>> for String { #[cfg(test)] mod tests { - use crate::{ast::*, instructions::Instruction}; + use crate::{ast::*, interpreter::Instruction}; #[test] fn test_interface_types() { diff --git a/lib/interface-types/src/instructions/interpreter.rs b/lib/interface-types/src/instructions/interpreter.rs deleted file mode 100644 index 79466d7f955..00000000000 --- a/lib/interface-types/src/instructions/interpreter.rs +++ /dev/null @@ -1,1033 +0,0 @@ -use crate::instructions::{ - stack::{Stack, Stackable}, - wasm::{self, FunctionIndex, InterfaceType, InterfaceValue, TypedIndex}, - Instruction, -}; -use std::{ - cell::Cell, - convert::{TryFrom, TryInto}, - marker::PhantomData, -}; - -struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory> -where - Export: wasm::Export + 'instance, - LocalImport: wasm::LocalImport + 'instance, - Memory: wasm::Memory + 'instance, - Instance: wasm::Instance + 'instance, -{ - invocation_inputs: &'invocation [InterfaceValue], - stack: Stack, - wasm_instance: &'instance Instance, - _wasm_exports: PhantomData, - _wasm_locals_or_imports: PhantomData, - _wasm_memories: PhantomData, -} - -type ExecutableInstruction = - Box) -> Result<(), String>>; - -pub struct Interpreter -where - Export: wasm::Export, - LocalImport: wasm::LocalImport, - Memory: wasm::Memory, - Instance: wasm::Instance, -{ - executable_instructions: Vec>, -} - -impl Interpreter -where - Export: wasm::Export, - LocalImport: wasm::LocalImport, - Memory: wasm::Memory, - Instance: wasm::Instance, -{ - fn iter( - &self, - ) -> impl Iterator> + '_ - { - self.executable_instructions.iter() - } - - pub fn run( - &self, - invocation_inputs: &[InterfaceValue], - wasm_instance: &Instance, - ) -> Result, 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>> - for Interpreter -where - Export: wasm::Export, - LocalImport: wasm::LocalImport, - Memory: wasm::Memory, - Instance: wasm::Instance, -{ - type Error = String; - - fn try_from(instructions: &Vec) -> Result { - let executable_instructions = instructions - .iter() - .map( - |instruction| -> ExecutableInstruction { - match instruction { - Instruction::ArgumentGet { index } => { - let index = index.to_owned(); - let instruction_name: String = instruction.into(); - - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { - 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(()) - }) - } - Instruction::Call { function_index: index } => { - let index = index.to_owned(); - let instruction_name: String = instruction.into(); - - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { - let instance = runtime.wasm_instance; - let function_index = FunctionIndex::new(index); - - match instance.local_or_import(function_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::>(); - - 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, - 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, - 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, - index, - inputs_cardinality, - )) - } - } - None => Err(format!( - "`{}` cannot call the local or imported function `{}` because it doesn't exist.", - instruction_name, - index, - )) - } - }) - } - Instruction::CallExport { export_name } => { - let export_name = (*export_name).to_owned(); - let instruction_name: String = instruction.into(); - - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { - 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::>(); - - 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, - )) - } - }) - } - Instruction::ReadUtf8 => { - let instruction_name: String = instruction.into(); - - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { - match runtime.stack.pop(2) { - Some(inputs) => match runtime.wasm_instance.memory(0) { - Some(memory) => { - let length = i32::try_from(&inputs[0])? as usize; - let pointer = i32::try_from(&inputs[1])? as usize; - let memory_view = memory.view::(); - - 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 = (&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, - )) - } - }) - } - Instruction::WriteUtf8 { allocator_name } => { - let allocator_name = (*allocator_name).to_owned(); - let instruction_name: String = instruction.into(); - - Box::new(move |runtime: &mut Runtime| -> Result<(), _> { - 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::(); - - 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 - )) - } - }) - } - _ => unimplemented!(), - } - }, - ) - .collect(); - - Ok(Interpreter { - executable_instructions, - }) - } -} - -#[cfg(test)] -mod tests { - use super::Interpreter; - use crate::instructions::{ - stack::Stackable, - wasm::{self, InterfaceType, InterfaceValue}, - Instruction, - }; - use std::{cell::Cell, collections::HashMap, convert::TryInto}; - - struct Export { - inputs: Vec, - outputs: Vec, - function: fn(arguments: &[InterfaceValue]) -> Result, ()>, - } - - impl wasm::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, ()> { - (self.function)(arguments) - } - } - - struct LocalImport { - inputs: Vec, - outputs: Vec, - function: fn(arguments: &[InterfaceValue]) -> Result, ()>, - } - - impl wasm::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, ()> { - (self.function)(arguments) - } - } - - #[derive(Default)] - struct Memory { - data: Vec>, - } - - impl Memory { - fn new(data: Vec>) -> Self { - Self { data } - } - } - - impl wasm::Memory for Memory { - fn view(&self) -> &[Cell] { - let slice = self.data.as_slice(); - - unsafe { ::std::slice::from_raw_parts(slice.as_ptr() as *const Cell, slice.len()) } - } - } - - #[derive(Default)] - struct Instance { - exports: HashMap, - locals_or_imports: HashMap, - memory: Memory, - } - - impl Instance { - 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::Instance for Instance { - fn export(&self, export_name: &str) -> Option<&Export> { - self.exports.get(export_name) - } - - fn local_or_import( - &self, - index: I, - ) -> Option<&LocalImport> { - self.locals_or_imports.get(&index.index()) - } - - fn memory(&self, _index: usize) -> Option<&Memory> { - Some(&self.memory) - } - } - - #[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); - } - - macro_rules! test { - ( - $test_name:ident = - instructions: [ $($instructions:expr),* $(,)* ], - invocation_inputs: [ $($invocation_inputs:expr),* $(,)* ], - instance: $instance:expr, - stack: [ $($stack:expr),* $(,)* ] - $(,)* - ) => { - #[test] - #[allow(non_snake_case)] - fn $test_name() { - let interpreter: Interpreter = - (&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)] - fn $test_name() { - let interpreter: Interpreter = - (&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)); - } - }; - } - - test!( - test_interpreter_argument_get = - instructions: [Instruction::ArgumentGet { index: 0 }], - invocation_inputs: [InterfaceValue::I32(42)], - instance: Instance::new(), - stack: [InterfaceValue::I32(42)], - ); - - test!( - test_interpreter_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!( - test_interpreter_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." - ); - - test!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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: [], - ); - - test!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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::>>()), - ..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!( - test_interpreter_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)."#, - ); - - test!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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: [], - ); - - test!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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!( - test_interpreter_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])."#, - ); -} diff --git a/lib/interface-types/src/instructions/mod.rs b/lib/interface-types/src/interpreter/instruction.rs similarity index 93% rename from lib/interface-types/src/instructions/mod.rs rename to lib/interface-types/src/interpreter/instruction.rs index 173e4af406e..e474d2daf36 100644 --- a/lib/interface-types/src/instructions/mod.rs +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -1,9 +1,5 @@ use crate::ast::InterfaceType; -pub mod interpreter; -mod stack; -pub mod wasm; - #[derive(PartialEq, Debug)] pub enum Instruction<'input> { ArgumentGet { index: u64 }, diff --git a/lib/interface-types/src/interpreter/instructions/argument_get.rs b/lib/interface-types/src/interpreter/instructions/argument_get.rs new file mode 100644 index 00000000000..881930297e7 --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/argument_get.rs @@ -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." + ); +} diff --git a/lib/interface-types/src/interpreter/instructions/call.rs b/lib/interface-types/src/interpreter/instructions/call.rs new file mode 100644 index 00000000000..4a74655c3a4 --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/call.rs @@ -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::>(); + + 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: [], + ); +} diff --git a/lib/interface-types/src/interpreter/instructions/call_export.rs b/lib/interface-types/src/interpreter/instructions/call_export.rs new file mode 100644 index 00000000000..05de6470bd5 --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/call_export.rs @@ -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::>(); + + 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/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs new file mode 100644 index 00000000000..05c3f9723fd --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/mod.rs @@ -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, + pub(crate) outputs: Vec, + pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result, ()>, + } + + 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, ()> { + (self.function)(arguments) + } + } + + pub(crate) struct LocalImport { + pub(crate) inputs: Vec, + pub(crate) outputs: Vec, + pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result, ()>, + } + + 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, ()> { + (self.function)(arguments) + } + } + + #[derive(Default)] + pub(crate) struct Memory { + pub(crate) data: Vec>, + } + + impl Memory { + pub(crate) fn new(data: Vec>) -> Self { + Self { data } + } + } + + impl wasm::structures::Memory for Memory { + fn view(&self) -> &[Cell] { + use std::slice; + + let slice = self.data.as_slice(); + + unsafe { slice::from_raw_parts(slice.as_ptr() as *const Cell, slice.len()) } + } + } + + #[derive(Default)] + pub(crate) struct Instance { + pub(crate) exports: HashMap, + pub(crate) locals_or_imports: HashMap, + 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 for Instance { + fn export(&self, export_name: &str) -> Option<&Export> { + self.exports.get(export_name) + } + + fn local_or_import( + &self, + index: I, + ) -> Option<&LocalImport> { + self.locals_or_imports.get(&index.index()) + } + + fn memory(&self, _index: usize) -> Option<&Memory> { + Some(&self.memory) + } + } +} diff --git a/lib/interface-types/src/interpreter/instructions/read_utf8.rs b/lib/interface-types/src/interpreter/instructions/read_utf8.rs new file mode 100644 index 00000000000..8f195fd998d --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/read_utf8.rs @@ -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::(); + + 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 = (&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::>>()), + ..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)."#, + ); +} diff --git a/lib/interface-types/src/interpreter/instructions/write_utf8.rs b/lib/interface-types/src/interpreter/instructions/write_utf8.rs new file mode 100644 index 00000000000..fa66621b2e4 --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/write_utf8.rs @@ -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::(); + + 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])."#, + ); +} diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs new file mode 100644 index 00000000000..e8b45bd579b --- /dev/null +++ b/lib/interface-types/src/interpreter/mod.rs @@ -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 + 'instance, +{ + invocation_inputs: &'invocation [InterfaceValue], + stack: Stack, + wasm_instance: &'instance Instance, + _wasm_exports: PhantomData, + _wasm_locals_or_imports: PhantomData, + _wasm_memories: PhantomData, +} + +pub(crate) type ExecutableInstruction = + Box) -> Result<(), String>>; + +pub struct Interpreter +where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + Instance: wasm::structures::Instance, +{ + executable_instructions: Vec>, +} + +impl Interpreter +where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + Instance: wasm::structures::Instance, +{ + fn iter( + &self, + ) -> impl Iterator> + '_ + { + self.executable_instructions.iter() + } + + pub fn run( + &self, + invocation_inputs: &[InterfaceValue], + wasm_instance: &Instance, + ) -> Result, 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>> + for Interpreter +where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + Instance: wasm::structures::Instance, +{ + type Error = String; + + fn try_from(instructions: &Vec) -> Result { + let executable_instructions = instructions + .iter() + .map( + |instruction| -> ExecutableInstruction { + 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); + } +} diff --git a/lib/interface-types/src/instructions/stack.rs b/lib/interface-types/src/interpreter/stack.rs similarity index 100% rename from lib/interface-types/src/instructions/stack.rs rename to lib/interface-types/src/interpreter/stack.rs diff --git a/lib/interface-types/src/interpreter/wasm/mod.rs b/lib/interface-types/src/interpreter/wasm/mod.rs new file mode 100644 index 00000000000..0ff75a9afc6 --- /dev/null +++ b/lib/interface-types/src/interpreter/wasm/mod.rs @@ -0,0 +1,2 @@ +pub mod structures; +pub mod values; diff --git a/lib/interface-types/src/instructions/wasm.rs b/lib/interface-types/src/interpreter/wasm/structures.rs similarity index 58% rename from lib/interface-types/src/instructions/wasm.rs rename to lib/interface-types/src/interpreter/wasm/structures.rs index 014b95c5c2e..d1fa583496f 100644 --- a/lib/interface-types/src/instructions/wasm.rs +++ b/lib/interface-types/src/interpreter/wasm/structures.rs @@ -1,88 +1,5 @@ -use std::{cell::Cell, 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 { - 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); +use super::values::{InterfaceType, InterfaceValue, ValueType}; +use std::cell::Cell; pub trait TypedIndex: Copy + Clone { fn new(index: usize) -> Self; diff --git a/lib/interface-types/src/interpreter/wasm/values.rs b/lib/interface-types/src/interpreter/wasm/values.rs new file mode 100644 index 00000000000..1cfac911f16 --- /dev/null +++ b/lib/interface-types/src/interpreter/wasm/values.rs @@ -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 { + 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); diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 748575ceabc..2185b2c1596 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -3,13 +3,13 @@ pub mod ast; mod macros; pub mod decoders; pub mod encoders; -pub mod instructions; +pub mod interpreter; pub use decoders::binary::parse as parse_binary; #[cfg(test)] mod tests { - use crate::{ast::*, instructions::Instruction, parse_binary}; + use crate::{ast::*, interpreter::Instruction, parse_binary}; use std::fs; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core as runtime; diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs index 7dfc839ca44..e6c0abf5f2c 100644 --- a/lib/interface-types/src/macros.rs +++ b/lib/interface-types/src/macros.rs @@ -23,3 +23,92 @@ macro_rules! consume { $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( + $($argument_name: $argument_type),* + ) -> ExecutableInstruction + where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + Instance: wasm::structures::Instance, + { + $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 = + (&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 = + (&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)); + } + }; +} From f537b4dfa30b031c1f98656adcbb62dc6def5cc3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 14:17:43 +0200 Subject: [PATCH 36/78] chore(interface-types) Simplify the `executable_instruction` macro. --- .../src/interpreter/instructions/argument_get.rs | 4 ++-- lib/interface-types/src/interpreter/instructions/call.rs | 4 ++-- .../src/interpreter/instructions/call_export.rs | 4 ++-- lib/interface-types/src/interpreter/instructions/read_utf8.rs | 4 ++-- .../src/interpreter/instructions/write_utf8.rs | 4 ++-- lib/interface-types/src/macros.rs | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/interface-types/src/interpreter/instructions/argument_get.rs b/lib/interface-types/src/interpreter/instructions/argument_get.rs index 881930297e7..0a679091008 100644 --- a/lib/interface-types/src/interpreter/instructions/argument_get.rs +++ b/lib/interface-types/src/interpreter/instructions/argument_get.rs @@ -1,6 +1,6 @@ executable_instruction!( argument_get(index: u64, instruction_name: String) -> _ { - Box::new(move |runtime| -> _ { + move |runtime| -> _ { let invocation_inputs = runtime.invocation_inputs; if index >= (invocation_inputs.len() as u64) { @@ -13,7 +13,7 @@ executable_instruction!( runtime.stack.push(invocation_inputs[index as usize].clone()); Ok(()) - }) + } } ); diff --git a/lib/interface-types/src/interpreter/instructions/call.rs b/lib/interface-types/src/interpreter/instructions/call.rs index 4a74655c3a4..d58368c1c00 100644 --- a/lib/interface-types/src/interpreter/instructions/call.rs +++ b/lib/interface-types/src/interpreter/instructions/call.rs @@ -5,7 +5,7 @@ use crate::interpreter::wasm::{ executable_instruction!( call(function_index: usize, instruction_name: String) -> _ { - Box::new(move |runtime| -> _ { + move |runtime| -> _ { let instance = runtime.wasm_instance; let index = FunctionIndex::new(function_index); @@ -58,7 +58,7 @@ executable_instruction!( function_index, )) } - }) + } } ); diff --git a/lib/interface-types/src/interpreter/instructions/call_export.rs b/lib/interface-types/src/interpreter/instructions/call_export.rs index 05de6470bd5..a1a43404ea2 100644 --- a/lib/interface-types/src/interpreter/instructions/call_export.rs +++ b/lib/interface-types/src/interpreter/instructions/call_export.rs @@ -2,7 +2,7 @@ use crate::interpreter::wasm::values::InterfaceType; executable_instruction!( call_export(export_name: String, instruction_name: String) -> _ { - Box::new(move |runtime| -> _ { + move |runtime| -> _ { let instance = runtime.wasm_instance; match instance.export(&export_name) { @@ -54,7 +54,7 @@ executable_instruction!( export_name, )) } - }) + } } ); diff --git a/lib/interface-types/src/interpreter/instructions/read_utf8.rs b/lib/interface-types/src/interpreter/instructions/read_utf8.rs index 8f195fd998d..55fe3509e90 100644 --- a/lib/interface-types/src/interpreter/instructions/read_utf8.rs +++ b/lib/interface-types/src/interpreter/instructions/read_utf8.rs @@ -3,7 +3,7 @@ use std::{cell::Cell, convert::TryFrom}; executable_instruction!( read_utf8(instruction_name: String) -> _ { - Box::new(move |runtime| -> _ { + move |runtime| -> _ { match runtime.stack.pop(2) { Some(inputs) => match runtime.wasm_instance.memory(0) { Some(memory) => { @@ -48,7 +48,7 @@ executable_instruction!( instruction_name, )) } - }) + } } ); diff --git a/lib/interface-types/src/interpreter/instructions/write_utf8.rs b/lib/interface-types/src/interpreter/instructions/write_utf8.rs index fa66621b2e4..34e148faf61 100644 --- a/lib/interface-types/src/interpreter/instructions/write_utf8.rs +++ b/lib/interface-types/src/interpreter/instructions/write_utf8.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; executable_instruction!( write_utf8(allocator_name: String, instruction_name: String) -> _ { - Box::new(move |runtime| -> _ { + move |runtime| -> _ { let instance = runtime.wasm_instance; match instance.export(&allocator_name) { @@ -67,7 +67,7 @@ executable_instruction!( allocator_name )) } - }) + } } ); diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs index e6c0abf5f2c..2d2e1464f09 100644 --- a/lib/interface-types/src/macros.rs +++ b/lib/interface-types/src/macros.rs @@ -37,7 +37,7 @@ macro_rules! executable_instruction { Memory: wasm::structures::Memory, Instance: wasm::structures::Instance, { - $implementation + Box::new($implementation) } }; } From c35395bd9435412ed83d48d75cf042b05203f72a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 14:18:30 +0200 Subject: [PATCH 37/78] feat(interface-types) Rename a variable. --- lib/interface-types/src/interpreter/mod.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index e8b45bd579b..6824128ef17 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -90,26 +90,22 @@ where .iter() .map( |instruction| -> ExecutableInstruction { - let instruction_representation: String = instruction.into(); + let instruction_name: String = instruction.into(); match instruction { Instruction::ArgumentGet { index } => { - instructions::argument_get(*index, instruction_representation) + instructions::argument_get(*index, instruction_name) } Instruction::Call { function_index } => { - instructions::call(*function_index, instruction_representation) + instructions::call(*function_index, instruction_name) } - Instruction::CallExport { export_name } => instructions::call_export( - (*export_name).to_owned(), - instruction_representation, - ), - Instruction::ReadUtf8 => { - instructions::read_utf8(instruction_representation) + Instruction::CallExport { export_name } => { + instructions::call_export((*export_name).to_owned(), instruction_name) + } + Instruction::ReadUtf8 => instructions::read_utf8(instruction_name), + Instruction::WriteUtf8 { allocator_name } => { + instructions::write_utf8((*allocator_name).to_owned(), instruction_name) } - Instruction::WriteUtf8 { allocator_name } => instructions::write_utf8( - (*allocator_name).to_owned(), - instruction_representation, - ), _ => unimplemented!(), } }, From 7e18be191dc9869b8944f920d505a3693cbaa6cf Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 14:36:31 +0200 Subject: [PATCH 38/78] chore(cargo) Update `Cargo.lock`. --- Cargo.lock | 45 ++++++++------------------------------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8faf93b3018..f31e1cb976f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,7 +429,7 @@ dependencies = [ [[package]] name = "either" -version = "1.5.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -614,7 +614,7 @@ name = "inkwell" version = "0.1.0" source = "git+https://github.com/wasmerio/inkwell?branch=llvm8-0#8f480124663b812ee76cd4370f3ee170135b9d0e" dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "enum-methods 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "inkwell_internal_macros 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -659,7 +659,7 @@ name = "itertools" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -687,7 +687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lexical-core" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -804,7 +804,7 @@ name = "nom" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lexical-core 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -940,21 +940,6 @@ dependencies = [ [[package]] name = "rand" -<<<<<<< HEAD -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -======= ->>>>>>> master version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -1041,7 +1026,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1537,15 +1522,9 @@ dependencies = [ "serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", -<<<<<<< HEAD - "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-fork-frontend 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-fork-wasm 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", -======= "target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-fork-frontend 0.43.1 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-fork-wasm 0.43.1 (registry+https://github.com/rust-lang/crates.io-index)", ->>>>>>> master "wasmer-runtime-core 0.7.0", "wasmer-win-exception-handler 0.7.0", "wasmparser 0.37.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1897,7 +1876,7 @@ dependencies = [ "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dynasm 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f36d49ab6f8ecc642d2c6ee10fda04ba68003ef0277300866745cdde160e6b40" "checksum dynasmrt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c408a211e7f5762829f5e46bdff0c14bc3b1517a21a4bb781c716bf88b0c68" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum enum-methods 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7798e7da2d4cb0d6d6fc467e8d6b5bf247e9e989f786dde1732d79899c32bb10" "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3beee4bc16478a1b26f2e80ad819a52d24745e292f521a63c16eea5f74b7eb60" @@ -1928,7 +1907,7 @@ dependencies = [ "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum lexical-core 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d11f3928ffd249baadf9f083cdea16d7cf317b2a8be6227e1169102432a36d2" +"checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" "checksum llvm-sys 80.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2110cd4daf9cd8e39dd3b933b1a2a2ac7315e91f7c92b3a20beab526c63b5978" @@ -1941,11 +1920,7 @@ dependencies = [ "checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -<<<<<<< HEAD "checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca" -"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -======= ->>>>>>> master "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" @@ -1962,10 +1937,6 @@ dependencies = [ "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -<<<<<<< HEAD -"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -======= ->>>>>>> master "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" From 63b13914c3e38d9d155d446bc98265140ca995f8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 14:37:29 +0200 Subject: [PATCH 39/78] feat(interface-types) Let the type inference engine do the work. --- lib/interface-types/src/interpreter/mod.rs | 38 ++++++++++------------ 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index 6824128ef17..a6c11f20b79 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -88,28 +88,26 @@ where fn try_from(instructions: &Vec) -> Result { let executable_instructions = instructions .iter() - .map( - |instruction| -> ExecutableInstruction { - let instruction_name: String = instruction.into(); + .map(|instruction| { + let instruction_name: String = instruction.into(); - match instruction { - 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::ReadUtf8 => instructions::read_utf8(instruction_name), - Instruction::WriteUtf8 { allocator_name } => { - instructions::write_utf8((*allocator_name).to_owned(), instruction_name) - } - _ => unimplemented!(), + match instruction { + 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::ReadUtf8 => instructions::read_utf8(instruction_name), + Instruction::WriteUtf8 { allocator_name } => { + instructions::write_utf8((*allocator_name).to_owned(), instruction_name) + } + _ => unimplemented!(), + } + }) .collect(); Ok(Interpreter { From 87f61ab226618fbc7f0af38deb892cf0c7781a0d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 15:02:20 +0200 Subject: [PATCH 40/78] feat(interface-types) Add attributes. --- lib/interface-types/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 2185b2c1596..397a2db5c23 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -1,3 +1,15 @@ +#![deny( + dead_code, + nonstandard_style, + unused_imports, + unused_mut, + unused_variables, + unused_unsafe, + unreachable_patterns +)] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://github.com/wasmerio.png")] + pub mod ast; #[macro_use] mod macros; From 9fdaf0cd4fe63b84005acec378c7d472eafcb351 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 26 Sep 2019 15:05:22 +0200 Subject: [PATCH 41/78] chore(cargo) Remove patches from version contraints. --- lib/interface-types/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml index 19ddeb22b86..e41b0e5d77f 100644 --- a/lib/interface-types/Cargo.toml +++ b/lib/interface-types/Cargo.toml @@ -11,5 +11,5 @@ edition = "2018" nom = "5.0" [dev-dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.7.0", features = ["backend-cranelift"] } -wasmer-clif-backend = { path = "../clif-backend", version = "0.7.0" } \ No newline at end of file +wasmer-runtime-core = { path = "../runtime-core", version = "0.7", features = ["backend-cranelift"] } +wasmer-clif-backend = { path = "../clif-backend", version = "0.7" } \ No newline at end of file From d8134721a854669c5fb221fcc06ffc3e7291e01e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Sat, 28 Sep 2019 00:55:35 +0200 Subject: [PATCH 42/78] feat(interface-types) Introduce the `wasm::structures::MemoryView` trait. --- lib/interface-types/src/ast.rs | 2 +- .../src/interpreter/instructions/mod.rs | 35 +++++++---- .../src/interpreter/instructions/read_utf8.rs | 2 +- .../interpreter/instructions/write_utf8.rs | 2 +- lib/interface-types/src/interpreter/mod.rs | 58 ++++++++++--------- .../src/interpreter/wasm/structures.rs | 39 +++++++++---- .../src/interpreter/wasm/values.rs | 20 ------- lib/interface-types/src/macros.rs | 17 +++--- 8 files changed, 96 insertions(+), 79 deletions(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 2bcabfb5548..1dc9ab96184 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -1,7 +1,7 @@ use crate::interpreter::Instruction; use std::str; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Clone, Debug)] pub enum InterfaceType { Int, Float, diff --git a/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs index 05c3f9723fd..e38dcf374e0 100644 --- a/lib/interface-types/src/interpreter/instructions/mod.rs +++ b/lib/interface-types/src/interpreter/instructions/mod.rs @@ -14,9 +14,9 @@ pub(crate) use write_utf8::write_utf8; pub(crate) mod tests { use crate::interpreter::wasm::{ self, - values::{InterfaceType, InterfaceValue, ValueType}, + values::{InterfaceType, InterfaceValue}, }; - use std::{cell::Cell, collections::HashMap, convert::TryInto}; + use std::{cell::Cell, collections::HashMap, convert::TryInto, ops::Deref, rc::Rc}; pub(crate) struct Export { pub(crate) inputs: Vec, @@ -74,24 +74,35 @@ pub(crate) mod tests { } } + #[derive(Default, Clone)] + pub(crate) struct MemoryView(Rc>>); + + impl wasm::structures::MemoryView for MemoryView {} + + impl Deref for MemoryView { + type Target = [Cell]; + + fn deref(&self) -> &[Cell] { + self.0.as_slice() + } + } + #[derive(Default)] pub(crate) struct Memory { - pub(crate) data: Vec>, + pub(crate) view: MemoryView, } impl Memory { pub(crate) fn new(data: Vec>) -> Self { - Self { data } + Self { + view: MemoryView(Rc::new(data)), + } } } - impl wasm::structures::Memory for Memory { - fn view(&self) -> &[Cell] { - use std::slice; - - let slice = self.data.as_slice(); - - unsafe { slice::from_raw_parts(slice.as_ptr() as *const Cell, slice.len()) } + impl wasm::structures::Memory for Memory { + fn view(&self) -> MemoryView { + self.view.clone() } } @@ -158,7 +169,7 @@ pub(crate) mod tests { } } - impl wasm::structures::Instance for Instance { + impl wasm::structures::Instance for Instance { fn export(&self, export_name: &str) -> Option<&Export> { self.exports.get(export_name) } diff --git a/lib/interface-types/src/interpreter/instructions/read_utf8.rs b/lib/interface-types/src/interpreter/instructions/read_utf8.rs index 55fe3509e90..a06bc56309f 100644 --- a/lib/interface-types/src/interpreter/instructions/read_utf8.rs +++ b/lib/interface-types/src/interpreter/instructions/read_utf8.rs @@ -9,7 +9,7 @@ executable_instruction!( 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::(); + let memory_view = memory.view(); if memory_view.len() < pointer + length { return Err(format!( diff --git a/lib/interface-types/src/interpreter/instructions/write_utf8.rs b/lib/interface-types/src/interpreter/instructions/write_utf8.rs index 34e148faf61..b0738f32862 100644 --- a/lib/interface-types/src/interpreter/instructions/write_utf8.rs +++ b/lib/interface-types/src/interpreter/instructions/write_utf8.rs @@ -20,7 +20,7 @@ executable_instruction!( match runtime.wasm_instance.memory(0) { Some(memory) => match runtime.stack.pop1() { Some(string) => { - let memory_view = memory.view::(); + let memory_view = memory.view(); let string: String = (&string).try_into()?; let string_bytes = string.as_bytes(); diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index a6c11f20b79..1b133a7bf67 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -8,45 +8,50 @@ use stack::Stack; use std::{convert::TryFrom, marker::PhantomData}; use wasm::values::InterfaceValue; -pub(crate) struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory> +pub(crate) struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory, MemoryView> where Export: wasm::structures::Export + 'instance, LocalImport: wasm::structures::LocalImport + 'instance, - Memory: wasm::structures::Memory + 'instance, - Instance: wasm::structures::Instance + 'instance, + Memory: wasm::structures::Memory + 'instance, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance + 'instance, { invocation_inputs: &'invocation [InterfaceValue], stack: Stack, wasm_instance: &'instance Instance, - _wasm_exports: PhantomData, - _wasm_locals_or_imports: PhantomData, - _wasm_memories: PhantomData, + _phantom: PhantomData<(Export, LocalImport, Memory, MemoryView)>, } -pub(crate) type ExecutableInstruction = - Box) -> Result<(), String>>; +pub(crate) type ExecutableInstruction = Box< + dyn Fn(&mut Runtime) -> Result<(), String>, +>; -pub struct Interpreter +pub struct Interpreter where Export: wasm::structures::Export, LocalImport: wasm::structures::LocalImport, - Memory: wasm::structures::Memory, - Instance: wasm::structures::Instance, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, { - executable_instructions: Vec>, + executable_instructions: + Vec>, } -impl Interpreter +impl + Interpreter where Export: wasm::structures::Export, LocalImport: wasm::structures::LocalImport, - Memory: wasm::structures::Memory, - Instance: wasm::structures::Instance, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, { fn iter( &self, - ) -> impl Iterator> + '_ - { + ) -> impl Iterator< + Item = &ExecutableInstruction, + > + '_ { self.executable_instructions.iter() } @@ -59,9 +64,7 @@ where invocation_inputs, stack: Stack::new(), wasm_instance, - _wasm_exports: PhantomData, - _wasm_locals_or_imports: PhantomData, - _wasm_memories: PhantomData, + _phantom: PhantomData, }; for executable_instruction in self.iter() { @@ -75,13 +78,15 @@ where } } -impl<'binary_input, Instance, Export, LocalImport, Memory> TryFrom<&Vec>> - for Interpreter +impl<'binary_input, Instance, Export, LocalImport, Memory, MemoryView> + TryFrom<&Vec>> + for Interpreter where Export: wasm::structures::Export, LocalImport: wasm::structures::LocalImport, - Memory: wasm::structures::Memory, - Instance: wasm::structures::Instance, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, { type Error = String; @@ -118,7 +123,7 @@ where #[cfg(test)] mod tests { - use super::{Instruction, Interpreter}; + use super::{wasm::structures::EmptyMemoryView, Instruction, Interpreter}; use std::convert::TryInto; #[test] @@ -130,7 +135,8 @@ mod tests { Instruction::ReadUtf8, Instruction::Call { function_index: 7 }, ]; - let interpreter: Interpreter<(), (), (), ()> = (&instructions).try_into().unwrap(); + let interpreter: Interpreter<(), (), (), (), EmptyMemoryView> = + (&instructions).try_into().unwrap(); assert_eq!(interpreter.executable_instructions.len(), 5); } diff --git a/lib/interface-types/src/interpreter/wasm/structures.rs b/lib/interface-types/src/interpreter/wasm/structures.rs index d1fa583496f..eb0a5113a8c 100644 --- a/lib/interface-types/src/interpreter/wasm/structures.rs +++ b/lib/interface-types/src/interpreter/wasm/structures.rs @@ -1,5 +1,5 @@ -use super::values::{InterfaceType, InterfaceValue, ValueType}; -use std::cell::Cell; +use super::values::{InterfaceType, InterfaceValue}; +use std::{cell::Cell, ops::Deref}; pub trait TypedIndex: Copy + Clone { fn new(index: usize) -> Self; @@ -53,15 +53,21 @@ pub trait LocalImport { fn call(&self, arguments: &[InterfaceValue]) -> Result, ()>; } -pub trait Memory { - fn view(&self) -> &[Cell]; +pub trait MemoryView: Deref]> {} + +pub trait Memory +where + View: MemoryView, +{ + fn view(&self) -> View; } -pub trait Instance +pub trait Instance where E: Export, LI: LocalImport, - M: Memory, + M: Memory, + MV: MemoryView, { fn export(&self, export_name: &str) -> Option<&E>; fn local_or_import(&self, index: I) -> Option<&LI>; @@ -112,17 +118,30 @@ impl LocalImport for () { } } -impl Memory for () { - fn view(&self) -> &[Cell] { +pub(crate) struct EmptyMemoryView; + +impl MemoryView for EmptyMemoryView {} + +impl Deref for EmptyMemoryView { + type Target = [Cell]; + + fn deref(&self) -> &[Cell] { &[] } } -impl Instance for () +impl Memory for () { + fn view(&self) -> EmptyMemoryView { + EmptyMemoryView + } +} + +impl Instance for () where E: Export, LI: LocalImport, - M: Memory, + M: Memory, + MV: MemoryView, { fn export(&self, _export_name: &str) -> Option<&E> { None diff --git a/lib/interface-types/src/interpreter/wasm/values.rs b/lib/interface-types/src/interpreter/wasm/values.rs index 1cfac911f16..632a88f65f4 100644 --- a/lib/interface-types/src/interpreter/wasm/values.rs +++ b/lib/interface-types/src/interpreter/wasm/values.rs @@ -63,23 +63,3 @@ 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); diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs index 2d2e1464f09..aee817824a7 100644 --- a/lib/interface-types/src/macros.rs +++ b/lib/interface-types/src/macros.rs @@ -28,14 +28,15 @@ 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( + pub(crate) fn $name( $($argument_name: $argument_type),* - ) -> ExecutableInstruction + ) -> ExecutableInstruction where Export: wasm::structures::Export, LocalImport: wasm::structures::LocalImport, - Memory: wasm::structures::Memory, - Instance: wasm::structures::Instance, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, { Box::new($implementation) } @@ -56,14 +57,14 @@ macro_rules! test_executable_instruction { #[allow(non_snake_case, unused)] fn $test_name() { use crate::interpreter::{ - instructions::tests::{Export, Instance, LocalImport, Memory}, + instructions::tests::{Export, Instance, LocalImport, Memory, MemoryView}, stack::Stackable, wasm::values::{InterfaceType, InterfaceValue}, Instruction, Interpreter, }; use std::{cell::Cell, collections::HashMap, convert::TryInto}; - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![$($instructions),*]).try_into().unwrap(); let invocation_inputs = vec![$($invocation_inputs),*]; @@ -90,14 +91,14 @@ macro_rules! test_executable_instruction { #[allow(non_snake_case, unused)] fn $test_name() { use crate::interpreter::{ - instructions::tests::{Export, Instance, LocalImport, Memory}, + instructions::tests::{Export, Instance, LocalImport, Memory, MemoryView}, stack::Stackable, wasm::values::{InterfaceType, InterfaceValue}, Instruction, Interpreter, }; use std::{cell::Cell, collections::HashMap, convert::TryInto}; - let interpreter: Interpreter = + let interpreter: Interpreter = (&vec![$($instructions),*]).try_into().unwrap(); let invocation_inputs = vec![$($invocation_inputs),*]; From 7b3ab386088a81699782728c5c2cde89638cfeeb Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 2 Oct 2019 14:58:48 +0200 Subject: [PATCH 43/78] chore(interface-types) Simplify the code. --- lib/interface-types/src/interpreter/instructions/mod.rs | 4 ++-- lib/interface-types/src/interpreter/wasm/structures.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs index e38dcf374e0..183e0dc8247 100644 --- a/lib/interface-types/src/interpreter/instructions/mod.rs +++ b/lib/interface-types/src/interpreter/instructions/mod.rs @@ -54,7 +54,7 @@ pub(crate) mod tests { impl wasm::structures::LocalImport for LocalImport { fn inputs_cardinality(&self) -> usize { - self.inputs.len() as usize + self.inputs.len() } fn outputs_cardinality(&self) -> usize { @@ -82,7 +82,7 @@ pub(crate) mod tests { impl Deref for MemoryView { type Target = [Cell]; - fn deref(&self) -> &[Cell] { + fn deref(&self) -> &Self::Target { self.0.as_slice() } } diff --git a/lib/interface-types/src/interpreter/wasm/structures.rs b/lib/interface-types/src/interpreter/wasm/structures.rs index eb0a5113a8c..a38511c8f39 100644 --- a/lib/interface-types/src/interpreter/wasm/structures.rs +++ b/lib/interface-types/src/interpreter/wasm/structures.rs @@ -125,7 +125,7 @@ impl MemoryView for EmptyMemoryView {} impl Deref for EmptyMemoryView { type Target = [Cell]; - fn deref(&self) -> &[Cell] { + fn deref(&self) -> &Self::Target { &[] } } From 99c9fc44dc3769b180f91cf1f6058bc6bf1ca3b3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 2 Oct 2019 22:14:08 +0200 Subject: [PATCH 44/78] chore(interface-types) The `tests/` directory has moved in `wasmer-runtime-core-tests`. --- lib/interface-types/tests/assets/hello_world.wasm | Bin 444 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 lib/interface-types/tests/assets/hello_world.wasm diff --git a/lib/interface-types/tests/assets/hello_world.wasm b/lib/interface-types/tests/assets/hello_world.wasm deleted file mode 100644 index 88d3c079ccadb8ee3722e991a139b542f26d797f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 444 zcmZ{gO-{ow5QX22oq}-HMr_!+J0#Q#z@}LsvE&A6NDZWNY?QcES*2IwUP$1kAfcje zW=8X)_vS;<)&zjAk|p(6g8C&8%b2;3k?(|j^=(k4cdfT3xBdkfT9cJ8H??sg4^^-R zB#e6>)u#4M$F$=ei$t;z^6W;+x~LpDDXjG+#HbC8H4bvRZK_~$x3M;V>ViRX@hDTG zK1)-iG=oM8l_1-lt83z3EHvi8j5XiOg{G;}$Y>q+XrE{d1vQCRn${U^5i6xG30bPf z4w(FquF!iUjcbdhlqapPA^ R&?%U`Ad&}Od~AmXoZr-0bans$ From 207d69fdbd838855dbad33287c24e3cdf13c309c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 3 Oct 2019 00:13:07 +0200 Subject: [PATCH 45/78] feat(interface-types) Change `Instance::local_or_import(&self)` to `Instance::local_or_import(&mut self)`. It allows the instance to create or update locals/imports when the `local_or_import` function is called. It's not ideal, but fine enough for a first step. --- Cargo.lock | 2 -- lib/interface-types/src/interpreter/instructions/call.rs | 2 +- .../src/interpreter/instructions/call_export.rs | 2 +- lib/interface-types/src/interpreter/instructions/mod.rs | 2 +- .../src/interpreter/instructions/write_utf8.rs | 4 ++-- lib/interface-types/src/interpreter/mod.rs | 4 ++-- lib/interface-types/src/interpreter/wasm/structures.rs | 4 ++-- lib/interface-types/src/macros.rs | 8 ++++---- 8 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f31e1cb976f..f63153bbfa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1594,8 +1594,6 @@ name = "wasmer-interface-types" version = "0.7.0" dependencies = [ "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.7.0", - "wasmer-runtime-core 0.7.0", ] [[package]] diff --git a/lib/interface-types/src/interpreter/instructions/call.rs b/lib/interface-types/src/interpreter/instructions/call.rs index d58368c1c00..f6375c48582 100644 --- a/lib/interface-types/src/interpreter/instructions/call.rs +++ b/lib/interface-types/src/interpreter/instructions/call.rs @@ -6,7 +6,7 @@ use crate::interpreter::wasm::{ executable_instruction!( call(function_index: usize, instruction_name: String) -> _ { move |runtime| -> _ { - let instance = runtime.wasm_instance; + let instance = &mut runtime.wasm_instance; let index = FunctionIndex::new(function_index); match instance.local_or_import(index) { diff --git a/lib/interface-types/src/interpreter/instructions/call_export.rs b/lib/interface-types/src/interpreter/instructions/call_export.rs index a1a43404ea2..62e360aa8f0 100644 --- a/lib/interface-types/src/interpreter/instructions/call_export.rs +++ b/lib/interface-types/src/interpreter/instructions/call_export.rs @@ -3,7 +3,7 @@ use crate::interpreter::wasm::values::InterfaceType; executable_instruction!( call_export(export_name: String, instruction_name: String) -> _ { move |runtime| -> _ { - let instance = runtime.wasm_instance; + let instance = &mut runtime.wasm_instance; match instance.export(&export_name) { Some(export) => { diff --git a/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs index 183e0dc8247..d84d2b47e82 100644 --- a/lib/interface-types/src/interpreter/instructions/mod.rs +++ b/lib/interface-types/src/interpreter/instructions/mod.rs @@ -175,7 +175,7 @@ pub(crate) mod tests { } fn local_or_import( - &self, + &mut self, index: I, ) -> Option<&LocalImport> { self.locals_or_imports.get(&index.index()) diff --git a/lib/interface-types/src/interpreter/instructions/write_utf8.rs b/lib/interface-types/src/interpreter/instructions/write_utf8.rs index b0738f32862..a1e21509f14 100644 --- a/lib/interface-types/src/interpreter/instructions/write_utf8.rs +++ b/lib/interface-types/src/interpreter/instructions/write_utf8.rs @@ -4,7 +4,7 @@ use std::convert::TryInto; executable_instruction!( write_utf8(allocator_name: String, instruction_name: String) -> _ { move |runtime| -> _ { - let instance = runtime.wasm_instance; + let instance = &mut runtime.wasm_instance; match instance.export(&allocator_name) { Some(allocator) => { @@ -17,7 +17,7 @@ executable_instruction!( )) } - match runtime.wasm_instance.memory(0) { + match instance.memory(0) { Some(memory) => match runtime.stack.pop1() { Some(string) => { let memory_view = memory.view(); diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index 1b133a7bf67..551311a83ee 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -18,7 +18,7 @@ where { invocation_inputs: &'invocation [InterfaceValue], stack: Stack, - wasm_instance: &'instance Instance, + wasm_instance: &'instance mut Instance, _phantom: PhantomData<(Export, LocalImport, Memory, MemoryView)>, } @@ -58,7 +58,7 @@ where pub fn run( &self, invocation_inputs: &[InterfaceValue], - wasm_instance: &Instance, + wasm_instance: &mut Instance, ) -> Result, String> { let mut runtime = Runtime { invocation_inputs, diff --git a/lib/interface-types/src/interpreter/wasm/structures.rs b/lib/interface-types/src/interpreter/wasm/structures.rs index a38511c8f39..7ec386f681a 100644 --- a/lib/interface-types/src/interpreter/wasm/structures.rs +++ b/lib/interface-types/src/interpreter/wasm/structures.rs @@ -70,7 +70,7 @@ where MV: MemoryView, { fn export(&self, export_name: &str) -> Option<&E>; - fn local_or_import(&self, index: I) -> Option<&LI>; + fn local_or_import(&mut self, index: I) -> Option<&LI>; fn memory(&self, index: usize) -> Option<&M>; } @@ -151,7 +151,7 @@ where None } - fn local_or_import(&self, _index: I) -> Option<&LI> { + fn local_or_import(&mut self, _index: I) -> Option<&LI> { None } } diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs index aee817824a7..a0b99940db5 100644 --- a/lib/interface-types/src/macros.rs +++ b/lib/interface-types/src/macros.rs @@ -68,8 +68,8 @@ macro_rules! test_executable_instruction { (&vec![$($instructions),*]).try_into().unwrap(); let invocation_inputs = vec![$($invocation_inputs),*]; - let instance = $instance; - let run = interpreter.run(&invocation_inputs, &instance); + let mut instance = $instance; + let run = interpreter.run(&invocation_inputs, &mut instance); assert!(run.is_ok()); @@ -102,8 +102,8 @@ macro_rules! test_executable_instruction { (&vec![$($instructions),*]).try_into().unwrap(); let invocation_inputs = vec![$($invocation_inputs),*]; - let instance = $instance; - let run = interpreter.run(&invocation_inputs, &instance); + let mut instance = $instance; + let run = interpreter.run(&invocation_inputs, &mut instance); assert!(run.is_err()); From b56240010c875f44b40c818a0ce83767cb9e64fa Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 4 Oct 2019 12:07:21 +0200 Subject: [PATCH 46/78] feat(interface-types) Simplify code. --- lib/interface-types/src/interpreter/instructions/call.rs | 2 +- lib/interface-types/src/interpreter/instructions/call_export.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interface-types/src/interpreter/instructions/call.rs b/lib/interface-types/src/interpreter/instructions/call.rs index f6375c48582..b788f535016 100644 --- a/lib/interface-types/src/interpreter/instructions/call.rs +++ b/lib/interface-types/src/interpreter/instructions/call.rs @@ -17,7 +17,7 @@ executable_instruction!( Some(inputs) => { let input_types = inputs .iter() - .map(|input| input.into()) + .map(Into::into) .collect::>(); if input_types != local_or_import.inputs() { diff --git a/lib/interface-types/src/interpreter/instructions/call_export.rs b/lib/interface-types/src/interpreter/instructions/call_export.rs index 62e360aa8f0..9afe984174c 100644 --- a/lib/interface-types/src/interpreter/instructions/call_export.rs +++ b/lib/interface-types/src/interpreter/instructions/call_export.rs @@ -13,7 +13,7 @@ executable_instruction!( Some(inputs) => { let input_types = inputs .iter() - .map(|input| input.into()) + .map(Into::into) .collect::>(); if input_types != export.inputs() { From c8af1b1fb7f086121fe89da9ab29de70f79b61aa Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 13:15:52 +0100 Subject: [PATCH 47/78] feat(interface-types) Remove dev-dependencies to any runtime. --- lib/interface-types/Cargo.toml | 6 +- lib/interface-types/src/lib.rs | 154 --------------------------------- 2 files changed, 1 insertion(+), 159 deletions(-) diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml index e41b0e5d77f..0e8bf2d97cf 100644 --- a/lib/interface-types/Cargo.toml +++ b/lib/interface-types/Cargo.toml @@ -8,8 +8,4 @@ repository = "https://github.com/wasmerio/wasmer" edition = "2018" [dependencies] -nom = "5.0" - -[dev-dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.7", features = ["backend-cranelift"] } -wasmer-clif-backend = { path = "../clif-backend", version = "0.7" } \ No newline at end of file +nom = "5.0" \ No newline at end of file diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 397a2db5c23..6798a1e297e 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -18,157 +18,3 @@ pub mod encoders; pub mod interpreter; pub use decoders::binary::parse as parse_binary; - -#[cfg(test)] -mod tests { - use crate::{ast::*, interpreter::Instruction, parse_binary}; - use std::fs; - use wasmer_clif_backend::CraneliftCompiler; - use wasmer_runtime_core as runtime; - - fn get_module() -> runtime::Module { - runtime::compile_with( - fs::read("tests/assets/hello_world.wasm") - .expect("Failed to read `tests/assets/hello_world.wasm`.") - .as_slice(), - &CraneliftCompiler::new(), - ) - .expect("Failed to parse the `hello_world.wasm` module.") - } - - #[test] - fn test_has_custom_section() { - let module = get_module(); - let custom_section = module.info().custom_sections.get("interface-types"); - - assert!(custom_section.is_some()); - } - - #[test] - fn test_parse_binary_from_custom_section() { - let module = get_module(); - let custom_section_bytes = module - .info() - .custom_sections - .get("interface-types") - .unwrap() - .as_slice(); - - match parse_binary::<()>(custom_section_bytes) { - Ok((remainder, interfaces)) => { - assert!(remainder.is_empty()); - assert_eq!( - interfaces, - Interfaces { - exports: vec![ - Export { - name: "strlen", - input_types: vec![InterfaceType::I32], - output_types: vec![InterfaceType::I32] - }, - Export { - name: "write_null_byte", - input_types: vec![InterfaceType::I32, InterfaceType::I32], - output_types: vec![InterfaceType::I32], - } - ], - types: vec![], - imported_functions: vec![ - ImportedFunction { - namespace: "host", - name: "console_log", - input_types: vec![InterfaceType::String], - output_types: vec![], - }, - ImportedFunction { - namespace: "host", - name: "document_title", - input_types: vec![], - output_types: vec![InterfaceType::String], - } - ], - adapters: vec![ - Adapter::Import { - namespace: "host", - name: "console_log", - input_types: vec![InterfaceType::I32], - output_types: vec![], - instructions: vec![ - Instruction::ArgumentGet { index: 0 }, - Instruction::ArgumentGet { index: 0 }, - Instruction::CallExport { - export_name: "strlen" - }, - Instruction::ReadUtf8, - Instruction::Call { function_index: 0 }, - ] - }, - Adapter::Import { - namespace: "host", - name: "document_title", - input_types: vec![], - output_types: vec![InterfaceType::I32], - instructions: vec![ - Instruction::Call { function_index: 1 }, - Instruction::WriteUtf8 { - allocator_name: "alloc" - }, - Instruction::CallExport { - export_name: "write_null_byte" - }, - ] - } - ], - forwards: vec![Forward { name: "main" }] - } - ); - - let wat = String::from(&interfaces); - - assert_eq!( - wat, - r#";; Interfaces - -;; Interface, Export strlen -(@interface export "strlen" - (param i32) - (result i32)) - -;; Interface, Export write_null_byte -(@interface export "write_null_byte" - (param i32 i32) - (result i32)) - -;; Interface, Imported function host.console_log -(@interface func $host_console_log (import "host" "console_log") - (param String)) - -;; Interface, Imported function host.document_title -(@interface func $host_document_title (import "host" "document_title") - (result String)) - -;; Interface, Adapter host.console_log -(@interface adapt (import "host" "console_log") - (param i32) - arg.get 0 - arg.get 0 - call-export "strlen" - read-utf8 - call 0) - -;; Interface, Adapter host.document_title -(@interface adapt (import "host" "document_title") - (result i32) - call 1 - write-utf8 "alloc" - call-export "write_null_byte") - -;; Interface, Forward main -(@interface forward (export "main"))"#, - ); - } - - Err(_) => assert!(false), - } - } -} From fe14c5c06b49f177cccfbf68bcaf56881ef32101 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 13:32:04 +0100 Subject: [PATCH 48/78] chore(interface-types) Update crate version. --- lib/interface-types/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml index 0e8bf2d97cf..cd1b97f6c9e 100644 --- a/lib/interface-types/Cargo.toml +++ b/lib/interface-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmer-interface-types" -version = "0.7.0" +version = "0.13.1" description = "WebAssembly Interface Types library for Wasmer" license = "MIT" authors = ["The Wasmer Engineering Team "] From 60a905698d62a150489ec6377693c4d7dcb42257 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 14:05:13 +0100 Subject: [PATCH 49/78] feat(interface-types) Use nom 5.1. --- Cargo.lock | 8 ++++---- lib/interface-types/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54af3029689..ca64343f4f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -706,7 +706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nom" -version = "5.0.1" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1604,9 +1604,9 @@ dependencies = [ [[package]] name = "wasmer-interface-types" -version = "0.7.0" +version = "0.13.1" dependencies = [ - "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1951,7 +1951,7 @@ dependencies = [ "checksum minifb 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "55cbdf43445926b65e07992f06019321e7481df8fd656dcb6871d00cdbd9fc73" "checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" -"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca" +"checksum nom 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c433f4d505fe6ce7ff78523d2fa13a0b9f2690e181fc26168bcbe5ccc5d14e07" "checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" "checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml index cd1b97f6c9e..a3d2474796e 100644 --- a/lib/interface-types/Cargo.toml +++ b/lib/interface-types/Cargo.toml @@ -8,4 +8,4 @@ repository = "https://github.com/wasmerio/wasmer" edition = "2018" [dependencies] -nom = "5.0" \ No newline at end of file +nom = "5.1" \ No newline at end of file From 0b0a89bf0fc652978a8d7f68c3373ec323ef155d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 14:41:06 +0100 Subject: [PATCH 50/78] test(interface-types) Fix a documentation message. --- lib/interface-types/src/interpreter/instructions/call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interface-types/src/interpreter/instructions/call.rs b/lib/interface-types/src/interpreter/instructions/call.rs index b788f535016..9f4ac022686 100644 --- a/lib/interface-types/src/interpreter/instructions/call.rs +++ b/lib/interface-types/src/interpreter/instructions/call.rs @@ -174,7 +174,7 @@ mod tests { inputs: vec![InterfaceType::I32, InterfaceType::I32], outputs: vec![InterfaceType::I32], function: |_| Ok(vec![]), - // ^^^^^^^^^^ void fails + // ^^^^^^^^^^ void }, ); From 102ebe87f8dddd09b08c33e8548f1dfab0b60d15 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 14:41:40 +0100 Subject: [PATCH 51/78] chore(interface-types) Format code. --- lib/interface-types/src/encoders/wat.rs | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index 89387ed4fea..fd1cd214c53 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -147,14 +147,14 @@ impl<'input> From<&Adapter<'input>> for String { name = name, inputs = input_types_to_param(&input_types), outputs = output_types_to_result(&output_types), - instructions = instructions.iter().fold( - String::new(), - |mut accumulator, instruction| { - accumulator.push_str("\n "); - accumulator.push_str(&String::from(instruction)); - accumulator - } - ), + instructions = + instructions + .iter() + .fold(String::new(), |mut accumulator, instruction| { + accumulator.push_str("\n "); + accumulator.push_str(&String::from(instruction)); + accumulator + }), ), Adapter::Export { @@ -167,14 +167,14 @@ impl<'input> From<&Adapter<'input>> for String { name = name, inputs = input_types_to_param(&input_types), outputs = output_types_to_result(&output_types), - instructions = instructions.iter().fold( - String::new(), - |mut accumulator, instruction| { - accumulator.push_str("\n "); - accumulator.push_str(&String::from(instruction)); - accumulator - } - ), + instructions = + instructions + .iter() + .fold(String::new(), |mut accumulator, instruction| { + accumulator.push_str("\n "); + accumulator.push_str(&String::from(instruction)); + accumulator + }), ), _ => unimplemented!(), From 5ed5ac82c07cda0a2a8601251a35852d5d25357b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 15:22:22 +0100 Subject: [PATCH 52/78] test(interface-types) Improve test cases for `Stack`. --- lib/interface-types/src/interpreter/stack.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/interface-types/src/interpreter/stack.rs b/lib/interface-types/src/interpreter/stack.rs index 57b999df26d..324c94a1f2b 100644 --- a/lib/interface-types/src/interpreter/stack.rs +++ b/lib/interface-types/src/interpreter/stack.rs @@ -100,7 +100,9 @@ mod tests { assert_eq!(stack.pop(1), Some(vec![6])); assert_eq!(stack.pop(2), Some(vec![5, 4])); + assert_eq!(stack.pop(4), None); // not enough items assert_eq!(stack.pop(3), Some(vec![3, 2, 1])); + assert_eq!(stack.pop1(), None); assert_eq!(stack.is_empty(), true); } } From e0989343289f3e9f8a46c44276904939db55edc5 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 15:22:56 +0100 Subject: [PATCH 53/78] doc(interface-types) Improve documentation of the crate itself. --- lib/interface-types/src/lib.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 6798a1e297e..64dac5b561a 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -1,3 +1,36 @@ +//! This crate contains an implementation of [WebAssembly Interface +//! Types][wit] (abbreviated WIT). It is composed of 4 parts: +//! +//! 1. AST: To represent the WIT language as a tree (which is not +//! really abstract). This is the central representation of the +//! language. +//! 2. Decoders: To read the AST from a particular data representation; +//! for instance, `decoders::binary` reads the AST from a binary. +//! 3. Encoders: To write the AST into a particular format; for +//! instance, `encoders::wat` writes the AST into a string +//! representing WIT with its textual format. + +//! 4. Interpreter: WIT defines a concept called Adapters. An adapter +//! contains a set of instructions. So, in more details, this +//! module contains: +//! * A very light and generic stack implementation, exposing only +//! the operations required by the interpreter, +//! * A stack-based interpreter, defined by: +//! * A compiler that transforms a set of instructions into a +//! set of executable instructions, +//! * A stack, +//! * A runtime that holds the “invocation inputs” (arguments +//! of the interpreter), the stack, and the WebAssembly +//! instance (which holds the exports, the imports, the +//! memories, the tables etc.), +//! * An hypothetic WebAssembly runtime, represented as a set of +//! enums, types, and traits —basically this is the part a +//! runtime should take a look to use the +//! `wasmer-interface-types` crate—. +//! +//! +//! [wit]: https://github.com/WebAssembly/interface-types + #![deny( dead_code, nonstandard_style, From d63508f19ef69a00b5218f5b98880275f68ce70f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 15:23:28 +0100 Subject: [PATCH 54/78] doc(interface-types) Improve documentation of the `stack` module. --- lib/interface-types/src/interpreter/stack.rs | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/interface-types/src/interpreter/stack.rs b/lib/interface-types/src/interpreter/stack.rs index 324c94a1f2b..7877da5739e 100644 --- a/lib/interface-types/src/interpreter/stack.rs +++ b/lib/interface-types/src/interpreter/stack.rs @@ -1,18 +1,39 @@ +//! A very light and generic stack implementation, exposing only the +//! operations required by the interpreter. + +/// The `Stackable` trait represents a small basic set of operations +/// required by the interpreter. pub trait Stackable { + /// The kind of item the stack holds. type Item; + /// Checks whether the stack is empty. fn is_empty(&self) -> bool; + + /// Extracts a slice containing the entire stack. fn as_slice(&self) -> &[Self::Item]; + + /// Appends one item to the end of the stack. fn push(&mut self, item: Self::Item); + + /// Removes the last item of the stack and returns it, `None` if + /// the stack is empty. fn pop1(&mut self) -> Option; + + /// Removes `n` elements from the end of the stack, `None` if the + /// stack doesn't contain enough elements. + /// Returned items are ordered by FIFO: the last element comes + /// first in the list. fn pop(&mut self, n: usize) -> Option>; } +/// A stack implementation of the `Stackable` trait, based on a vector. #[derive(Debug, Default)] pub struct Stack where T: Default + Clone, { + /// Inner structure holding the items. inner: Vec, } @@ -20,6 +41,7 @@ impl Stack where T: Default + Clone, { + /// Creates a new empty stack. pub fn new() -> Self { Self { ..Default::default() From 32325c1861cfac471f77c9b8e951ae3e65cccabb Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 15:27:04 +0100 Subject: [PATCH 55/78] doc(interface-types) Improve module descriptions. --- lib/interface-types/src/ast.rs | 3 +++ lib/interface-types/src/decoders/mod.rs | 3 +++ lib/interface-types/src/encoders/mod.rs | 4 ++++ lib/interface-types/src/interpreter/mod.rs | 2 ++ 4 files changed, 12 insertions(+) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 1dc9ab96184..d2020a8984c 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -1,3 +1,6 @@ +//! Represents the WIT language as a tree. This is the central +//! representation of the language. + use crate::interpreter::Instruction; use std::str; diff --git a/lib/interface-types/src/decoders/mod.rs b/lib/interface-types/src/decoders/mod.rs index 96eab66819c..0ac9d881dd2 100644 --- a/lib/interface-types/src/decoders/mod.rs +++ b/lib/interface-types/src/decoders/mod.rs @@ -1 +1,4 @@ +//! Reads the AST from a particular data representation; for instance, +//! `decoders::binary` reads the AST from a binary. + pub mod binary; diff --git a/lib/interface-types/src/encoders/mod.rs b/lib/interface-types/src/encoders/mod.rs index 070bf5893c0..ae1611e892f 100644 --- a/lib/interface-types/src/encoders/mod.rs +++ b/lib/interface-types/src/encoders/mod.rs @@ -1 +1,5 @@ +//! Writes the AST into a particular format; for instance, +//! `encoders::wat` writes the AST into a string representing WIT with +//! its textual format. + pub mod wat; diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index 551311a83ee..d9452abce6d 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -1,3 +1,5 @@ +//! A stack-based interpreter to execute instructions of WIT adapters. + mod instruction; mod instructions; pub mod stack; From 36c7dbd92fac16f334da5e4369c360568220b869 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 15:41:10 +0100 Subject: [PATCH 56/78] feat(interface-types) Rename `ImportedFunction` to `Import`. So that we are consistent with `Export`. --- lib/interface-types/src/decoders/binary.rs | 44 +++++++++--------- lib/interface-types/src/encoders/wat.rs | 52 +++++++++++----------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 9a1d77e47e1..2853f7eaecc 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -291,30 +291,30 @@ fn types<'input, E: ParseError<&'input [u8]>>( Ok((input, types)) } -fn imported_functions<'input, E: ParseError<&'input [u8]>>( +fn imports<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], -) -> IResult<&'input [u8], Vec, E> { +) -> IResult<&'input [u8], Vec, E> { let mut input = input; - consume!((input, number_of_imported_functions) = leb(input)?); + consume!((input, number_of_imports) = leb(input)?); - let mut imported_functions = Vec::with_capacity(number_of_imported_functions as usize); + let mut imports = Vec::with_capacity(number_of_imports as usize); - for _ in 0..number_of_imported_functions { - consume!((input, imported_function_namespace) = string(input)?); - consume!((input, imported_function_name) = string(input)?); - consume!((input, imported_function_input_types) = list(input, ty)?); - consume!((input, imported_function_output_types) = list(input, ty)?); + for _ in 0..number_of_imports { + consume!((input, import_namespace) = string(input)?); + consume!((input, import_name) = string(input)?); + consume!((input, import_input_types) = list(input, ty)?); + consume!((input, import_output_types) = list(input, ty)?); - imported_functions.push(ImportedFunction { - namespace: imported_function_namespace, - name: imported_function_name, - input_types: imported_function_input_types, - output_types: imported_function_output_types, + imports.push(Import { + namespace: import_namespace, + name: import_name, + input_types: import_input_types, + output_types: import_output_types, }); } - Ok((input, imported_functions)) + Ok((input, imports)) } fn adapters<'input, E: ParseError<&'input [u8]>>( @@ -406,7 +406,7 @@ pub fn parse<'input, E: ParseError<&'input [u8]>>( consume!((input, exports) = exports(input)?); consume!((input, types) = types(input)?); - consume!((input, imported_functions) = imported_functions(input)?); + consume!((input, imports) = imports(input)?); consume!((input, adapters) = adapters(input)?); consume!((input, forwards) = forwards(input)?); @@ -415,7 +415,7 @@ pub fn parse<'input, E: ParseError<&'input [u8]>>( Interfaces { exports, types, - imported_functions, + imports, adapters, forwards, }, @@ -634,9 +634,9 @@ mod tests { } #[test] - fn test_imported_functions() { + fn test_imports() { let input = &[ - 0x02, // 2 imported functions + 0x02, // 2 imports 0x01, // string of 1 byte 0x61, // "a" 0x01, // string of 1 byte @@ -657,13 +657,13 @@ mod tests { let output = Ok(( &[] as &[u8], vec![ - ImportedFunction { + Import { namespace: "a", name: "b", input_types: vec![InterfaceType::I32], output_types: vec![InterfaceType::I64], }, - ImportedFunction { + Import { namespace: "c", name: "d", input_types: vec![InterfaceType::I32], @@ -672,7 +672,7 @@ mod tests { ], )); - assert_eq!(imported_functions::<()>(input), output); + assert_eq!(imports::<()>(input), output); } #[test] diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index fd1cd214c53..8768da73382 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -1,5 +1,5 @@ use crate::{ - ast::{Adapter, Export, Forward, ImportedFunction, InterfaceType, Interfaces, Type}, + ast::{Adapter, Export, Forward, Import, InterfaceType, Interfaces, Type}, interpreter::Instruction, }; @@ -120,14 +120,14 @@ impl<'input> From<&Type<'input>> for String { } } -impl<'input> From<&ImportedFunction<'input>> for String { - fn from(imported_function: &ImportedFunction) -> Self { +impl<'input> From<&Import<'input>> for String { + fn from(import: &Import) -> Self { format!( r#"(@interface func ${namespace}_{name} (import "{namespace}" "{name}"){inputs}{outputs})"#, - namespace = imported_function.namespace, - name = imported_function.name, - inputs = input_types_to_param(&imported_function.input_types), - outputs = output_types_to_result(&imported_function.output_types), + namespace = import.namespace, + name = import.name, + inputs = input_types_to_param(&import.input_types), + outputs = output_types_to_result(&import.output_types), ) } } @@ -213,17 +213,17 @@ impl<'input> From<&Interfaces<'input>> for String { accumulator }); - let imported_functions = interfaces.imported_functions.iter().fold( - String::new(), - |mut accumulator, imported_function| { + let imports = interfaces + .imports + .iter() + .fold(String::new(), |mut accumulator, import| { accumulator.push_str(&format!( - "\n\n;; Interface, Imported function {}.{}\n", - imported_function.namespace, imported_function.name + "\n\n;; Interface, Import {}.{}\n", + import.namespace, import.name )); - accumulator.push_str(&String::from(imported_function)); + accumulator.push_str(&String::from(import)); accumulator - }, - ); + }); let adapters = interfaces @@ -260,7 +260,7 @@ impl<'input> From<&Interfaces<'input>> for String { output.push_str(&exports); output.push_str(&types); - output.push_str(&imported_functions); + output.push_str(&imports); output.push_str(&adapters); output.push_str(&forwards); @@ -389,30 +389,30 @@ mod tests { } #[test] - fn test_imported_functions() { + fn test_imports() { let inputs: Vec = vec![ - (&ImportedFunction { + (&Import { namespace: "ns", name: "foo", input_types: vec![InterfaceType::Int, InterfaceType::String], output_types: vec![InterfaceType::String], }) .into(), - (&ImportedFunction { + (&Import { namespace: "ns", name: "foo", input_types: vec![InterfaceType::String], output_types: vec![], }) .into(), - (&ImportedFunction { + (&Import { namespace: "ns", name: "foo", input_types: vec![], output_types: vec![InterfaceType::String], }) .into(), - (&ImportedFunction { + (&Import { namespace: "ns", name: "foo", input_types: vec![], @@ -549,14 +549,14 @@ mod tests { }, ], types: vec![], - imported_functions: vec![ - ImportedFunction { + imports: vec![ + Import { namespace: "ns", name: "foo", input_types: vec![], output_types: vec![InterfaceType::I32], }, - ImportedFunction { + Import { namespace: "ns", name: "bar", input_types: vec![], @@ -590,11 +590,11 @@ mod tests { ;; Interface, Export bar (@interface export "bar") -;; Interface, Imported function ns.foo +;; Interface, Import ns.foo (@interface func $ns_foo (import "ns" "foo") (result i32)) -;; Interface, Imported function ns.bar +;; Interface, Import ns.bar (@interface func $ns_bar (import "ns" "bar")) ;; Interface, Adapter ns.foo From d667cb8e2fad0e59ba0177296fac3e832d146f77 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 15:41:41 +0100 Subject: [PATCH 57/78] doc(interface-types) Improve documentation of the `ast` module. --- lib/interface-types/src/ast.rs | 99 +++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index d2020a8984c..33609ad54b6 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -4,82 +4,165 @@ use crate::interpreter::Instruction; use std::str; +/// Represents the types supported by WIT. #[derive(PartialEq, Clone, Debug)] pub enum InterfaceType { + /// An integer. Int, + + /// A float. Float, + + /// Opaque reference. Any, + + /// A string. String, + + /// A sequence. Seq, + + /// A 32-bits integer. I32, + + /// A 64-bits integer. I64, + + /// A 32-bits float. F32, + + /// A 64-bits float. F64, + + /// An `any` reference. AnyRef, } +/// Represents the kind of adapter. #[derive(PartialEq, Debug)] pub(crate) enum AdapterKind { + /// An adapter defined for an imported function of a WebAssembly instance. Import, + + /// An adapter defined for an exported function of a WebAssembly instance. Export, + + /// A helper function. HelperFunction, } +/// Represents an exported function signature. #[derive(PartialEq, Debug)] pub struct Export<'input> { + /// The function name. pub name: &'input str, + + /// The function input types. pub input_types: Vec, + + /// The function output types. pub output_types: Vec, } +/// Represents an imported function signature. #[derive(PartialEq, Debug)] -pub struct Type<'input> { +pub struct Import<'input> { + /// The function namespace. + pub namespace: &'input str, + + /// The function name. pub name: &'input str, - pub fields: Vec<&'input str>, - pub types: Vec, + + /// The function input types. + pub input_types: Vec, + + /// The function output types. + pub output_types: Vec, } +/// Represents a type. #[derive(PartialEq, Debug)] -pub struct ImportedFunction<'input> { - pub namespace: &'input str, +pub struct Type<'input> { pub name: &'input str, - pub input_types: Vec, - pub output_types: Vec, + pub fields: Vec<&'input str>, + pub types: Vec, } +/// Represents an adapter. #[derive(PartialEq, Debug)] pub enum Adapter<'input> { + /// An adapter for an imported function. Import { + /// The function namespace. namespace: &'input str, + + /// The function name. name: &'input str, + + /// The function input types. input_types: Vec, + + /// The function output types. output_types: Vec, + + /// The instructions of the adapter. instructions: Vec>, }, + + /// An adapter for an exported function. Export { + /// The function name. name: &'input str, + + /// The function input types. input_types: Vec, + + /// The function output types. output_types: Vec, + + /// The instructions of the adapter. instructions: Vec>, }, + + /// An adapter for a helper function. HelperFunction { + /// The helper name. name: &'input str, + + /// The helper input types. input_types: Vec, + + /// The helper output types. output_types: Vec, + + /// The instructions of the adapter. instructions: Vec>, }, } +/// Represented a forwarded export. #[derive(PartialEq, Debug)] pub struct Forward<'input> { + /// The forwarded export name. pub name: &'input str, } +/// Represents a set of interfaces, i.e. it entirely describes a WIT +/// definition. #[derive(PartialEq, Debug)] pub struct Interfaces<'input> { + /// All the exports. pub exports: Vec>, + + /// All the types. pub types: Vec>, - pub imported_functions: Vec>, + + /// All the imported functions. + pub imports: Vec>, + + /// All the exported functions. pub adapters: Vec>, + + /// All the forwarded functions. pub forwards: Vec>, } From 2fc1fbb7ecebed7c479ede1405a02f06557a019d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 15:44:28 +0100 Subject: [PATCH 58/78] doc(interface-types) Improve documentation of the `ast` module. --- lib/interface-types/src/ast.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 33609ad54b6..4adf2f0227f 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -80,11 +80,16 @@ pub struct Import<'input> { pub output_types: Vec, } -/// Represents a type. +/// Represents a structural type. #[derive(PartialEq, Debug)] pub struct Type<'input> { + /// The type name. pub name: &'input str, + + /// The field names. pub fields: Vec<&'input str>, + + /// The field types. pub types: Vec, } @@ -151,7 +156,7 @@ pub struct Forward<'input> { /// definition. #[derive(PartialEq, Debug)] pub struct Interfaces<'input> { - /// All the exports. + /// All the exported functions. pub exports: Vec>, /// All the types. @@ -160,7 +165,7 @@ pub struct Interfaces<'input> { /// All the imported functions. pub imports: Vec>, - /// All the exported functions. + /// All the adapters. pub adapters: Vec>, /// All the forwarded functions. From 9fda6f945186b770da3725c8559e9bc0cae6c145 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 16:39:06 +0100 Subject: [PATCH 59/78] doc(interface-types) Improve documentation of the `binary` module. --- lib/interface-types/src/decoders/binary.rs | 27 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 2853f7eaecc..403fc5b6fc7 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -1,3 +1,5 @@ +//! Parse the WIT binary representation into an AST. + use crate::{ast::*, interpreter::Instruction}; use nom::{ error::{make_error, ErrorKind, ParseError}, @@ -5,6 +7,7 @@ use nom::{ }; use std::{convert::TryFrom, str}; +/// Parse an `InterfaceType`. impl TryFrom for InterfaceType { type Error = &'static str; @@ -25,6 +28,7 @@ impl TryFrom for InterfaceType { } } +/// Parse an adapter kind. impl TryFrom for AdapterKind { type Error = &'static str; @@ -38,6 +42,7 @@ impl TryFrom for AdapterKind { } } +/// Parse a byte. fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); @@ -46,6 +51,7 @@ fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'i Ok((&input[1..], input[0])) } +/// Parse an unsigned LEB with value no larger than a 64-bits number. fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); @@ -65,6 +71,7 @@ fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'in )) } +/// Parse a UTF-8 string. fn string<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], &'input str, E> { @@ -84,6 +91,7 @@ fn string<'input, E: ParseError<&'input [u8]>>( })) } +/// Parse a list, with a item parser. #[allow(clippy::type_complexity)] fn list<'input, I, E: ParseError<&'input [u8]>>( input: &'input [u8], @@ -110,6 +118,7 @@ fn list<'input, I, E: ParseError<&'input [u8]>>( Ok((input, items)) } +/// Parse a type. fn ty<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], InterfaceType, E> { @@ -125,7 +134,8 @@ fn ty<'input, E: ParseError<&'input [u8]>>( } } -fn instructions<'input, E: ParseError<&'input [u8]>>( +/// Parse an instruction with its arguments. +fn instruction<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Instruction, E> { let (mut input, opcode) = byte(input)?; @@ -243,6 +253,7 @@ fn instructions<'input, E: ParseError<&'input [u8]>>( }) } +/// Parse a list of exports. fn exports<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { @@ -267,6 +278,7 @@ fn exports<'input, E: ParseError<&'input [u8]>>( Ok((input, exports)) } +/// Parse a list of types. fn types<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { @@ -291,6 +303,7 @@ fn types<'input, E: ParseError<&'input [u8]>>( Ok((input, types)) } +/// Parse a list of imports. fn imports<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { @@ -317,6 +330,7 @@ fn imports<'input, E: ParseError<&'input [u8]>>( Ok((input, imports)) } +/// Parse a list of adapters. fn adapters<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { @@ -337,7 +351,7 @@ fn adapters<'input, E: ParseError<&'input [u8]>>( consume!((input, adapter_name) = string(input)?); consume!((input, adapter_input_types) = list(input, ty)?); consume!((input, adapter_output_types) = list(input, ty)?); - consume!((input, adapter_instructions) = list(input, instructions)?); + consume!((input, adapter_instructions) = list(input, instruction)?); adapters.push(Adapter::Import { namespace: adapter_namespace, @@ -352,7 +366,7 @@ fn adapters<'input, E: ParseError<&'input [u8]>>( consume!((input, adapter_name) = string(input)?); consume!((input, adapter_input_types) = list(input, ty)?); consume!((input, adapter_output_types) = list(input, ty)?); - consume!((input, adapter_instructions) = list(input, instructions)?); + consume!((input, adapter_instructions) = list(input, instruction)?); adapters.push(Adapter::Export { name: adapter_name, @@ -366,7 +380,7 @@ fn adapters<'input, E: ParseError<&'input [u8]>>( consume!((input, adapter_name) = string(input)?); consume!((input, adapter_input_types) = list(input, ty)?); consume!((input, adapter_output_types) = list(input, ty)?); - consume!((input, adapter_instructions) = list(input, instructions)?); + consume!((input, adapter_instructions) = list(input, instruction)?); adapters.push(Adapter::HelperFunction { name: adapter_name, @@ -381,6 +395,7 @@ fn adapters<'input, E: ParseError<&'input [u8]>>( Ok((input, adapters)) } +/// Parse a list of forwarded exports. fn forwards<'input, E: ParseError<&'input [u8]>>( input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { @@ -399,6 +414,8 @@ fn forwards<'input, E: ParseError<&'input [u8]>>( Ok((input, forwards)) } +/// Parse a sequence of bytes, expecting it to be a valid WIT binary +/// representation, into an `ast::Interfaces`. pub fn parse<'input, E: ParseError<&'input [u8]>>( bytes: &'input [u8], ) -> IResult<&'input [u8], Interfaces, E> { @@ -569,7 +586,7 @@ mod tests { ], )); - assert_eq!(list::(input, instructions), output); + assert_eq!(list::(input, instruction), output); } #[test] From 98fb0697d1e27929fd09d5a4d19ebca51b24f609 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 16:39:34 +0100 Subject: [PATCH 60/78] test(interface-types) Test the `parse` parser. --- lib/interface-types/src/decoders/binary.rs | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 403fc5b6fc7..4c3af40df27 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -770,4 +770,83 @@ mod tests { assert_eq!(forwards::<()>(input), output); } + + #[test] + fn test_parse() { + let input = &[ + 0x01, // 1 export + 0x02, // string of 2 bytes + 0x61, 0x62, // "a", "b" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // 1 type + 0x02, // string of 2 bytes + 0x61, 0x62, // "a", "b" + 0x02, // list of 2 items + 0x02, // string of 2 bytes + 0x63, 0x64, // "c", "d" + 0x01, // string of 1 byte + 0x65, // "e" + 0x02, // list of 2 items + 0x7f, // I32 + 0x7f, // I32 + 0x01, // 1 import + 0x01, // string of 1 byte + 0x61, // "a" + 0x01, // string of 1 byte + 0x62, // "b" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7e, // I64 + 0x01, // 1 adapter + 0x00, // adapter kind: import + 0x01, // string of 1 byte + 0x61, // "a" + 0x01, // string of 1 byte + 0x62, // "b" + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x7f, // I32 + 0x01, // list of 1 item + 0x00, 0x01, // ArgumentGet { index: 1 } + 0x01, // 1 adapter + 0x01, // string of 1 byte + 0x61, // "a" + ]; + let output = Ok(( + &[] as &[u8], + Interfaces { + exports: vec![Export { + name: "ab", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I32], + }], + types: vec![Type { + name: "ab", + fields: vec!["cd", "e"], + types: vec![InterfaceType::I32, InterfaceType::I32], + }], + imports: vec![Import { + namespace: "a", + name: "b", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I64], + }], + adapters: vec![Adapter::Import { + namespace: "a", + name: "b", + input_types: vec![InterfaceType::I32], + output_types: vec![InterfaceType::I32], + instructions: vec![Instruction::ArgumentGet { index: 1 }], + }], + forwards: vec![Forward { name: "a" }], + }, + )); + + assert_eq!(parse::<()>(input), output); + } } From 55ae0e474ef22f2dbfed0f3d076c92c62aeda600 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 16:48:25 +0100 Subject: [PATCH 61/78] doc(interface-types) Add a doctest for `decoders::binary::parse`. --- lib/interface-types/src/decoders/binary.rs | 88 ++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 4c3af40df27..2eb914b3abf 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -416,6 +416,94 @@ fn forwards<'input, E: ParseError<&'input [u8]>>( /// Parse a sequence of bytes, expecting it to be a valid WIT binary /// representation, into an `ast::Interfaces`. +/// +/// # Example +/// +/// ```rust +/// use wasmer_interface_types::{ +/// ast::*, +/// decoders::binary::parse, +/// interpreter::Instruction, +/// }; +/// +/// # fn main() { +/// let input = &[ +/// 0x01, // 1 export +/// 0x02, // string of 2 bytes +/// 0x61, 0x62, // "a", "b" +/// 0x01, // list of 1 item +/// 0x7f, // I32 +/// 0x01, // list of 1 item +/// 0x7f, // I32 +/// 0x01, // 1 type +/// 0x02, // string of 2 bytes +/// 0x61, 0x62, // "a", "b" +/// 0x02, // list of 2 items +/// 0x02, // string of 2 bytes +/// 0x63, 0x64, // "c", "d" +/// 0x01, // string of 1 byte +/// 0x65, // "e" +/// 0x02, // list of 2 items +/// 0x7f, // I32 +/// 0x7f, // I32 +/// 0x01, // 1 import +/// 0x01, // string of 1 byte +/// 0x61, // "a" +/// 0x01, // string of 1 byte +/// 0x62, // "b" +/// 0x01, // list of 1 item +/// 0x7f, // I32 +/// 0x01, // list of 1 item +/// 0x7e, // I64 +/// 0x01, // 1 adapter +/// 0x00, // adapter kind: import +/// 0x01, // string of 1 byte +/// 0x61, // "a" +/// 0x01, // string of 1 byte +/// 0x62, // "b" +/// 0x01, // list of 1 item +/// 0x7f, // I32 +/// 0x01, // list of 1 item +/// 0x7f, // I32 +/// 0x01, // list of 1 item +/// 0x00, 0x01, // ArgumentGet { index: 1 } +/// 0x01, // 1 adapter +/// 0x01, // string of 1 byte +/// 0x61, // "a" +/// ]; +/// let output = Ok(( +/// &[] as &[u8], +/// Interfaces { +/// exports: vec![Export { +/// name: "ab", +/// input_types: vec![InterfaceType::I32], +/// output_types: vec![InterfaceType::I32], +/// }], +/// types: vec![Type { +/// name: "ab", +/// fields: vec!["cd", "e"], +/// types: vec![InterfaceType::I32, InterfaceType::I32], +/// }], +/// imports: vec![Import { +/// namespace: "a", +/// name: "b", +/// input_types: vec![InterfaceType::I32], +/// output_types: vec![InterfaceType::I64], +/// }], +/// adapters: vec![Adapter::Import { +/// namespace: "a", +/// name: "b", +/// input_types: vec![InterfaceType::I32], +/// output_types: vec![InterfaceType::I32], +/// instructions: vec![Instruction::ArgumentGet { index: 1 }], +/// }], +/// forwards: vec![Forward { name: "a" }], +/// }, +/// )); +/// +/// assert_eq!(parse::<()>(input), output); +/// # } +/// ``` pub fn parse<'input, E: ParseError<&'input [u8]>>( bytes: &'input [u8], ) -> IResult<&'input [u8], Interfaces, E> { From c5b963b81b8ad7849dbbc275bbda78b1aef8a379 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 10 Feb 2020 17:12:32 +0100 Subject: [PATCH 62/78] doc(interface-types) Improve documentation of the `wat` module. --- lib/interface-types/src/encoders/wat.rs | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index 8768da73382..fcdc9161b16 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -1,8 +1,99 @@ +//! Writes the AST into a string representing WIT with its textual format. +//! +//! # Example +//! +//! ```rust +//! use wasmer_interface_types::{ +//! ast::*, +//! encoders::wat::*, +//! interpreter::Instruction, +//! }; +//! +//! # fn main() { +//! let input: String = (&Interfaces { +//! exports: vec![ +//! Export { +//! name: "foo", +//! input_types: vec![InterfaceType::I32], +//! output_types: vec![], +//! }, +//! Export { +//! name: "bar", +//! input_types: vec![], +//! output_types: vec![], +//! }, +//! ], +//! types: vec![], +//! imports: vec![ +//! Import { +//! namespace: "ns", +//! name: "foo", +//! input_types: vec![], +//! output_types: vec![InterfaceType::I32], +//! }, +//! Import { +//! namespace: "ns", +//! name: "bar", +//! input_types: vec![], +//! output_types: vec![], +//! }, +//! ], +//! adapters: vec![ +//! Adapter::Import { +//! namespace: "ns", +//! name: "foo", +//! input_types: vec![InterfaceType::I32], +//! output_types: vec![], +//! instructions: vec![Instruction::ArgumentGet { index: 42 }], +//! }, +//! Adapter::Export { +//! name: "bar", +//! input_types: vec![], +//! output_types: vec![], +//! instructions: vec![Instruction::ArgumentGet { index: 42 }], +//! }, +//! ], +//! forwards: vec![Forward { name: "main" }], +//! }) +//! .into(); +//! let output = r#";; Interfaces +//! +//! ;; Interface, Export foo +//! (@interface export "foo" +//! (param i32)) +//! +//! ;; Interface, Export bar +//! (@interface export "bar") +//! +//! ;; Interface, Import ns.foo +//! (@interface func $ns_foo (import "ns" "foo") +//! (result i32)) +//! +//! ;; Interface, Import ns.bar +//! (@interface func $ns_bar (import "ns" "bar")) +//! +//! ;; Interface, Adapter ns.foo +//! (@interface adapt (import "ns" "foo") +//! (param i32) +//! arg.get 42) +//! +//! ;; Interface, Adapter bar +//! (@interface adapt (export "bar") +//! arg.get 42) +//! +//! ;; Interface, Forward main +//! (@interface forward (export "main"))"#; +//! +//! assert_eq!(input, output); +//! # } +//! ``` + use crate::{ ast::{Adapter, Export, Forward, Import, InterfaceType, Interfaces, Type}, interpreter::Instruction, }; +/// Encode an `InterfaceType` into a string. impl From<&InterfaceType> for String { fn from(interface_type: &InterfaceType) -> Self { match interface_type { @@ -20,6 +111,7 @@ impl From<&InterfaceType> for String { } } +/// Encode an `Instruction` into a string. impl<'input> From<&Instruction<'input>> for String { fn from(instruction: &Instruction) -> Self { match instruction { @@ -69,6 +161,8 @@ impl<'input> From<&Instruction<'input>> for String { } } +/// Encode a list of `InterfaceType` representing inputs into a +/// string. fn input_types_to_param(input_types: &[InterfaceType]) -> String { if input_types.is_empty() { "".into() @@ -86,6 +180,8 @@ fn input_types_to_param(input_types: &[InterfaceType]) -> String { } } +/// Encode a list of `InterfaceType` representing outputs into a +/// string. fn output_types_to_result(output_types: &[InterfaceType]) -> String { if output_types.is_empty() { "".into() @@ -103,6 +199,7 @@ fn output_types_to_result(output_types: &[InterfaceType]) -> String { } } +/// Encode an `Export` into a string. impl<'input> From<&Export<'input>> for String { fn from(export: &Export) -> Self { format!( @@ -114,12 +211,14 @@ impl<'input> From<&Export<'input>> for String { } } +/// Encode a `Type` into a string. impl<'input> From<&Type<'input>> for String { fn from(_ty: &Type) -> Self { unimplemented!() } } +/// Encode an `Import` into a string. impl<'input> From<&Import<'input>> for String { fn from(import: &Import) -> Self { format!( @@ -132,6 +231,7 @@ impl<'input> From<&Import<'input>> for String { } } +/// Encode an `Adapter` into a string. impl<'input> From<&Adapter<'input>> for String { fn from(adapter: &Adapter) -> Self { match adapter { @@ -182,6 +282,7 @@ impl<'input> From<&Adapter<'input>> for String { } } +/// Encode a `Forward` into a string. impl<'input> From<&Forward<'input>> for String { fn from(forward: &Forward) -> Self { format!( @@ -191,6 +292,7 @@ impl<'input> From<&Forward<'input>> for String { } } +/// Encode an `Interfaces` into a string. impl<'input> From<&Interfaces<'input>> for String { fn from(interfaces: &Interfaces) -> Self { let mut output = String::from(";; Interfaces"); From 98c73099c3e9f370d612e4664611a2f7191a88bc Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 12 Feb 2020 15:52:15 +0100 Subject: [PATCH 63/78] doc(interface-types) Improve documentation of the `macros` module. --- lib/interface-types/src/macros.rs | 47 ++++++++++++++++++------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs index a0b99940db5..735cecd3cd1 100644 --- a/lib/interface-types/src/macros.rs +++ b/lib/interface-types/src/macros.rs @@ -1,22 +1,5 @@ -#[allow(unused)] -macro_rules! d { - ($expression:expr) => { - match $expression { - tmp => { - eprintln!( - "[{}:{}] {} = {:?}", - file!(), - line!(), - stringify!($expression), - &tmp - ); - - tmp - } - } - }; -} - +/// This macro runs a parser, extracts the next input and the parser +/// output, and positions the next input on `$input`. macro_rules! consume { (($input:ident, $parser_output:ident) = $parser_expression:expr) => { let (next_input, $parser_output) = $parser_expression; @@ -24,6 +7,32 @@ macro_rules! consume { }; } +/// This macro creates an executable instruction for the interpreter. +/// +/// # Example +/// +/// The following example creates a `foo` executable instruction, +/// which takes 2 arguments (`x` and `y`), and does something +/// mysterious by using the `interpreter::Runtime` API. +/// +/// ```rust,ignore +/// executable_instruction!( +/// foo(x: u64, y: u64, instruction_name: String) -> _ { +/// // ^ output type is purposely blank +/// // ^^^^^^^^^^^^^^^^ the instruction name, for debugging purposes +/// // ^ the `y` argument +/// // ^ the `x` argument +/// +/// // an executable instruction is a closure that takes a `Runtime` instance +/// move |runtime| -> _ { +/// // Do something. +/// +/// Ok(()) +/// } +/// ); +/// ``` +/// +/// Check the existing executable instruction to get more examples. macro_rules! executable_instruction { ($name:ident ( $($argument_name:ident: $argument_type:ty),* ) -> _ $implementation:block ) => { use crate::interpreter::{ExecutableInstruction, wasm, stack::Stackable}; From b3c102da37372c0008d98f924c92af0f6d8540b9 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 12 Feb 2020 15:59:41 +0100 Subject: [PATCH 64/78] doc(interface-types) Improve documentation of the `instruction` module. Also, rename `RepeatWhile` to `RepeatUntil`. --- lib/interface-types/src/decoders/binary.rs | 6 +-- lib/interface-types/src/encoders/wat.rs | 8 ++-- .../src/interpreter/instruction.rs | 42 ++++++++++++++++++- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 2eb914b3abf..afa549ab287 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -246,7 +246,7 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( 0x14 => { consume!((input, argument_0) = leb(input)?); consume!((input, argument_1) = leb(input)?); - (input, Instruction::RepeatWhile(argument_0, argument_1)) + (input, Instruction::RepeatUntil(argument_0, argument_1)) } _ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))), @@ -643,7 +643,7 @@ mod tests { 0x11, 0x7f, 0x03, 0x61, 0x62, 0x63, // Load(I32, "abc") 0x12, 0x7f, // SeqNew(I32) 0x13, // ListPush - 0x14, 0x01, 0x02, // RepeatWhile(1, 2) + 0x14, 0x01, 0x02, // RepeatUntil(1, 2) 0x0a, ]; let output = Ok(( @@ -670,7 +670,7 @@ mod tests { Instruction::Load(InterfaceType::I32, "abc"), Instruction::SeqNew(InterfaceType::I32), Instruction::ListPush, - Instruction::RepeatWhile(1, 2), + Instruction::RepeatUntil(1, 2), ], )); diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index fcdc9161b16..08573295929 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -154,8 +154,8 @@ impl<'input> From<&Instruction<'input>> for String { format!("seq.new {}", String::from(interface_type)) } Instruction::ListPush => "list.push".into(), - Instruction::RepeatWhile(condition_index, step_index) => { - format!("repeat-while {} {}", condition_index, step_index) + Instruction::RepeatUntil(condition_index, step_index) => { + format!("repeat-until {} {}", condition_index, step_index) } } } @@ -420,7 +420,7 @@ mod tests { (&Instruction::Load(InterfaceType::Int, "foo")).into(), (&Instruction::SeqNew(InterfaceType::Int)).into(), (&Instruction::ListPush).into(), - (&Instruction::RepeatWhile(1, 2)).into(), + (&Instruction::RepeatUntil(1, 2)).into(), ]; let outputs = vec![ "arg.get 7", @@ -442,7 +442,7 @@ mod tests { r#"load Int "foo""#, "seq.new Int", "list.push", - "repeat-while 1 2", + "repeat-until 1 2", ]; assert_eq!(inputs, outputs); diff --git a/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs index e474d2daf36..20fbd40341a 100644 --- a/lib/interface-types/src/interpreter/instruction.rs +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -1,25 +1,65 @@ use crate::ast::InterfaceType; +/// Represents all the possible WIT instructions. #[derive(PartialEq, Debug)] pub enum Instruction<'input> { + /// `arg.get` ArgumentGet { index: u64 }, + + /// `call` Call { function_index: usize }, + + /// `call-export` CallExport { export_name: &'input str }, + + /// `read-utf8` ReadUtf8, + + /// `write-utf8` WriteUtf8 { allocator_name: &'input str }, + + /// `as-wasm` AsWasm(InterfaceType), + + /// `as-interface` AsInterface(InterfaceType), + + /// `table-ref-add` TableRefAdd, + + /// `table-ref-get` TableRefGet, + + /// `call-method` CallMethod(u64), + + /// `make-record` MakeRecord(InterfaceType), + + /// `get-field` GetField(InterfaceType, u64), + + /// `const` Const(InterfaceType, u64), + + /// `fold-seq` FoldSeq(u64), + + /// `add` Add(InterfaceType), + + /// `mem-to-seq` MemToSeq(InterfaceType, &'input str), + + /// `load` Load(InterfaceType, &'input str), + + /// `seq.new` SeqNew(InterfaceType), + + /// `list.push` ListPush, - RepeatWhile(u64, u64), + + /// `repeat-until` + RepeatUntil(u64, u64), } From 6b0e43b7c48d436597c0b35d9f35f2e243099811 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 12 Feb 2020 16:01:33 +0100 Subject: [PATCH 65/78] doc(interface-types) Improve the documentation of the `instruction` module. --- .../src/interpreter/instruction.rs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs index 20fbd40341a..ddf8be627bd 100644 --- a/lib/interface-types/src/interpreter/instruction.rs +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -3,63 +3,63 @@ use crate::ast::InterfaceType; /// Represents all the possible WIT instructions. #[derive(PartialEq, Debug)] pub enum Instruction<'input> { - /// `arg.get` + /// The `arg.get` instruction. ArgumentGet { index: u64 }, - /// `call` + /// The `call` instruction. Call { function_index: usize }, - /// `call-export` + /// The `call-export` instruction. CallExport { export_name: &'input str }, - /// `read-utf8` + /// The `read-utf8` instruction. ReadUtf8, - /// `write-utf8` + /// The `write-utf8` instruction. WriteUtf8 { allocator_name: &'input str }, - /// `as-wasm` + /// The `as-wasm` instruction. AsWasm(InterfaceType), - /// `as-interface` + /// The `as-interface` instruction. AsInterface(InterfaceType), - /// `table-ref-add` + /// The `table-ref-add` instruction. TableRefAdd, - /// `table-ref-get` + /// The `table-ref-get` instruction. TableRefGet, - /// `call-method` + /// The `call-method` instruction. CallMethod(u64), - /// `make-record` + /// The `make-record` instruction. MakeRecord(InterfaceType), - /// `get-field` + /// The `get-field` instruction. GetField(InterfaceType, u64), - /// `const` + /// The `const` instruction. Const(InterfaceType, u64), - /// `fold-seq` + /// The `fold-seq` instruction. FoldSeq(u64), - /// `add` + /// The `add` instruction. Add(InterfaceType), - /// `mem-to-seq` + /// The `mem-to-seq` instruction. MemToSeq(InterfaceType, &'input str), - /// `load` + /// The `load` instruction. Load(InterfaceType, &'input str), - /// `seq.new` + /// The `seq.new` instruction. SeqNew(InterfaceType), - /// `list.push` + /// The `list.push` instruction. ListPush, - /// `repeat-until` + /// The `repeat-until` instruction. RepeatUntil(u64, u64), } From 13b9cb3995e91d4db9c4557d770ab1332a731b8a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 12 Feb 2020 17:37:06 +0100 Subject: [PATCH 66/78] doc(interface-types) Improve the documentation of the `interpreter` module. --- lib/interface-types/src/interpreter/mod.rs | 96 +++++++++++++++++++ .../src/interpreter/wasm/mod.rs | 4 + 2 files changed, 100 insertions(+) diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index d9452abce6d..26916dfe31a 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -10,6 +10,8 @@ use stack::Stack; use std::{convert::TryFrom, marker::PhantomData}; use wasm::values::InterfaceValue; +/// Represents the `Runtime`, which is used by an adapter to execute +/// its instructions. pub(crate) struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory, MemoryView> where Export: wasm::structures::Export + 'instance, @@ -18,16 +20,104 @@ where MemoryView: wasm::structures::MemoryView, Instance: wasm::structures::Instance + 'instance, { + /// The invocation inputs are all the arguments received by an + /// adapter. invocation_inputs: &'invocation [InterfaceValue], + + /// Each runtime (so adapter) has its own stack instance. stack: Stack, + + /// The WebAssembly module instance. It is used by adapter's + /// instructions. wasm_instance: &'instance mut Instance, + + /// Phantom data. _phantom: PhantomData<(Export, LocalImport, Memory, MemoryView)>, } +/// Type alias for an executable instruction. It's an implementation +/// details, but an instruction is a boxed closure instance. pub(crate) type ExecutableInstruction = Box< dyn Fn(&mut Runtime) -> Result<(), String>, >; +/// An interpreter is the central piece of this crate. It is a set of +/// executable instructions. Each instruction takes the runtime as +/// argument. The runtime holds the invocation inputs, the stack, and +/// the WebAssembly instance. +/// +/// When the interpreter executes the instructions, each of them can +/// query the WebAssembly instance, operates on the stack, or reads +/// the invocation inputs. At the end of the execution, the stack +/// supposedly contains a result. Since an interpreter is used by a +/// WIT adapter to execute its instructions, the result on the stack +/// is the result of the adapter. +/// +/// # Example +/// +/// ```rust,ignore +/// use std::{cell::Cell, collections::HashMap, convert::TryInto}; +/// use wasmer_interface_types::interpreter::{ +/// instructions::tests::{Export, Instance, LocalImport, Memory, MemoryView}, +/// // ^^^^^^^^^^^^ This is private and for testing purposes only. +/// // It is basically a fake WebAssembly runtime. +/// stack::Stackable, +/// wasm::values::{InterfaceType, InterfaceValue}, +/// Instruction, Interpreter, +/// }; +/// +/// # fn main() { +/// // 1. Creates an interpreter from a set of instructions. They will +/// // be transformed into executable instructions. +/// let interpreter: Interpreter = (&vec![ +/// Instruction::ArgumentGet { index: 1 }, +/// Instruction::ArgumentGet { index: 0 }, +/// Instruction::CallExport { export_name: "sum" }, +/// ]) +/// .try_into() +/// .unwrap(); +/// +/// // 2. Defines the arguments of the adapter. +/// let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)]; +/// +/// // 3. Creates a WebAssembly instance. +/// let mut instance = Instance { +/// // 3.1. Defines one exported function: `fn sum(a: i32, b: i32) -> i32 { a + b }`. +/// exports: { +/// let mut hashmap = HashMap::new(); +/// hashmap.insert( +/// "sum".into(), +/// Export { +/// // Defines the argument types of the function. +/// inputs: vec![InterfaceType::I32, InterfaceType::I32], +/// +/// // Defines the result types. +/// outputs: vec![InterfaceType::I32], +/// +/// // Defines the function implementation. +/// 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)]) +/// }, +/// }, +/// ); +/// }, +/// ..Default::default() +/// }; +/// +/// // 4. Executes the instructions. +/// let run = interpreter.run(&invocation_inputs, &mut instance); +/// +/// assert!(run.is_ok()); +/// +/// let stack = run.unwrap(); +/// +/// // 5. Read the stack to get the result. +/// assert_eq!(stack.as_slice(), &[InterfaceValue::I32(7)]); +/// # } +/// ``` pub struct Interpreter where Export: wasm::structures::Export, @@ -57,6 +147,11 @@ where self.executable_instructions.iter() } + /// Runs the interpreter, such as: + /// 1. Create a fresh stack, + /// 2. Create a fresh stack, + /// 3. Execute the instructions one after the other, and + /// returns the stack. pub fn run( &self, invocation_inputs: &[InterfaceValue], @@ -80,6 +175,7 @@ where } } +/// Transforms a `Vec` into an `Interpreter`. impl<'binary_input, Instance, Export, LocalImport, Memory, MemoryView> TryFrom<&Vec>> for Interpreter diff --git a/lib/interface-types/src/interpreter/wasm/mod.rs b/lib/interface-types/src/interpreter/wasm/mod.rs index 0ff75a9afc6..1edccc59268 100644 --- a/lib/interface-types/src/interpreter/wasm/mod.rs +++ b/lib/interface-types/src/interpreter/wasm/mod.rs @@ -1,2 +1,6 @@ +//! An hypothetic WebAssembly runtime, represented as a set of enums, +//! types, and traits —basically this is the part a runtime should +//! take a look to use the `wasmer-interface-types` crate—. + pub mod structures; pub mod values; From b7941f401957a831a7e1d15003888d8290bd6e23 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 11:24:29 +0100 Subject: [PATCH 67/78] feat(interface-types) Ensure `ast::Type` is always well-formed. As @MarkMcCaskey noted, `Type` can be corrupted because `field_names` and `field_types` must have the same length. This patch removes the public visibility, and adds methods like `new`, `add_field`, `field_names` and `field_types` to encapsulate `Type` internal data. --- lib/interface-types/src/ast.rs | 40 ++++++++++++++++++++-- lib/interface-types/src/decoders/binary.rs | 36 +++++++++---------- 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs index 4adf2f0227f..d8b82f588e7 100644 --- a/lib/interface-types/src/ast.rs +++ b/lib/interface-types/src/ast.rs @@ -87,10 +87,46 @@ pub struct Type<'input> { pub name: &'input str, /// The field names. - pub fields: Vec<&'input str>, + field_names: Vec<&'input str>, /// The field types. - pub types: Vec, + field_types: Vec, +} + +impl<'input> Type<'input> { + /// Creates a new `Type`. + /// + /// The constructor panics if there is the length of `names` is + /// different than the length of `types`. + pub fn new(type_name: &'input str, names: Vec<&'input str>, types: Vec) -> Self { + assert_eq!( + names.len(), + types.len(), + "There must be the same number of field names than field types." + ); + + Self { + name: type_name, + field_names: names, + field_types: types, + } + } + + /// Adds a new field to the type. + pub fn add_field(&mut self, name: &'input str, ty: InterfaceType) { + self.field_names.push(name); + self.field_types.push(ty); + } + + /// Returns the field names. + pub fn field_names(&self) -> &Vec<&'input str> { + &self.field_names + } + + /// Returns the field types. + pub fn field_types(&self) -> &Vec { + &self.field_types + } } /// Represents an adapter. diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index afa549ab287..027e8e5fc65 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -293,11 +293,7 @@ fn types<'input, E: ParseError<&'input [u8]>>( consume!((input, type_fields) = list(input, string)?); consume!((input, type_types) = list(input, ty)?); - types.push(Type { - name: type_name, - fields: type_fields, - types: type_types, - }); + types.push(Type::new(type_name, type_fields, type_types)); } Ok((input, types)) @@ -479,11 +475,11 @@ fn forwards<'input, E: ParseError<&'input [u8]>>( /// input_types: vec![InterfaceType::I32], /// output_types: vec![InterfaceType::I32], /// }], -/// types: vec![Type { -/// name: "ab", -/// fields: vec!["cd", "e"], -/// types: vec![InterfaceType::I32, InterfaceType::I32], -/// }], +/// types: vec![Type::new( +/// "ab", +/// vec!["cd", "e"], +/// vec![InterfaceType::I32, InterfaceType::I32], +/// )], /// imports: vec![Import { /// namespace: "a", /// name: "b", @@ -728,11 +724,11 @@ mod tests { ]; let output = Ok(( &[] as &[u8], - vec![Type { - name: "ab", - fields: vec!["cd", "e"], - types: vec![InterfaceType::I32, InterfaceType::I32], - }], + vec![Type::new( + "ab", + vec!["cd", "e"], + vec![InterfaceType::I32, InterfaceType::I32], + )], )); assert_eq!(types::<()>(input), output); @@ -913,11 +909,11 @@ mod tests { input_types: vec![InterfaceType::I32], output_types: vec![InterfaceType::I32], }], - types: vec![Type { - name: "ab", - fields: vec!["cd", "e"], - types: vec![InterfaceType::I32, InterfaceType::I32], - }], + types: vec![Type::new( + "ab", + vec!["cd", "e"], + vec![InterfaceType::I32, InterfaceType::I32], + )], imports: vec![Import { namespace: "a", name: "b", From 1ad42d81cb7b39240adb65247bea942603e8cb5f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 11:41:02 +0100 Subject: [PATCH 68/78] doc(interface-types) Improve documentation of `decoders::binary::leb`. --- lib/interface-types/src/decoders/binary.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 027e8e5fc65..e0a3fad1ae1 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -51,7 +51,9 @@ fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'i Ok((&input[1..], input[0])) } -/// Parse an unsigned LEB with value no larger than a 64-bits number. +/// Parse an unsigned Little Endian Based (LEB) with value no larger +/// than a 64-bits number. Read +/// [LEB128](https://en.wikipedia.org/wiki/LEB128) to learn more. fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); From 370fd6d86675d3255dbbed3f012fecb39a28d34a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 13:36:18 +0100 Subject: [PATCH 69/78] feat(decoders) Improve LEB parser in the `binary` module. The LEB parser is renamed `uleb`. It now checks for overflow, and badly-formed bits, resp. `TooLarge` or `Eof`. More test cases are added, whose from the DWARF 4 standard. --- lib/interface-types/src/decoders/binary.rs | 99 +++++++++++++++++----- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index e0a3fad1ae1..64cf1e7c719 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -53,15 +53,18 @@ fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'i /// Parse an unsigned Little Endian Based (LEB) with value no larger /// than a 64-bits number. Read -/// [LEB128](https://en.wikipedia.org/wiki/LEB128) to learn more. -fn leb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { +/// [LEB128](https://en.wikipedia.org/wiki/LEB128) to learn more, or +/// the Variable Length Data Section from the [DWARF 4 +/// standard](http://dwarfstd.org/doc/DWARF4.pdf). +fn uleb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> { if input.is_empty() { return Err(Err::Error(make_error(input, ErrorKind::Eof))); } - let (output, bytes) = match input.iter().position(|&byte| byte & 0x80 == 0) { - Some(position) => (&input[position + 1..], &input[..=position]), - None => (&[] as &[u8], input), + let (output, bytes) = match dbg!(input.iter().position(|&byte| byte & 0x80 == 0)) { + Some(length) if length <= 8 => (&input[length + 1..], &input[..=length]), + Some(_) => return Err(Err::Error(make_error(input, ErrorKind::TooLarge))), + None => return Err(Err::Error(make_error(input, ErrorKind::Eof))), }; Ok(( @@ -128,7 +131,7 @@ fn ty<'input, E: ParseError<&'input [u8]>>( return Err(Err::Error(make_error(input, ErrorKind::Eof))); } - let (output, ty) = leb(input)?; + let (output, ty) = uleb(input)?; match InterfaceType::try_from(ty) { Ok(ty) => Ok((output, ty)), @@ -144,12 +147,12 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( Ok(match opcode { 0x00 => { - consume!((input, argument_0) = leb(input)?); + consume!((input, argument_0) = uleb(input)?); (input, Instruction::ArgumentGet { index: argument_0 }) } 0x01 => { - consume!((input, argument_0) = leb(input)?); + consume!((input, argument_0) = uleb(input)?); ( input, Instruction::Call { @@ -195,7 +198,7 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( 0x08 => (input, Instruction::TableRefGet), 0x09 => { - consume!((input, argument_0) = leb(input)?); + consume!((input, argument_0) = uleb(input)?); (input, Instruction::CallMethod(argument_0)) } @@ -206,18 +209,18 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( 0x0c => { consume!((input, argument_0) = ty(input)?); - consume!((input, argument_1) = leb(input)?); + consume!((input, argument_1) = uleb(input)?); (input, Instruction::GetField(argument_0, argument_1)) } 0x0d => { consume!((input, argument_0) = ty(input)?); - consume!((input, argument_1) = leb(input)?); + consume!((input, argument_1) = uleb(input)?); (input, Instruction::Const(argument_0, argument_1)) } 0x0e => { - consume!((input, argument_0) = leb(input)?); + consume!((input, argument_0) = uleb(input)?); (input, Instruction::FoldSeq(argument_0)) } @@ -246,8 +249,8 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( 0x13 => (input, Instruction::ListPush), 0x14 => { - consume!((input, argument_0) = leb(input)?); - consume!((input, argument_1) = leb(input)?); + consume!((input, argument_0) = uleb(input)?); + consume!((input, argument_1) = uleb(input)?); (input, Instruction::RepeatUntil(argument_0, argument_1)) } @@ -261,7 +264,7 @@ fn exports<'input, E: ParseError<&'input [u8]>>( ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - consume!((input, number_of_exports) = leb(input)?); + consume!((input, number_of_exports) = uleb(input)?); let mut exports = Vec::with_capacity(number_of_exports as usize); @@ -286,7 +289,7 @@ fn types<'input, E: ParseError<&'input [u8]>>( ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - consume!((input, number_of_types) = leb(input)?); + consume!((input, number_of_types) = uleb(input)?); let mut types = Vec::with_capacity(number_of_types as usize); @@ -307,7 +310,7 @@ fn imports<'input, E: ParseError<&'input [u8]>>( ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - consume!((input, number_of_imports) = leb(input)?); + consume!((input, number_of_imports) = uleb(input)?); let mut imports = Vec::with_capacity(number_of_imports as usize); @@ -334,7 +337,7 @@ fn adapters<'input, E: ParseError<&'input [u8]>>( ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - consume!((input, number_of_adapters) = leb(input)?); + consume!((input, number_of_adapters) = uleb(input)?); let mut adapters = Vec::with_capacity(number_of_adapters as usize); @@ -399,7 +402,7 @@ fn forwards<'input, E: ParseError<&'input [u8]>>( ) -> IResult<&'input [u8], Vec, E> { let mut input = input; - consume!((input, number_of_forwards) = leb(input)?); + consume!((input, number_of_forwards) = uleb(input)?); let mut forwards = Vec::with_capacity(number_of_forwards as usize); @@ -528,6 +531,7 @@ pub fn parse<'input, E: ParseError<&'input [u8]>>( #[cfg(test)] mod tests { use super::*; + use nom::{error, Err}; #[test] fn test_byte() { @@ -538,19 +542,68 @@ mod tests { } #[test] - fn test_leb_1_byte() { + fn test_uleb_1_byte() { let input = &[0x01, 0x02, 0x03]; let output = Ok((&[0x02, 0x03][..], 0x01u64)); - assert_eq!(leb::<()>(input), output); + assert_eq!(uleb::<()>(input), output); } #[test] - fn test_leb_3_bytes() { + fn test_uleb_3_bytes() { let input = &[0xfc, 0xff, 0x01, 0x02]; let output = Ok((&[0x02][..], 0x7ffcu64)); - assert_eq!(leb::<()>(input), output); + assert_eq!(uleb::<()>(input), output); + } + + // Examples from Figure 22 of [DWARF 4 + // standard](http://dwarfstd.org/doc/DWARF4.pdf). + #[test] + fn test_uleb_from_dwarf_standard() { + macro_rules! assert_uleb { + ($to_parse:expr => $expected_result:expr) => { + assert_eq!(uleb::<()>($to_parse), Ok((&[][..], $expected_result))); + }; + } + + assert_uleb!(&[2u8] => 2u64); + assert_uleb!(&[127u8] => 127u64); + assert_uleb!(&[0x80, 1u8] => 128u64); + assert_uleb!(&[1u8 | 0x80, 1] => 129u64); + assert_uleb!(&[2u8 | 0x80, 1] => 130u64); + assert_uleb!(&[57u8 | 0x80, 100] => 12857u64); + } + + #[test] + fn test_uleb_eof() { + let input = &[0x80]; + + assert_eq!( + uleb::<(&[u8], error::ErrorKind)>(input), + Err(Err::Error((&input[..], error::ErrorKind::Eof))), + ); + } + + #[test] + fn test_uleb_overflow() { + let input = &[ + 0x01 | 0x80, + 0x02 | 0x80, + 0x03 | 0x80, + 0x04 | 0x80, + 0x05 | 0x80, + 0x06 | 0x80, + 0x07 | 0x80, + 0x08 | 0x80, + 0x09 | 0x80, + 0x0a, + ]; + + assert_eq!( + uleb::<(&[u8], error::ErrorKind)>(input), + Err(Err::Error((&input[..], error::ErrorKind::TooLarge))), + ); } #[test] From 3f0c32bf44bfb735749ed20a17eeac82884d8792 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 13:52:23 +0100 Subject: [PATCH 70/78] fix(decoders) Remove the `unsafe` block in the `string` parser. Instead of using `str::from_utf8_unchecked`, this patch updates the code to use `str::from_utf8` and handles the error appropriately. --- lib/interface-types/src/decoders/binary.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 64cf1e7c719..642635e3a97 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -91,9 +91,11 @@ fn string<'input, E: ParseError<&'input [u8]>>( return Err(Err::Error(make_error(input, ErrorKind::Eof))); } - Ok((&input[length..], unsafe { - str::from_utf8_unchecked(&input[..length]) - })) + Ok(( + &input[length..], + str::from_utf8(&input[..length]) + .map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?, + )) } /// Parse a list, with a item parser. From d8f923091f89686faa99fc7a8af378873f86c514 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 13:54:26 +0100 Subject: [PATCH 71/78] chore(decoders) Simplify code. --- lib/interface-types/src/decoders/binary.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index 642635e3a97..ce5c2c04b9a 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -400,10 +400,8 @@ fn adapters<'input, E: ParseError<&'input [u8]>>( /// Parse a list of forwarded exports. fn forwards<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], + mut input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - consume!((input, number_of_forwards) = uleb(input)?); let mut forwards = Vec::with_capacity(number_of_forwards as usize); From 722727bd56426ada1625dde3c3cce4cbaf00fb25 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 13:56:30 +0100 Subject: [PATCH 72/78] chore(decoders) Simplify code. --- lib/interface-types/src/decoders/binary.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs index ce5c2c04b9a..270fc13a725 100644 --- a/lib/interface-types/src/decoders/binary.rs +++ b/lib/interface-types/src/decoders/binary.rs @@ -262,10 +262,8 @@ fn instruction<'input, E: ParseError<&'input [u8]>>( /// Parse a list of exports. fn exports<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], + mut input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - consume!((input, number_of_exports) = uleb(input)?); let mut exports = Vec::with_capacity(number_of_exports as usize); @@ -287,10 +285,8 @@ fn exports<'input, E: ParseError<&'input [u8]>>( /// Parse a list of types. fn types<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], + mut input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - consume!((input, number_of_types) = uleb(input)?); let mut types = Vec::with_capacity(number_of_types as usize); @@ -308,10 +304,8 @@ fn types<'input, E: ParseError<&'input [u8]>>( /// Parse a list of imports. fn imports<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], + mut input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - consume!((input, number_of_imports) = uleb(input)?); let mut imports = Vec::with_capacity(number_of_imports as usize); @@ -335,10 +329,8 @@ fn imports<'input, E: ParseError<&'input [u8]>>( /// Parse a list of adapters. fn adapters<'input, E: ParseError<&'input [u8]>>( - input: &'input [u8], + mut input: &'input [u8], ) -> IResult<&'input [u8], Vec, E> { - let mut input = input; - consume!((input, number_of_adapters) = uleb(input)?); let mut adapters = Vec::with_capacity(number_of_adapters as usize); From 13cee90d17620c76512f315a061d6d5442f11bbd Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 14:19:31 +0100 Subject: [PATCH 73/78] =?UTF-8?q?feat(encoders)=20Implement=20`ToString`?= =?UTF-8?q?=20instead=20of=20`From<=E2=80=A6>=20for=20String`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/interface-types/src/encoders/wat.rs | 251 ++++++++++----------- lib/interface-types/src/interpreter/mod.rs | 2 +- 2 files changed, 125 insertions(+), 128 deletions(-) diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index 08573295929..bc4ab5b5925 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -55,7 +55,7 @@ //! ], //! forwards: vec![Forward { name: "main" }], //! }) -//! .into(); +//! .to_string(); //! let output = r#";; Interfaces //! //! ;; Interface, Export foo @@ -92,11 +92,12 @@ use crate::{ ast::{Adapter, Export, Forward, Import, InterfaceType, Interfaces, Type}, interpreter::Instruction, }; +use std::string::ToString; /// Encode an `InterfaceType` into a string. -impl From<&InterfaceType> for String { - fn from(interface_type: &InterfaceType) -> Self { - match interface_type { +impl ToString for &InterfaceType { + fn to_string(&self) -> String { + match self { InterfaceType::Int => "Int".into(), InterfaceType::Float => "Float".into(), InterfaceType::Any => "Any".into(), @@ -112,9 +113,9 @@ impl From<&InterfaceType> for String { } /// Encode an `Instruction` into a string. -impl<'input> From<&Instruction<'input>> for String { - fn from(instruction: &Instruction) -> Self { - match instruction { +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), @@ -123,35 +124,33 @@ impl<'input> From<&Instruction<'input>> for String { format!(r#"write-utf8 "{}""#, allocator_name) } Instruction::AsWasm(interface_type) => { - format!("as-wasm {}", String::from(interface_type)) + format!("as-wasm {}", interface_type.to_string()) } Instruction::AsInterface(interface_type) => { - format!("as-interface {}", String::from(interface_type)) + format!("as-interface {}", interface_type.to_string()) } Instruction::TableRefAdd => "table-ref-add".into(), Instruction::TableRefGet => "table-ref-get".into(), Instruction::CallMethod(index) => format!("call-method {}", index), Instruction::MakeRecord(interface_type) => { - format!("make-record {}", String::from(interface_type)) + format!("make-record {}", interface_type.to_string()) } Instruction::GetField(interface_type, field_index) => { - format!("get-field {} {}", String::from(interface_type), field_index) + format!("get-field {} {}", interface_type.to_string(), field_index) } Instruction::Const(interface_type, value) => { - format!("const {} {}", String::from(interface_type), value) + format!("const {} {}", interface_type.to_string(), value) } Instruction::FoldSeq(import_index) => format!("fold-seq {}", import_index), - Instruction::Add(interface_type) => format!("add {}", String::from(interface_type)), - Instruction::MemToSeq(interface_type, memory) => format!( - r#"mem-to-seq {} "{}""#, - String::from(interface_type), - memory - ), + Instruction::Add(interface_type) => format!("add {}", interface_type.to_string()), + Instruction::MemToSeq(interface_type, memory) => { + format!(r#"mem-to-seq {} "{}""#, interface_type.to_string(), memory) + } Instruction::Load(interface_type, memory) => { - format!(r#"load {} "{}""#, String::from(interface_type), memory) + format!(r#"load {} "{}""#, interface_type.to_string(), memory) } Instruction::SeqNew(interface_type) => { - format!("seq.new {}", String::from(interface_type)) + format!("seq.new {}", interface_type.to_string()) } Instruction::ListPush => "list.push".into(), Instruction::RepeatUntil(condition_index, step_index) => { @@ -173,7 +172,7 @@ fn input_types_to_param(input_types: &[InterfaceType]) -> String { .iter() .fold(String::new(), |mut accumulator, interface_type| { accumulator.push(' '); - accumulator.push_str(&String::from(interface_type)); + accumulator.push_str(&interface_type.to_string()); accumulator }) ) @@ -192,7 +191,7 @@ fn output_types_to_result(output_types: &[InterfaceType]) -> String { .iter() .fold(String::new(), |mut accumulator, interface_type| { accumulator.push(' '); - accumulator.push_str(&String::from(interface_type)); + accumulator.push_str(&interface_type.to_string()); accumulator }) ) @@ -200,41 +199,41 @@ fn output_types_to_result(output_types: &[InterfaceType]) -> String { } /// Encode an `Export` into a string. -impl<'input> From<&Export<'input>> for String { - fn from(export: &Export) -> Self { +impl<'input> ToString for &Export<'input> { + fn to_string(&self) -> String { format!( r#"(@interface export "{name}"{inputs}{outputs})"#, - name = export.name, - inputs = input_types_to_param(&export.input_types), - outputs = output_types_to_result(&export.output_types), + name = self.name, + inputs = input_types_to_param(&self.input_types), + outputs = output_types_to_result(&self.output_types), ) } } /// Encode a `Type` into a string. -impl<'input> From<&Type<'input>> for String { - fn from(_ty: &Type) -> Self { +impl<'input> ToString for &Type<'input> { + fn to_string(&self) -> String { unimplemented!() } } /// Encode an `Import` into a string. -impl<'input> From<&Import<'input>> for String { - fn from(import: &Import) -> Self { +impl<'input> ToString for &Import<'input> { + fn to_string(&self) -> String { format!( r#"(@interface func ${namespace}_{name} (import "{namespace}" "{name}"){inputs}{outputs})"#, - namespace = import.namespace, - name = import.name, - inputs = input_types_to_param(&import.input_types), - outputs = output_types_to_result(&import.output_types), + namespace = self.namespace, + name = self.name, + inputs = input_types_to_param(&self.input_types), + outputs = output_types_to_result(&self.output_types), ) } } /// Encode an `Adapter` into a string. -impl<'input> From<&Adapter<'input>> for String { - fn from(adapter: &Adapter) -> Self { - match adapter { +impl<'input> ToString for &Adapter<'input> { + fn to_string(&self) -> String { + match self { Adapter::Import { namespace, name, @@ -252,7 +251,7 @@ impl<'input> From<&Adapter<'input>> for String { .iter() .fold(String::new(), |mut accumulator, instruction| { accumulator.push_str("\n "); - accumulator.push_str(&String::from(instruction)); + accumulator.push_str(&instruction.to_string()); accumulator }), ), @@ -272,7 +271,7 @@ impl<'input> From<&Adapter<'input>> for String { .iter() .fold(String::new(), |mut accumulator, instruction| { accumulator.push_str("\n "); - accumulator.push_str(&String::from(instruction)); + accumulator.push_str(&instruction.to_string()); accumulator }), ), @@ -283,39 +282,39 @@ impl<'input> From<&Adapter<'input>> for String { } /// Encode a `Forward` into a string. -impl<'input> From<&Forward<'input>> for String { - fn from(forward: &Forward) -> Self { +impl<'input> ToString for &Forward<'input> { + fn to_string(&self) -> String { format!( r#"(@interface forward (export "{name}"))"#, - name = forward.name, + name = self.name, ) } } /// Encode an `Interfaces` into a string. -impl<'input> From<&Interfaces<'input>> for String { - fn from(interfaces: &Interfaces) -> Self { +impl<'input> ToString for &Interfaces<'input> { + fn to_string(&self) -> String { let mut output = String::from(";; Interfaces"); - let exports = interfaces + let exports = self .exports .iter() .fold(String::new(), |mut accumulator, export| { accumulator.push_str(&format!("\n\n;; Interface, Export {}\n", export.name)); - accumulator.push_str(&String::from(export)); + accumulator.push_str(&export.to_string()); accumulator }); - let types = interfaces + let types = self .types .iter() .fold(String::new(), |mut accumulator, ty| { accumulator.push_str(&format!("\n\n;; Interface, Ty {}\n", ty.name)); - accumulator.push_str(&String::from(ty)); + accumulator.push_str(&ty.to_string()); accumulator }); - let imports = interfaces + let imports = self .imports .iter() .fold(String::new(), |mut accumulator, import| { @@ -323,42 +322,40 @@ impl<'input> From<&Interfaces<'input>> for String { "\n\n;; Interface, Import {}.{}\n", import.namespace, import.name )); - accumulator.push_str(&String::from(import)); + accumulator.push_str(&import.to_string()); accumulator }); - let adapters = - interfaces - .adapters - .iter() - .fold(String::new(), |mut accumulator, adapter| { - match adapter { - Adapter::Import { - namespace, name, .. - } => accumulator.push_str(&format!( - "\n\n;; Interface, Adapter {}.{}\n", - namespace, name - )), - - Adapter::Export { name, .. } => { - accumulator.push_str(&format!("\n\n;; Interface, Adapter {}\n", name)) - } - - _ => unimplemented!(), + let adapters = self + .adapters + .iter() + .fold(String::new(), |mut accumulator, adapter| { + match adapter { + Adapter::Import { + namespace, name, .. + } => accumulator.push_str(&format!( + "\n\n;; Interface, Adapter {}.{}\n", + namespace, name + )), + + Adapter::Export { name, .. } => { + accumulator.push_str(&format!("\n\n;; Interface, Adapter {}\n", name)) } - accumulator.push_str(&String::from(adapter)); - accumulator - }); - let forwards = - interfaces - .forwards - .iter() - .fold(String::new(), |mut accumulator, forward| { - accumulator.push_str(&format!("\n\n;; Interface, Forward {}\n", forward.name)); - accumulator.push_str(&String::from(forward)); - accumulator - }); + _ => unimplemented!(), + } + accumulator.push_str(&adapter.to_string()); + accumulator + }); + + let forwards = self + .forwards + .iter() + .fold(String::new(), |mut accumulator, forward| { + accumulator.push_str(&format!("\n\n;; Interface, Forward {}\n", forward.name)); + accumulator.push_str(&forward.to_string()); + accumulator + }); output.push_str(&exports); output.push_str(&types); @@ -377,16 +374,16 @@ mod tests { #[test] fn test_interface_types() { let inputs: Vec = vec![ - (&InterfaceType::Int).into(), - (&InterfaceType::Float).into(), - (&InterfaceType::Any).into(), - (&InterfaceType::String).into(), - (&InterfaceType::Seq).into(), - (&InterfaceType::I32).into(), - (&InterfaceType::I64).into(), - (&InterfaceType::F32).into(), - (&InterfaceType::F64).into(), - (&InterfaceType::AnyRef).into(), + (&InterfaceType::Int).to_string(), + (&InterfaceType::Float).to_string(), + (&InterfaceType::Any).to_string(), + (&InterfaceType::String).to_string(), + (&InterfaceType::Seq).to_string(), + (&InterfaceType::I32).to_string(), + (&InterfaceType::I64).to_string(), + (&InterfaceType::F32).to_string(), + (&InterfaceType::F64).to_string(), + (&InterfaceType::AnyRef).to_string(), ]; let outputs = vec![ "Int", "Float", "Any", "String", "Seq", "i32", "i64", "f32", "f64", "anyref", @@ -398,29 +395,29 @@ mod tests { #[test] fn test_instructions() { let inputs: Vec = vec![ - (&Instruction::ArgumentGet { index: 7 }).into(), - (&Instruction::Call { function_index: 7 }).into(), - (&Instruction::CallExport { export_name: "foo" }).into(), - (&Instruction::ReadUtf8).into(), + (&Instruction::ArgumentGet { index: 7 }).to_string(), + (&Instruction::Call { function_index: 7 }).to_string(), + (&Instruction::CallExport { export_name: "foo" }).to_string(), + (&Instruction::ReadUtf8).to_string(), (&Instruction::WriteUtf8 { allocator_name: "foo", }) - .into(), - (&Instruction::AsWasm(InterfaceType::Int)).into(), - (&Instruction::AsInterface(InterfaceType::AnyRef)).into(), - (&Instruction::TableRefAdd).into(), - (&Instruction::TableRefGet).into(), - (&Instruction::CallMethod(7)).into(), - (&Instruction::MakeRecord(InterfaceType::Int)).into(), - (&Instruction::GetField(InterfaceType::Int, 7)).into(), - (&Instruction::Const(InterfaceType::I32, 7)).into(), - (&Instruction::FoldSeq(7)).into(), - (&Instruction::Add(InterfaceType::Int)).into(), - (&Instruction::MemToSeq(InterfaceType::Int, "foo")).into(), - (&Instruction::Load(InterfaceType::Int, "foo")).into(), - (&Instruction::SeqNew(InterfaceType::Int)).into(), - (&Instruction::ListPush).into(), - (&Instruction::RepeatUntil(1, 2)).into(), + .to_string(), + (&Instruction::AsWasm(InterfaceType::Int)).to_string(), + (&Instruction::AsInterface(InterfaceType::AnyRef)).to_string(), + (&Instruction::TableRefAdd).to_string(), + (&Instruction::TableRefGet).to_string(), + (&Instruction::CallMethod(7)).to_string(), + (&Instruction::MakeRecord(InterfaceType::Int)).to_string(), + (&Instruction::GetField(InterfaceType::Int, 7)).to_string(), + (&Instruction::Const(InterfaceType::I32, 7)).to_string(), + (&Instruction::FoldSeq(7)).to_string(), + (&Instruction::Add(InterfaceType::Int)).to_string(), + (&Instruction::MemToSeq(InterfaceType::Int, "foo")).to_string(), + (&Instruction::Load(InterfaceType::Int, "foo")).to_string(), + (&Instruction::SeqNew(InterfaceType::Int)).to_string(), + (&Instruction::ListPush).to_string(), + (&Instruction::RepeatUntil(1, 2)).to_string(), ]; let outputs = vec![ "arg.get 7", @@ -456,25 +453,25 @@ mod tests { input_types: vec![InterfaceType::I32, InterfaceType::F32], output_types: vec![InterfaceType::I32], }) - .into(), + .to_string(), (&Export { name: "foo", input_types: vec![InterfaceType::I32], output_types: vec![], }) - .into(), + .to_string(), (&Export { name: "foo", input_types: vec![], output_types: vec![InterfaceType::I32], }) - .into(), + .to_string(), (&Export { name: "foo", input_types: vec![], output_types: vec![], }) - .into(), + .to_string(), ]; let outputs = vec![ r#"(@interface export "foo" @@ -499,28 +496,28 @@ mod tests { input_types: vec![InterfaceType::Int, InterfaceType::String], output_types: vec![InterfaceType::String], }) - .into(), + .to_string(), (&Import { namespace: "ns", name: "foo", input_types: vec![InterfaceType::String], output_types: vec![], }) - .into(), + .to_string(), (&Import { namespace: "ns", name: "foo", input_types: vec![], output_types: vec![InterfaceType::String], }) - .into(), + .to_string(), (&Import { namespace: "ns", name: "foo", input_types: vec![], output_types: vec![], }) - .into(), + .to_string(), ]; let outputs = vec![ r#"(@interface func $ns_foo (import "ns" "foo") @@ -552,7 +549,7 @@ mod tests { Instruction::CallExport { export_name: "f" }, ], }) - .into(), + .to_string(), (&Adapter::Import { namespace: "ns", name: "foo", @@ -560,7 +557,7 @@ mod tests { output_types: vec![], instructions: vec![Instruction::CallExport { export_name: "f" }], }) - .into(), + .to_string(), (&Adapter::Import { namespace: "ns", name: "foo", @@ -568,7 +565,7 @@ mod tests { output_types: vec![InterfaceType::I32], instructions: vec![Instruction::CallExport { export_name: "f" }], }) - .into(), + .to_string(), (&Adapter::Export { name: "foo", input_types: vec![InterfaceType::I32, InterfaceType::F32], @@ -581,21 +578,21 @@ mod tests { Instruction::CallExport { export_name: "f" }, ], }) - .into(), + .to_string(), (&Adapter::Export { name: "foo", input_types: vec![InterfaceType::I32], output_types: vec![], instructions: vec![Instruction::CallExport { export_name: "f" }], }) - .into(), + .to_string(), (&Adapter::Export { name: "foo", input_types: vec![], output_types: vec![InterfaceType::I32], instructions: vec![Instruction::CallExport { export_name: "f" }], }) - .into(), + .to_string(), ]; let outputs = vec![ r#"(@interface adapt (import "ns" "foo") @@ -629,7 +626,7 @@ mod tests { #[test] fn test_forward() { - let input: String = (&Forward { name: "main" }).into(); + let input: String = (&Forward { name: "main" }).to_string(); let output = r#"(@interface forward (export "main"))"#; assert_eq!(input, output); @@ -682,7 +679,7 @@ mod tests { ], forwards: vec![Forward { name: "main" }], }) - .into(); + .to_string(); let output = r#";; Interfaces ;; Interface, Export foo diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs index 26916dfe31a..792c91e8973 100644 --- a/lib/interface-types/src/interpreter/mod.rs +++ b/lib/interface-types/src/interpreter/mod.rs @@ -192,7 +192,7 @@ where let executable_instructions = instructions .iter() .map(|instruction| { - let instruction_name: String = instruction.into(); + let instruction_name = instruction.to_string(); match instruction { Instruction::ArgumentGet { index } => { From 6891517c8befbb0f8abbbcf88f8cdaca658eb1ba Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 14:24:07 +0100 Subject: [PATCH 74/78] chore(encoders) Changing `unimplemented!()` to `todo!()`. --- lib/interface-types/src/encoders/wat.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs index bc4ab5b5925..08813a27136 100644 --- a/lib/interface-types/src/encoders/wat.rs +++ b/lib/interface-types/src/encoders/wat.rs @@ -213,7 +213,7 @@ impl<'input> ToString for &Export<'input> { /// Encode a `Type` into a string. impl<'input> ToString for &Type<'input> { fn to_string(&self) -> String { - unimplemented!() + todo!("To be implemented.") } } @@ -276,7 +276,7 @@ impl<'input> ToString for &Adapter<'input> { }), ), - _ => unimplemented!(), + _ => todo!("To be implemented."), } } } @@ -342,7 +342,7 @@ impl<'input> ToString for &Interfaces<'input> { accumulator.push_str(&format!("\n\n;; Interface, Adapter {}\n", name)) } - _ => unimplemented!(), + _ => todo!("To be implemented."), } accumulator.push_str(&adapter.to_string()); accumulator From ae6e26158f0713c93cfdf4ddcb476f5706a2c504 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 14:26:50 +0100 Subject: [PATCH 75/78] chore(interface-types) Simplify code. --- lib/interface-types/src/interpreter/instructions/call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interface-types/src/interpreter/instructions/call.rs b/lib/interface-types/src/interpreter/instructions/call.rs index 9f4ac022686..5fe896bb9da 100644 --- a/lib/interface-types/src/interpreter/instructions/call.rs +++ b/lib/interface-types/src/interpreter/instructions/call.rs @@ -88,7 +88,7 @@ mod tests { InterfaceValue::I32(3), InterfaceValue::I32(4), ], - instance: Instance { ..Default::default() }, + instance: Default::default(), error: r#"`call 42` cannot call the local or imported function `42` because it doesn't exist."#, ); From 95597473947446551c7b9f8da817714df689e9f7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 14:34:45 +0100 Subject: [PATCH 76/78] doc(interface-types) Add `#[deny(missing_docs)]`. --- .../src/interpreter/instruction.rs | 20 +++++++++++++++---- .../src/interpreter/wasm/structures.rs | 2 ++ .../src/interpreter/wasm/values.rs | 2 ++ lib/interface-types/src/lib.rs | 5 +++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs index ddf8be627bd..5364ac55a45 100644 --- a/lib/interface-types/src/interpreter/instruction.rs +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -4,19 +4,31 @@ use crate::ast::InterfaceType; #[derive(PartialEq, Debug)] pub enum Instruction<'input> { /// The `arg.get` instruction. - ArgumentGet { index: u64 }, + ArgumentGet { + /// The argument index. + index: u64, + }, /// The `call` instruction. - Call { function_index: usize }, + Call { + /// The function index. + function_index: usize, + }, /// The `call-export` instruction. - CallExport { export_name: &'input str }, + CallExport { + /// The exported function name. + export_name: &'input str, + }, /// The `read-utf8` instruction. ReadUtf8, /// The `write-utf8` instruction. - WriteUtf8 { allocator_name: &'input str }, + WriteUtf8 { + /// The allocator function name. + allocator_name: &'input str, + }, /// The `as-wasm` instruction. AsWasm(InterfaceType), diff --git a/lib/interface-types/src/interpreter/wasm/structures.rs b/lib/interface-types/src/interpreter/wasm/structures.rs index 7ec386f681a..dbeab832fde 100644 --- a/lib/interface-types/src/interpreter/wasm/structures.rs +++ b/lib/interface-types/src/interpreter/wasm/structures.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use super::values::{InterfaceType, InterfaceValue}; use std::{cell::Cell, ops::Deref}; diff --git a/lib/interface-types/src/interpreter/wasm/values.rs b/lib/interface-types/src/interpreter/wasm/values.rs index 632a88f65f4..f50763fc086 100644 --- a/lib/interface-types/src/interpreter/wasm/values.rs +++ b/lib/interface-types/src/interpreter/wasm/values.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use std::convert::TryFrom; pub use crate::ast::InterfaceType; diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 64dac5b561a..99a8f831291 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -33,12 +33,13 @@ #![deny( dead_code, + missing_docs, nonstandard_style, + unreachable_patterns, unused_imports, unused_mut, - unused_variables, unused_unsafe, - unreachable_patterns + unused_variables )] #![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] #![doc(html_logo_url = "https://github.com/wasmerio.png")] From eb2f9db2304f814c4d49ccf66f899e93643ed176 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 14:36:17 +0100 Subject: [PATCH 77/78] feat(interface-types) Forbids unsafe code. --- lib/interface-types/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs index 99a8f831291..f4deaba974c 100644 --- a/lib/interface-types/src/lib.rs +++ b/lib/interface-types/src/lib.rs @@ -41,6 +41,7 @@ unused_unsafe, unused_variables )] +#![forbid(unsafe_code)] #![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] #![doc(html_logo_url = "https://github.com/wasmerio.png")] From 7f8d9165d4c79b7013b43043078b6181598f582e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 13 Feb 2020 14:38:05 +0100 Subject: [PATCH 78/78] fix(interface-types) Clean up cargo features. --- lib/runtime-core/Cargo.toml | 1 - lib/runtime/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/runtime-core/Cargo.toml b/lib/runtime-core/Cargo.toml index 6f3a775cad6..7f07a9d2d77 100644 --- a/lib/runtime-core/Cargo.toml +++ b/lib/runtime-core/Cargo.toml @@ -52,4 +52,3 @@ cc = "1.0" [features] managed = [] deterministic-execution = ["wasmparser/deterministic"] -interface-types = [] diff --git a/lib/runtime/Cargo.toml b/lib/runtime/Cargo.toml index 1d51eb56394..a8dd63dbc71 100644 --- a/lib/runtime/Cargo.toml +++ b/lib/runtime/Cargo.toml @@ -52,7 +52,6 @@ singlepass = ["wasmer-singlepass-backend"] default-backend-singlepass = ["singlepass"] default-backend-llvm = ["llvm"] default-backend-cranelift = ["cranelift"] -interface-types = ["wasmer-runtime-core/interface-types"] deterministic-execution = ["wasmer-singlepass-backend/deterministic-execution", "wasmer-runtime-core/deterministic-execution"] [[bench]]