From 6c7f9ad8ec188a5bcb166569c007f370c719454e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 7 Aug 2023 12:23:36 -0700 Subject: [PATCH 1/3] Update to Wasmtime 12 This commit updates the Wasmtime dependency to the, currently unreleased, Wasmtime 12.0.0 branch. A number of changes happened to component translation in Wasmtime 12 to account for resources which required changes here as well. Additionally Wasmtime 12 disallows empty types in components which WASI was previously using, so this additionally updates the preview1 shims and test components. --- Cargo.lock | 176 +++++------------- Cargo.toml | 21 ++- .../src/transpile_bindgen.rs | 112 ++++++----- 3 files changed, 122 insertions(+), 187 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42c4e821a..2f5df24b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cranelift-entity" -version = "0.97.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6565198b5684367371e2b946ceca721eb36965e75e3592fad12fc2e15f65d7b" +version = "0.99.0" +source = "git+https://github.com/bytecodealliance/wasmtime?branch=release-12.0.0#de4ede08265e8e984a8fd34be5c0986e13b646b0" dependencies = [ "serde", ] @@ -153,7 +152,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", ] [[package]] @@ -179,7 +177,7 @@ version = "0.10.1" dependencies = [ "anyhow", "js-component-bindgen", - "wit-component 0.13.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wit-component", ] [[package]] @@ -189,10 +187,10 @@ dependencies = [ "anyhow", "base64", "heck", - "indexmap 1.9.3", + "indexmap 2.0.0", "wasmtime-environ", - "wit-component 0.13.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wit-parser 0.9.2 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wit-component", + "wit-parser", ] [[package]] @@ -225,9 +223,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "object" -version = "0.30.4" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "crc32fast", "hashbrown 0.13.2", @@ -457,15 +455,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wasm-encoder" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.31.1" @@ -475,14 +464,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-encoder" -version = "0.31.1" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-metadata" version = "0.10.1" @@ -494,22 +475,8 @@ dependencies = [ "serde", "serde_json", "spdx", - "wasm-encoder 0.31.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.110.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasm-metadata" -version = "0.10.1" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" -dependencies = [ - "anyhow", - "indexmap 2.0.0", - "serde", - "serde_json", - "spdx", - "wasm-encoder 0.31.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wasmparser 0.110.0 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wasm-encoder", + "wasmparser", ] [[package]] @@ -517,24 +484,14 @@ name = "wasm-tools-js" version = "0.1.0" dependencies = [ "anyhow", - "wasm-encoder 0.31.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wasm-metadata 0.10.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wasmparser 0.110.0 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wasmprinter 0.2.62 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wasmprinter", "wat", "wit-bindgen", - "wit-component 0.13.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wit-parser 0.9.2 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", -] - -[[package]] -name = "wasmparser" -version = "0.107.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" -dependencies = [ - "indexmap 1.9.3", - "semver", + "wit-component", + "wit-parser", ] [[package]] @@ -547,15 +504,6 @@ dependencies = [ "semver", ] -[[package]] -name = "wasmparser" -version = "0.110.0" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" -dependencies = [ - "indexmap 2.0.0", - "semver", -] - [[package]] name = "wasmprinter" version = "0.2.62" @@ -563,73 +511,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cd12ed4d96a984e4b598a17457f1126d01640cc7461afbb319642111ff9e7f" dependencies = [ "anyhow", - "wasmparser 0.110.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasmprinter" -version = "0.2.62" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" -dependencies = [ - "anyhow", - "wasmparser 0.110.0 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wasmparser", ] [[package]] name = "wasmtime-component-util" -version = "10.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f20a5135ec5ef01080e674979b02d6fa5eebaa2b0c2d6660513ee9956a1bf624" +version = "12.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime?branch=release-12.0.0#de4ede08265e8e984a8fd34be5c0986e13b646b0" [[package]] name = "wasmtime-environ" -version = "10.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f9e58e0ee7d43ff13e75375c726b16bce022db798d3a099a65eeaa7d7a544b" +version = "12.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime?branch=release-12.0.0#de4ede08265e8e984a8fd34be5c0986e13b646b0" dependencies = [ "anyhow", "cranelift-entity", "gimli", - "indexmap 1.9.3", + "indexmap 2.0.0", "log", "object", "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.29.0", - "wasmparser 0.107.0", - "wasmprinter 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder", + "wasmparser", + "wasmprinter", "wasmtime-component-util", "wasmtime-types", ] [[package]] name = "wasmtime-types" -version = "10.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb7c138f797192f46afdd3ec16f85ef007c3bb45fa8e5174031f17b0be4c4a" +version = "12.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime?branch=release-12.0.0#de4ede08265e8e984a8fd34be5c0986e13b646b0" dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser 0.107.0", + "wasmparser", ] [[package]] name = "wast" version = "62.0.1" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ae06f09dbe377b889fbd620ff8fa21e1d49d1d9d364983c0cdbf9870cb9f1f" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.31.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wasm-encoder", ] [[package]] name = "wat" version = "1.0.69" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842e15861d203fb4a96d314b0751cdeaf0f6f8b35e8d81d2953af2af5e44e637" dependencies = [ "wast", ] @@ -649,8 +587,8 @@ version = "0.9.0" source = "git+https://github.com/bytecodealliance/wit-bindgen#bf318b8ae4703586ae236a232f62813efc946df3" dependencies = [ "anyhow", - "wit-component 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wit-parser 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-component", + "wit-parser", ] [[package]] @@ -660,10 +598,10 @@ source = "git+https://github.com/bytecodealliance/wit-bindgen#bf318b8ae4703586ae dependencies = [ "anyhow", "heck", - "wasm-metadata 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-metadata", "wit-bindgen-core", "wit-bindgen-rust-lib", - "wit-component 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-component", ] [[package]] @@ -686,7 +624,7 @@ dependencies = [ "wit-bindgen-core", "wit-bindgen-rust", "wit-bindgen-rust-lib", - "wit-component 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-component", ] [[package]] @@ -699,26 +637,11 @@ dependencies = [ "bitflags 2.3.3", "indexmap 2.0.0", "log", - "wasm-encoder 0.31.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-metadata 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.110.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wit-parser 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wit-component" -version = "0.13.1" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" -dependencies = [ - "anyhow", - "bitflags 2.3.3", - "indexmap 2.0.0", - "log", - "wasm-encoder 0.31.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wasm-metadata 0.10.1 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", - "wasmparser 0.110.0 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wasm-encoder", + "wasm-metadata", + "wasmparser", "wat", - "wit-parser 0.9.2 (git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd)", + "wit-parser", ] [[package]] @@ -736,18 +659,3 @@ dependencies = [ "unicode-xid", "url", ] - -[[package]] -name = "wit-parser" -version = "0.9.2" -source = "git+https://github.com/bytecodealliance/wasm-tools?rev=5605721e5373015e70401962a5d381f6968e3fbd#5605721e5373015e70401962a5d381f6968e3fbd" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.0.0", - "log", - "pulldown-cmark", - "semver", - "unicode-xid", - "url", -] diff --git a/Cargo.toml b/Cargo.toml index 93f58861b..eac30db4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,13 +30,16 @@ version = "0.10.1" anyhow = "1.0.71" base64 = "0.21.2" heck = { version = "0.4", features = ["unicode"] } -indexmap = "1.9" -wasm-encoder = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "5605721e5373015e70401962a5d381f6968e3fbd" } -wasm-metadata = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "5605721e5373015e70401962a5d381f6968e3fbd" } -wasmparser = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "5605721e5373015e70401962a5d381f6968e3fbd" } -wasmprinter = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "5605721e5373015e70401962a5d381f6968e3fbd" } -wasmtime-environ = { version = "10.0.1", features = ["component-model"] } -wat = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "5605721e5373015e70401962a5d381f6968e3fbd" } +indexmap = "2.0" +wasm-encoder = "0.31.1" +wasm-metadata = "0.10.1" +wasmparser = "0.110.0" +wasmprinter = "0.2.62" +wasmtime-environ = { version = "12.0.0", features = ["component-model"] } +wat = "1.0.69" wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen" } -wit-component = { git = "https://github.com/bytecodealliance/wasm-tools", features = ["dummy-module"], rev = "5605721e5373015e70401962a5d381f6968e3fbd" } -wit-parser = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "5605721e5373015e70401962a5d381f6968e3fbd" } +wit-component = { version = "0.13.1", features = ["dummy-module"] } +wit-parser = "0.9.2" + +[patch.crates-io] +wasmtime-environ = { git = 'https://github.com/bytecodealliance/wasmtime', branch = 'release-12.0.0' } diff --git a/crates/js-component-bindgen/src/transpile_bindgen.rs b/crates/js-component-bindgen/src/transpile_bindgen.rs index cf7501edf..f3533025a 100644 --- a/crates/js-component-bindgen/src/transpile_bindgen.rs +++ b/crates/js-component-bindgen/src/transpile_bindgen.rs @@ -14,8 +14,9 @@ use std::mem; use wasmtime_environ::{ component, component::{ - CanonicalOptions, Component, CoreDef, CoreExport, Export, ExportItem, GlobalInitializer, - InstantiateModule, LowerImport, RuntimeInstanceIndex, StaticModuleIndex, Transcoder, + CanonicalOptions, Component, ComponentTranslation, CoreDef, CoreExport, Export, ExportItem, + GlobalInitializer, InstantiateModule, LoweredIndex, RuntimeImportIndex, + RuntimeInstanceIndex, StaticModuleIndex, Trampoline, TrampolineIndex, }, }; use wasmtime_environ::{EntityIndex, ModuleTranslation, PrimaryMap}; @@ -71,7 +72,7 @@ struct JsBindgen<'a> { pub fn transpile_bindgen( name: &str, - component: &Component, + component: &ComponentTranslation, modules: &PrimaryMap>, resolve: &Resolve, id: WorldId, @@ -114,9 +115,11 @@ pub fn transpile_bindgen( instances: Default::default(), resolve, world: id, - component, + translation: component, + component: &component.component, imports, exports, + lowering_options: Default::default(), }; instantiator.sizes.fill(resolve); instantiator.instantiate(); @@ -297,17 +300,24 @@ struct Instantiator<'a, 'b> { world: WorldId, sizes: SizeAlign, component: &'a Component, + translation: &'a ComponentTranslation, exports: BTreeMap, imports: BTreeMap, + lowering_options: PrimaryMap, } -impl Instantiator<'_, '_> { +impl<'a> Instantiator<'a, '_> { fn instantiate(&mut self) { - // To avoid uncaught promise rejection errors, we attach an intermediate - // Promise.all with a rejection handler, if there are multiple promises. for i in 0..self.component.num_runtime_component_instances { uwriteln!(self.src.js_init, "const instanceFlags{i} = new WebAssembly.Global({{ value: \"i32\", mutable: true }}, {});", wasmtime_environ::component::FLAG_MAY_LEAVE | wasmtime_environ::component::FLAG_MAY_ENTER); } + + for (i, trampoline) in self.translation.trampolines.iter() { + self.trampoline(i, trampoline); + } + + // To avoid uncaught promise rejection errors, we attach an intermediate + // Promise.all with a rejection handler, if there are multiple promises. if self.modules.len() > 1 { self.src.js_init.push_str("Promise.all(["); for i in 0..self.modules.len() { @@ -331,6 +341,45 @@ impl Instantiator<'_, '_> { self.exports(&self.component.exports) } + fn trampoline(&mut self, i: TrampolineIndex, trampoline: &'a Trampoline) { + match trampoline { + // This is only used for a "degenerate component" which internally + // has a function that always traps. While this should be trivial to + // implement (generate a JS function that always throws) there's no + // way to test this at this time so leave this unimplemented. + Trampoline::AlwaysTrap => unimplemented!(), + + // This is required when strings pass between components within a + // component and may change encodings. This is left unimplemented + // for now since it can't be tested and additionally JS doesn't + // support multi-memory which transcoders rely on anyway. + Trampoline::Transcoder { + op: _, + from: _, + from64: _, + to: _, + to64: _, + } => unimplemented!(), + + Trampoline::LowerImport { + index, + lower_ty: _, + options, + } => { + let i = self.lowering_options.push((options, i)); + assert_eq!(i, *index); + } + + Trampoline::ResourceNew(_) => unimplemented!(), + Trampoline::ResourceRep(_) => unimplemented!(), + Trampoline::ResourceDrop(_) => unimplemented!(), + Trampoline::ResourceTransferOwn => unimplemented!(), + Trampoline::ResourceTransferBorrow => unimplemented!(), + Trampoline::ResourceEnterCall => unimplemented!(), + Trampoline::ResourceExitCall => unimplemented!(), + } + } + fn instantiation_global_initializer(&mut self, init: &GlobalInitializer) { match init { GlobalInitializer::InstantiateModule(m) => match m { @@ -340,8 +389,8 @@ impl Instantiator<'_, '_> { // test at this time so it's left unimplemented. InstantiateModule::Import(..) => unimplemented!(), }, - GlobalInitializer::LowerImport(i) => { - self.lower_import(i); + GlobalInitializer::LowerImport { index, import } => { + self.lower_import(*index, *import); } GlobalInitializer::ExtractMemory(m) => { let def = self.core_export(&m.export); @@ -362,31 +411,7 @@ impl Instantiator<'_, '_> { uwriteln!(self.src.js_init, "postReturn{idx} = {def};"); } - // This is only used for a "degenerate component" which internally - // has a function that always traps. While this should be trivial to - // implement (generate a JS function that always throws) there's no - // way to test this at this time so leave this unimplemented. - GlobalInitializer::AlwaysTrap(_) => unimplemented!(), - - // This is only used when the component exports core wasm modules, - // but that's not possible to test right now so leave these as - // unimplemented. - GlobalInitializer::SaveStaticModule(_) => unimplemented!(), - GlobalInitializer::SaveModuleImport(_) => unimplemented!(), - - // This is required when strings pass between components within a - // component and may change encodings. This is left unimplemented - // for now since it can't be tested and additionally JS doesn't - // support multi-memory which transcoders rely on anyway. - GlobalInitializer::Transcoder(Transcoder { - index: _, - op: _, - from: _, - from64: _, - to: _, - to64: _, - signature: _, - }) => unimplemented!(), + GlobalInitializer::Resource(_) => unimplemented!(), } } @@ -431,8 +456,8 @@ impl Instantiator<'_, '_> { ); } - fn lower_import(&mut self, import: &LowerImport) { - let (import_index, path) = &self.component.imports[import.import]; + fn lower_import(&mut self, index: LoweredIndex, import: RuntimeImportIndex) { + let (import_index, path) = &self.component.imports[import]; let (import_name, _) = &self.component.import_types[*import_index]; let world_key = &self.imports[import_name]; @@ -453,13 +478,13 @@ impl Instantiator<'_, '_> { ( func, &path[0], - Some(iface.name.as_ref().unwrap_or_else(|| import_name)), + Some(iface.name.as_deref().unwrap_or_else(|| import_name)), ) } WorldItem::Type(_) => unreachable!(), }; - let index = import.index.as_u32(); + let (options, trampoline) = self.lowering_options[index]; // note, the same function can be lowered into multiple sub-components let callee_name = self @@ -468,7 +493,7 @@ impl Instantiator<'_, '_> { .get_or_create(&format!("import:{}-{}", import_name, func_name), func_name) .to_string(); - uwrite!(self.src.js, "\nfunction lowering{index}"); + uwrite!(self.src.js, "\nfunction trampoline{}", trampoline.as_u32()); let nparams = self .resolve .wasm_signature(AbiVariant::GuestImport, func) @@ -477,7 +502,7 @@ impl Instantiator<'_, '_> { self.bindgen( nparams, &callee_name, - &import.options, + options, func, AbiVariant::GuestImport, ); @@ -595,12 +620,10 @@ impl Instantiator<'_, '_> { fn core_def(&self, def: &CoreDef) -> String { match def { CoreDef::Export(e) => self.core_export(e), - CoreDef::Lowered(i) => format!("lowering{}", i.as_u32()), - CoreDef::AlwaysTrap(_) => unimplemented!(), + CoreDef::Trampoline(i) => format!("trampoline{}", i.as_u32()), CoreDef::InstanceFlags(i) => { format!("instance_flags{}", i.as_u32()) } - CoreDef::Transcoder(_) => unimplemented!(), } } @@ -682,7 +705,8 @@ impl Instantiator<'_, '_> { Export::Type(_) => {} // This can't be tested at this time so leave it unimplemented - Export::Module(_) => unimplemented!(), + Export::ModuleStatic(_) => unimplemented!(), + Export::ModuleImport(_) => unimplemented!(), } } self.gen.esm_bindgen.populate_export_aliases(); From 2fec0a5b02b8fa87a74bac1779e4cc4ea8827b74 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Jul 2023 19:14:48 -0700 Subject: [PATCH 2/3] Support polyfilling multi-memory Currently JS does not generally support the WebAssembly multi-memory proposal. Wasmtime will, however, generate adapter modules which use multi-memory to communicate between components. This means that composed components are typically not compatible with the transpile process as they produce a core module that doesn't actually run in any JS runtime. This commit fixes this issue by adding polyfill support for multi-memory. Whenever an adapter is produced that uses multi-memory jco will now rewrite the module such that any references to memory that isn't at index 0 to be indirected through functions. These imported functions then operate on the specified memory on behalf of the wasm itself. This is a horribly slow process because all memory reads/writes become function calls, but given that the baseline is otherwise "does not work" it's hopefully a bit better than before. The end goal here is to work up towards strings between components, but for now this just gets everything else working with multi-memory such as transferring lists. --- Cargo.lock | 2 + .../js-component-bindgen-component/Cargo.toml | 1 + .../js-component-bindgen-component/src/lib.rs | 1 + crates/js-component-bindgen/Cargo.toml | 1 + crates/js-component-bindgen/src/core.rs | 794 ++++++++++++++++++ crates/js-component-bindgen/src/lib.rs | 12 +- .../src/transpile_bindgen.rs | 119 ++- test/codegen.js | 2 +- .../components/list-adapter-fusion.wat | 156 ++++ test/runtime.js | 4 +- test/runtime/list-adapter-fusion.ts | 108 +++ 11 files changed, 1182 insertions(+), 18 deletions(-) create mode 100644 crates/js-component-bindgen/src/core.rs create mode 100644 test/fixtures/components/list-adapter-fusion.wat create mode 100644 test/runtime/list-adapter-fusion.ts diff --git a/Cargo.lock b/Cargo.lock index 2f5df24b0..75655d827 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,6 +188,7 @@ dependencies = [ "base64", "heck", "indexmap 2.0.0", + "wasm-encoder", "wasmtime-environ", "wit-component", "wit-parser", @@ -200,6 +201,7 @@ dependencies = [ "anyhow", "js-component-bindgen", "wasmtime-environ", + "wat", "wit-bindgen", ] diff --git a/crates/js-component-bindgen-component/Cargo.toml b/crates/js-component-bindgen-component/Cargo.toml index d91c5cbb3..0ba391261 100644 --- a/crates/js-component-bindgen-component/Cargo.toml +++ b/crates/js-component-bindgen-component/Cargo.toml @@ -14,3 +14,4 @@ anyhow = { workspace = true } js-component-bindgen = { path = "../js-component-bindgen" } wasmtime-environ = { workspace = true } wit-bindgen = { workspace = true } +wat = { workspace = true } diff --git a/crates/js-component-bindgen-component/src/lib.rs b/crates/js-component-bindgen-component/src/lib.rs index bde3b9736..8c2d65a12 100644 --- a/crates/js-component-bindgen-component/src/lib.rs +++ b/crates/js-component-bindgen-component/src/lib.rs @@ -44,6 +44,7 @@ struct JsComponentBindgenComponent; impl JsComponentBindgen for JsComponentBindgenComponent { fn generate(component: Vec, options: GenerateOptions) -> Result { + let component = wat::parse_bytes(&component).map_err(|e| format!("{e}"))?; let opts = js_component_bindgen::TranspileOpts { name: options.name, no_typescript: options.no_typescript.unwrap_or(false), diff --git a/crates/js-component-bindgen/Cargo.toml b/crates/js-component-bindgen/Cargo.toml index 210a10faa..07e94d329 100644 --- a/crates/js-component-bindgen/Cargo.toml +++ b/crates/js-component-bindgen/Cargo.toml @@ -25,3 +25,4 @@ wit-component = { workspace = true } wit-parser = { workspace = true } indexmap = { workspace = true } base64 = { workspace = true } +wasm-encoder = { workspace = true } diff --git a/crates/js-component-bindgen/src/core.rs b/crates/js-component-bindgen/src/core.rs new file mode 100644 index 000000000..f881f5eb1 --- /dev/null +++ b/crates/js-component-bindgen/src/core.rs @@ -0,0 +1,794 @@ +//! Support for transpiling core modules using multi-memory to those that don't +//! use multi-memory. +//! +//! Wasmtime's implementation of adapter modules between components requires the +//! usage of multi-memory for copying data back and forth between two +//! components. The multi-memory proposal is, at this time, not stable in any JS +//! engine. This module is an attempt to polyfill this until at such a time that +//! multi-memory can be used natively. +//! +//! The purpose of this module is to identify core wasms which require +//! multi-memory coming out of Wasmtime. These wasms are rewritten to not +//! actually use more than one memory. The implementation here is to replace all +//! memory instructions operating on memory index 1 or greater with function +//! calls where JS is the one that does the load/store/etc. This is not expected +//! to be fast at runtime but is intended to be just enough to get this working +//! in JS environments at this time. The true speed is expected to come with the +//! multi-memory proposal. +//! +//! This module exports a [`Translation`] which wraps a [`ModuleTranslation`] +//! either as a pass-through "normal" or an "augmented" version where +//! "augmented" means that the original wasm is not used but instead a +//! recompiled copy without multiple memories is used. When calculating the +//! imports for the "augmented" module the arguments for JS functions that +//! read/write memory are automatically injected and handled. +//! +//! Callers of this module need to have an implementation in JS for all of the +//! entries listed in [`AugmentedOp`], likely through the `DataView` class in +//! JS. +//! +//! Note that at this time this module is not intended to be a complete and +//! general purpose method of compiling multiple memories to single-memory +//! modules. This does not handle all instructions that use memory for example, +//! but only those that Wasmtime's adapter modules emits. It's possible to add +//! support for more instructions but such support isn't required at this time. +//! Examples of unsupported instructions are `i64.load8_u` and `memory.copy`. +//! Additionally core wasm sections such as data sections and tables are not +//! supported because, again, Wasmtime doesn't use it at this time. + +use anyhow::{bail, Result}; +use indexmap::IndexMap; +use std::collections::{HashMap, HashSet}; +use wasm_encoder::*; +use wasmparser::*; +use wasmtime_environ::component::CoreDef; +use wasmtime_environ::wasmparser; +use wasmtime_environ::{EntityIndex, MemoryIndex, ModuleTranslation, PrimaryMap}; + +pub enum Translation<'a> { + Normal(ModuleTranslation<'a>), + Augmented { + original: ModuleTranslation<'a>, + wasm: Vec, + imports_removed: HashSet<(String, String)>, + imports_added: Vec<(String, String, MemoryIndex, AugmentedOp)>, + }, +} + +pub enum AugmentedImport<'a> { + CoreDef(&'a CoreDef), + Memory { mem: &'a CoreDef, op: AugmentedOp }, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum AugmentedOp { + I32Load, + I32Load8U, + I32Load8S, + I32Load16U, + I32Load16S, + I64Load, + F32Load, + F64Load, + I32Store, + I32Store8, + I32Store16, + I64Store, + F32Store, + F64Store, + + MemorySize, +} + +impl<'a> Translation<'a> { + pub fn new(translation: ModuleTranslation<'a>) -> Result> { + let mut features = WasmFeatures::default(); + features.multi_memory = false; + match Validator::new_with_features(features).validate_all(&translation.wasm) { + // This module validates without multi-memory, no need to augment + // it + Ok(_) => return Ok(Translation::Normal(translation)), + Err(e) => { + features.multi_memory = true; + match Validator::new_with_features(features).validate_all(&translation.wasm) { + // This module validates with multi-memory, so fall through + // to augmentation. + Ok(_) => {} + + // This appears to not validate at all. + Err(_) => return Err(e.into()), + } + } + } + + let mut augmenter = Augmenter { + translation: &translation, + imports_removed: Default::default(), + imports_added: Default::default(), + imported_funcs: Default::default(), + imported_memories: Default::default(), + imports: Default::default(), + exports: Default::default(), + local_func_tys: Default::default(), + local_funcs: Default::default(), + types: Default::default(), + augments: Default::default(), + }; + let wasm = augmenter.run()?; + Ok(Translation::Augmented { + wasm, + imports_removed: augmenter.imports_removed, + imports_added: augmenter.imports_added, + original: translation, + }) + } + + /// Returns the encoded wasm that represents this module, automatically + /// returning the augmented version if multi-memory augmentation was + /// required. + pub fn wasm(&self) -> &[u8] { + match self { + Translation::Normal(translation) => translation.wasm, + Translation::Augmented { wasm, .. } => wasm, + } + } + + /// Returns an iterator over the imports for this module using the `args` as + /// supplied to the original module. + /// + /// The returned imports are either those within `args` or augmented + /// versions based on `args` that perform an `AugmentedOp`. + pub fn imports<'b>( + &'b self, + args: &'b [CoreDef], + ) -> Vec<(&'b str, &'b str, AugmentedImport<'b>)> { + match self { + Translation::Normal(translation) => { + assert_eq!(translation.module.imports().len(), args.len()); + translation + .module + .imports() + .zip(args) + .map(|((module, name, _), arg)| (module, name, AugmentedImport::CoreDef(arg))) + .collect() + } + Translation::Augmented { + original, + imports_removed, + imports_added, + .. + } => { + let mut ret = Vec::new(); + let mut memories: PrimaryMap = PrimaryMap::new(); + for ((module, name, _), arg) in original.module.imports().zip(args) { + if imports_removed.contains(&(module.to_string(), name.to_string())) { + memories.push(arg); + } else { + ret.push((module, name, AugmentedImport::CoreDef(arg))); + } + } + for (module, name, index, op) in imports_added { + ret.push(( + module, + name, + AugmentedImport::Memory { + mem: memories[*index], + op: *op, + }, + )); + } + ret + } + } + } + + /// Returns the exports of this module, which are not modified by + /// augmentation. + pub fn exports(&self) -> &IndexMap { + match self { + Translation::Normal(translation) => &translation.module.exports, + Translation::Augmented { original, .. } => &original.module.exports, + } + } +} + +pub struct Augmenter<'a> { + translation: &'a ModuleTranslation<'a>, + imports_removed: HashSet<(String, String)>, + imports_added: Vec<(String, String, MemoryIndex, AugmentedOp)>, + augments: HashMap<(MemoryIndex, AugmentedOp), u32>, + + types: Vec, + imports: Vec>, + imported_funcs: u32, + imported_memories: u32, + exports: Vec>, + local_funcs: Vec>, + local_func_tys: Vec, +} + +impl Augmenter<'_> { + fn run(&mut self) -> Result> { + // The first step is to parse the input original wasm and learn about + // its structure. This validates that all the sections are supported and + // records various bits of information about the module within `self`. + for payload in Parser::new(0).parse_all(self.translation.wasm) { + match payload? { + Payload::Version { .. } => {} + Payload::End(_) => {} + + Payload::TypeSection(s) => { + for ty in s { + self.types.push(ty?); + } + } + Payload::ImportSection(s) => { + for i in s { + let i = i?; + match i.ty { + TypeRef::Func(_) => self.imported_funcs += 1, + TypeRef::Memory(_) => { + if self.imported_memories > 0 { + let ok = self + .imports_removed + .insert((i.module.to_string(), i.name.to_string())); + assert!(ok); + continue; + } + self.imported_memories += 1; + } + _ => {} + } + self.imports.push(i); + } + } + + Payload::ExportSection(s) => { + for e in s { + let e = e?; + self.exports.push(e); + } + } + + Payload::FunctionSection(s) => { + for ty in s { + let ty = ty?; + self.local_func_tys.push(ty); + } + } + + Payload::CodeSectionStart { .. } => {} + Payload::CodeSectionEntry(body) => { + self.local_funcs.push(body); + } + + // Ignore all custom sections for now + Payload::CustomSection(_) => {} + + // NB: these sections are theoretically possible to handle but + // are not required at this time. + Payload::DataCountSection { .. } + | Payload::GlobalSection(_) + | Payload::TableSection(_) + | Payload::MemorySection(_) + | Payload::ElementSection(_) + | Payload::DataSection(_) + | Payload::StartSection { .. } + | Payload::TagSection(_) + | Payload::UnknownSection { .. } => { + bail!("unsupported section found in module using multiple memories") + } + + // component-model related things that shouldn't show up + Payload::ModuleSection { .. } + | Payload::ComponentSection { .. } + | Payload::InstanceSection(_) + | Payload::ComponentInstanceSection(_) + | Payload::ComponentAliasSection(_) + | Payload::ComponentCanonicalSection(_) + | Payload::ComponentStartSection { .. } + | Payload::ComponentImportSection(_) + | Payload::CoreTypeSection(_) + | Payload::ComponentExportSection(_) + | Payload::ComponentTypeSection(_) => { + bail!("component section found in module using multiple memories") + } + } + } + + // After the module has been parsed next the set of adapter functions is + // determined. This is done by parsing all instructions in the module + // and looking for anything that operates on memory index 1 or greater. + // + // This will fill out `self.augments` which is a list of functionality + // that must be provided by JS to mutate non-index-0 memories. + for body in self.local_funcs.clone() { + let mut reader = body.get_operators_reader()?; + while !reader.eof() { + reader.visit_operator(&mut CollectMemOps(self))?; + } + } + + // And now at the end we've got all the information for encoding so + // begin that process. + self.encode() + } + + fn augment_op(&mut self, mem: u32, op: AugmentedOp) { + // Memory 0 stays in the module and isn't removed, so no need to + // register an augmentation. + if mem == 0 { + return; + } + let index = MemoryIndex::from_u32(mem - 1); + self.augments.entry((index, op)).or_insert_with(|| { + let idx = self.imported_funcs + self.imports_added.len() as u32; + self.imports_added.push(( + "augments".to_string(), + format!("mem{mem} {op:?}"), + index, + op, + )); + idx + }); + } + + fn encode(&self) -> Result> { + let mut module = Module::new(); + + // Types are all passed through as-is to retain the same type section as + // before. + let mut types = TypeSection::new(); + for ty in self.types.iter() { + assert!(!ty.is_final); + assert!(ty.supertype_idx.is_none()); + match &ty.structural_type { + wasmparser::StructuralType::Func(f) => { + types.function( + f.params().iter().map(|v| valtype(*v)), + f.results().iter().map(|v| valtype(*v)), + ); + } + wasmparser::StructuralType::Array(_) | wasmparser::StructuralType::Struct(_) => { + unimplemented!() + } + } + } + + // Pass through all of `self.imports` into the import section. This will + // already have imports of multiple memories removed so this will import + // at most one memory. + let mut imports = ImportSection::new(); + for import in self.imports.iter() { + let ty = match import.ty { + TypeRef::Func(f) => EntityType::Function(f), + TypeRef::Global(g) => EntityType::Global(wasm_encoder::GlobalType { + mutable: g.mutable, + val_type: valtype(g.content_type), + }), + TypeRef::Memory(m) => EntityType::Memory(wasm_encoder::MemoryType { + maximum: m.maximum, + minimum: m.initial, + memory64: m.memory64, + shared: m.shared, + }), + TypeRef::Table(_) => unimplemented!(), + TypeRef::Tag(_) => unimplemented!(), + }; + imports.import(import.module, import.name, ty); + } + + // After the normal imports are all registered next the + // memory-modification-functions are all imported. This is a new + // addition to this module which shifts all functions in the index + // space, hence the rewriting of all function bodies below. + // + // Each augmentation function declares its type signature in the type + // section at the end of the type section to avoid tampering with the + // type section's original index spaces. It would be more efficient to + // not redeclare function signatures and reuse existing function + // signatures, but that's left as an optimization for a later date. + for (module, name, _, op) in self.imports_added.iter() { + let cnt = types.len(); + op.encode_type(&mut types); + imports.import(module, name, EntityType::Function(cnt)); + } + + // The function section remains the same as we're not tampering with the + // count or types of all local functions. + let mut funcs = FunctionSection::new(); + for ty in self.local_func_tys.iter() { + funcs.function(*ty); + } + + // Exports all remain the same with the one caveat that the function + // index space has changed so those indices are remapped. + let mut exports = ExportSection::new(); + for e in self.exports.iter() { + let (kind, index) = match e.kind { + ExternalKind::Func => (ExportKind::Func, self.remap_func(e.index)), + ExternalKind::Table => (ExportKind::Table, e.index), + ExternalKind::Global => (ExportKind::Global, e.index), + ExternalKind::Memory => { + assert!(e.index < 1); + (ExportKind::Memory, e.index) + } + ExternalKind::Tag => (ExportKind::Tag, e.index), + }; + exports.export(e.name, kind, index); + } + + // Finally the code section is remapped. This is done by translating + // operator-by-operator from `wasmparser` to `wasm-encoder`. This + // is where instructions like `i32.load 1` will become `call + // $i32_load_memory_1`. + let mut code = CodeSection::new(); + for body in self.local_funcs.iter() { + let mut locals = Vec::new(); + + for local in body.get_locals_reader()? { + let (cnt, ty) = local?; + locals.push((cnt, valtype(ty))); + } + + let mut f = Function::new(locals); + + let mut ops = body.get_operators_reader()?; + while !ops.eof() { + ops.visit_operator(&mut Translator { + func: &mut f, + augmenter: self, + })?; + } + + code.function(&f); + } + + module.section(&types); + module.section(&imports); + module.section(&funcs); + module.section(&exports); + module.section(&code); + + Ok(module.finish()) + } + + fn remap_func(&self, index: u32) -> u32 { + if index < self.imported_funcs { + index + } else { + index + self.imports_added.len() as u32 + } + } + + fn remap_memory(&self, index: u32) -> u32 { + assert!(index < 1); + index + } +} + +fn valtype(ty: wasmparser::ValType) -> wasm_encoder::ValType { + match ty { + wasmparser::ValType::I32 => wasm_encoder::ValType::I32, + wasmparser::ValType::I64 => wasm_encoder::ValType::I64, + wasmparser::ValType::F32 => wasm_encoder::ValType::F32, + wasmparser::ValType::F64 => wasm_encoder::ValType::F64, + wasmparser::ValType::V128 => wasm_encoder::ValType::V128, + wasmparser::ValType::Ref(_) => unimplemented!(), + } +} + +struct CollectMemOps<'a, 'b>(&'a mut Augmenter<'b>); + +macro_rules! define_visit { + ($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { + $( + #[allow(unreachable_code)] + fn $visit(&mut self $( $( ,$arg: $argty)* )?) { + define_visit!(augment self $op $($($arg)*)?); + } + )* + }; + + // List of instructions that are augmented which register the memory index + // and the relevant augmentation operation. + (augment $self:ident I32Load $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Load); + }; + (augment $self:ident I64Load $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I64Load); + }; + (augment $self:ident F32Load $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::F32Load); + }; + (augment $self:ident F64Load $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::F64Load); + }; + (augment $self:ident I32Load8U $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Load8U); + }; + (augment $self:ident I32Load8S $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Load8S); + }; + (augment $self:ident I32Load16U $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Load16U); + }; + (augment $self:ident I32Load16S $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Load16S); + }; + (augment $self:ident I32Store $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Store); + }; + (augment $self:ident I64Store $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I64Store); + }; + (augment $self:ident F32Store $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::F32Store); + }; + (augment $self:ident F64Store $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::F64Store); + }; + (augment $self:ident I32Store8 $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Store8); + }; + (augment $self:ident I32Store16 $memarg:ident) => { + $self.0.augment_op($memarg.memory, AugmentedOp::I32Store16); + }; + (augment $self:ident MemorySize $mem:ident $byte:ident) => { + $self.0.augment_op($mem, AugmentedOp::MemorySize); + }; + + // Catch-all which asserts that none of the `$arg` looks like a memory + // index ty catch any missing instructions from the list above. + (augment $self:ident $op:ident $($arg:ident)*) => { + $( + define_visit!(assert_not_mem $op $arg); + )* + }; + + (assert_not_mem $op:ident mem) => {panic!(concat!("missed case ", stringify!($op)));}; + (assert_not_mem $op:ident src_mem) => {panic!(concat!("missed case ", stringify!($op)));}; + (assert_not_mem $op:ident dst_mem) => {panic!(concat!("missed case ", stringify!($op)));}; + (assert_not_mem $op:ident memarg) => {panic!(concat!("missed case ", stringify!($op)));}; + (assert_not_mem $op:ident $other:ident) => {}; +} + +impl<'a> VisitOperator<'a> for CollectMemOps<'_, 'a> { + type Output = (); + + wasmparser::for_each_operator!(define_visit); +} + +impl AugmentedOp { + fn encode_type(&self, section: &mut TypeSection) { + use wasm_encoder::ValType::*; + match self { + // Loads take two arguments: the first is the address being loaded + // from and the second is the static offset that was listed on the + // relevant load instruction. + AugmentedOp::I32Load + | AugmentedOp::I32Load8U + | AugmentedOp::I32Load8S + | AugmentedOp::I32Load16U + | AugmentedOp::I32Load16S => { + section.function([I32, I32], [I32]); + } + AugmentedOp::I64Load => { + section.function([I32, I32], [I64]); + } + AugmentedOp::F32Load => { + section.function([I32, I32], [F32]); + } + AugmentedOp::F64Load => { + section.function([I32, I32], [F64]); + } + + // Stores, like loads, take an additional argument than usual which + // is the static offset on the store instruction. + AugmentedOp::I32Store | AugmentedOp::I32Store8 | AugmentedOp::I32Store16 => { + section.function([I32, I32, I32], []); + } + AugmentedOp::I64Store => { + section.function([I32, I64, I32], []); + } + AugmentedOp::F32Store => { + section.function([I32, F32, I32], []); + } + AugmentedOp::F64Store => { + section.function([I32, F64, I32], []); + } + + AugmentedOp::MemorySize => { + section.function([], [I32]); + } + } + } +} + +struct Translator<'a, 'b> { + func: &'a mut wasm_encoder::Function, + augmenter: &'a Augmenter<'b>, +} + +// TODO +macro_rules! define_translate { + ($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { + $( + #[allow(clippy::drop_copy)] + fn $visit(&mut self $(, $($arg: $argty),*)?) { + #[allow(unused_imports)] + use wasm_encoder::Instruction::*; + + define_translate!(translate self $op $($($arg)*)?) + } + )* + }; + + (translate $self:ident I32Load $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Load, I32Load, $memarg) + }}; + (translate $self:ident I32Load8U $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Load8U, I32Load8U, $memarg) + }}; + (translate $self:ident I32Load8S $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Load8S, I32Load8S, $memarg) + }}; + (translate $self:ident I32Load16U $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Load16U, I32Load16U, $memarg) + }}; + (translate $self:ident I32Load16S $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Load16S, I32Load16S, $memarg) + }}; + (translate $self:ident I64Load $memarg:ident) => {{ + $self.augment(AugmentedOp::I64Load, I64Load, $memarg) + }}; + (translate $self:ident F32Load $memarg:ident) => {{ + $self.augment(AugmentedOp::F32Load, F32Load, $memarg) + }}; + (translate $self:ident F64Load $memarg:ident) => {{ + $self.augment(AugmentedOp::F64Load, F64Load, $memarg) + }}; + (translate $self:ident I32Store $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Store, I32Store, $memarg) + }}; + (translate $self:ident I32Store8 $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Store8, I32Store8, $memarg) + }}; + (translate $self:ident I32Store16 $memarg:ident) => {{ + $self.augment(AugmentedOp::I32Store16, I32Store16, $memarg) + }}; + (translate $self:ident I64Store $memarg:ident) => {{ + $self.augment(AugmentedOp::I64Store, I64Store, $memarg) + }}; + (translate $self:ident F32Store $memarg:ident) => {{ + $self.augment(AugmentedOp::F32Store, F32Store, $memarg) + }}; + (translate $self:ident F64Store $memarg:ident) => {{ + $self.augment(AugmentedOp::F64Store, F64Store, $memarg) + }}; + (translate $self:ident MemorySize $mem:ident $byte:ident) => {{ + if $mem < 1 { + $self.func.instruction(&MemorySize($mem)); + } else { + let mem = MemoryIndex::from_u32($mem - 1); + let func = $self.augmenter.augments[&(mem, AugmentedOp::MemorySize)]; + $self.func.instruction(&Call(func)); + } + }}; + + (translate $self:ident $op:ident $($arg:ident)*) => {{ + $( + let $arg = define_translate!(map $self $arg $arg); + )* + let insn = define_translate!(mk $op $($arg)*); + $self.func.instruction(&insn); + }}; + + // No-payload instructions are named the same in wasmparser as they are in + // wasm-encoder + (mk $op:ident) => ($op); + + // Instructions which need "special care" to map from wasmparser to + // wasm-encoder + (mk BrTable $arg:ident) => ({ + BrTable($arg.0, $arg.1) + }); + (mk CallIndirect $ty:ident $table:ident $table_byte:ident) => ({ + let _ = $table_byte; + CallIndirect { ty: $ty, table: $table } + }); + (mk ReturnCallIndirect $ty:ident $table:ident) => ( + ReturnCallIndirect { ty: $ty, table: $table } + ); + (mk I32Const $v:ident) => (I32Const($v)); + (mk I64Const $v:ident) => (I64Const($v)); + (mk F32Const $v:ident) => (F32Const(f32::from_bits($v.bits()))); + (mk F64Const $v:ident) => (F64Const(f64::from_bits($v.bits()))); + (mk V128Const $v:ident) => (V128Const($v.i128())); + (mk MemoryGrow $($x:tt)*) => ({ + if true { unimplemented!() } Nop + }); + + // Catch-all for the translation of one payload argument which is typically + // represented as a tuple-enum in wasm-encoder. + (mk $op:ident $arg:ident) => ($op($arg)); + + // Catch-all of everything else where the wasmparser fields are simply + // translated to wasm-encoder fields. + (mk $op:ident $($arg:ident)*) => ($op { $($arg),* }); + + // Individual cases of mapping one argument type to another, similar to the + // `define_visit` macro above. + (map $self:ident $arg:ident memarg) => {$self.memarg($arg)}; + (map $self:ident $arg:ident blockty) => {$self.blockty($arg)}; + (map $self:ident $arg:ident hty) => {$self.heapty($arg)}; + (map $self:ident $arg:ident tag_index) => {$arg}; + (map $self:ident $arg:ident relative_depth) => {$arg}; + (map $self:ident $arg:ident function_index) => {$self.augmenter.remap_func($arg)}; + (map $self:ident $arg:ident global_index) => {$arg}; + (map $self:ident $arg:ident mem) => {$self.augmenter.remap_memory($arg)}; + (map $self:ident $arg:ident src_mem) => {$self.augmenter.remap_memory($arg)}; + (map $self:ident $arg:ident dst_mem) => {$self.augmenter.remap_memory($arg)}; + (map $self:ident $arg:ident table) => {$arg}; + (map $self:ident $arg:ident table_index) => {$arg}; + (map $self:ident $arg:ident src_table) => {$arg}; + (map $self:ident $arg:ident dst_table) => {$arg}; + (map $self:ident $arg:ident type_index) => {$arg}; + (map $self:ident $arg:ident ty) => {valtype($arg)}; + (map $self:ident $arg:ident local_index) => {$arg}; + (map $self:ident $arg:ident lane) => {$arg}; + (map $self:ident $arg:ident lanes) => {$arg}; + (map $self:ident $arg:ident elem_index) => {$arg}; + (map $self:ident $arg:ident data_index) => {$arg}; + (map $self:ident $arg:ident table_byte) => {$arg}; + (map $self:ident $arg:ident mem_byte) => {$arg}; + (map $self:ident $arg:ident value) => {$arg}; + (map $self:ident $arg:ident targets) => (( + $arg.targets().map(|i| i.unwrap()).collect::>().into(), + $arg.default(), + )); +} + +impl<'a> VisitOperator<'a> for Translator<'_, 'a> { + type Output = (); + + wasmparser::for_each_operator!(define_translate); +} + +impl Translator<'_, '_> { + fn blockty(&self, ty: wasmparser::BlockType) -> wasm_encoder::BlockType { + match ty { + wasmparser::BlockType::Empty => wasm_encoder::BlockType::Empty, + wasmparser::BlockType::Type(t) => wasm_encoder::BlockType::Result(valtype(t)), + wasmparser::BlockType::FuncType(i) => wasm_encoder::BlockType::FunctionType(i), + } + } + fn heapty(&self, _ty: wasmparser::HeapType) -> wasm_encoder::HeapType { + unimplemented!() + } + + fn memarg(&self, ty: wasmparser::MemArg) -> wasm_encoder::MemArg { + wasm_encoder::MemArg { + align: ty.align.into(), + offset: ty.offset, + memory_index: self.augmenter.remap_memory(ty.memory), + } + } + + fn augment( + &mut self, + op: AugmentedOp, + insn: fn(wasm_encoder::MemArg) -> wasm_encoder::Instruction<'static>, + memarg: wasmparser::MemArg, + ) { + use wasm_encoder::Instruction::*; + if memarg.memory < 1 { + self.func.instruction(&insn(self.memarg(memarg))); + return; + } + let idx = MemoryIndex::from_u32(memarg.memory - 1); + let func = self.augmenter.augments[&(idx, op)]; + self.func.instruction(&I32Const(memarg.offset as i32)); + self.func.instruction(&Call(func)); + } +} diff --git a/crates/js-component-bindgen/src/lib.rs b/crates/js-component-bindgen/src/lib.rs index 036eb8824..0eb1c1e6d 100644 --- a/crates/js-component-bindgen/src/lib.rs +++ b/crates/js-component-bindgen/src/lib.rs @@ -1,3 +1,4 @@ +mod core; mod files; mod transpile_bindgen; mod ts_bindgen; @@ -14,9 +15,9 @@ use transpile_bindgen::transpile_bindgen; use anyhow::{bail, Context}; use wasmtime_environ::component::Export; -use wasmtime_environ::component::{ComponentTypesBuilder, Translator}; +use wasmtime_environ::component::{ComponentTypesBuilder, StaticModuleIndex, Translator}; use wasmtime_environ::wasmparser::{Validator, WasmFeatures}; -use wasmtime_environ::{ScopeVec, Tunables}; +use wasmtime_environ::{PrimaryMap, ScopeVec, Tunables}; use wit_component::DecodedWasm; use ts_bindgen::ts_bindgen; @@ -123,10 +124,15 @@ pub fn transpile(component: &[u8], opts: TranspileOpts) -> Result> = modules + .into_iter() + .map(|(_i, module)| core::Translation::new(module)) + .collect::>()?; + // Insert all core wasm modules into the generated `Files` which will // end up getting used in the `generate_instantiate` method. for (i, module) in modules.iter() { - files.push(&core_file_name(&name, i.as_u32()), module.wasm); + files.push(&core_file_name(&name, i.as_u32()), module.wasm()); } if !opts.no_typescript { diff --git a/crates/js-component-bindgen/src/transpile_bindgen.rs b/crates/js-component-bindgen/src/transpile_bindgen.rs index f3533025a..ce8861fcb 100644 --- a/crates/js-component-bindgen/src/transpile_bindgen.rs +++ b/crates/js-component-bindgen/src/transpile_bindgen.rs @@ -1,3 +1,4 @@ +use crate::core; use crate::esm_bindgen::EsmBindgen; use crate::files::Files; use crate::function_bindgen::{ErrHandling, FunctionBindgen}; @@ -19,7 +20,7 @@ use wasmtime_environ::{ RuntimeInstanceIndex, StaticModuleIndex, Trampoline, TrampolineIndex, }, }; -use wasmtime_environ::{EntityIndex, ModuleTranslation, PrimaryMap}; +use wasmtime_environ::{EntityIndex, PrimaryMap}; use wit_component::StringEncoding; use wit_parser::abi::{AbiVariant, LiftLower}; use wit_parser::*; @@ -73,7 +74,7 @@ struct JsBindgen<'a> { pub fn transpile_bindgen( name: &str, component: &ComponentTranslation, - modules: &PrimaryMap>, + modules: &PrimaryMap>, resolve: &Resolve, id: WorldId, opts: TranspileOpts, @@ -294,7 +295,7 @@ impl<'a> JsBindgen<'a> { struct Instantiator<'a, 'b> { src: Source, gen: &'a mut JsBindgen<'b>, - modules: &'a PrimaryMap>, + modules: &'a PrimaryMap>, instances: PrimaryMap, resolve: &'a Resolve, world: WorldId, @@ -416,19 +417,19 @@ impl<'a> Instantiator<'a, '_> { } fn instantiate_static_module(&mut self, idx: StaticModuleIndex, args: &[CoreDef]) { - let module = &self.modules[idx].module; - // Build a JS "import object" which represents `args`. The `args` is a // flat representation which needs to be zip'd with the list of names to // correspond to the JS wasm embedding API. This is one of the major // differences between Wasmtime's and JS's embedding API. let mut import_obj = BTreeMap::new(); - assert_eq!(module.imports().len(), args.len()); - for ((module, name, _), arg) in module.imports().zip(args) { - let def = self.core_def(arg); + for (module, name, arg) in self.modules[idx].imports(args) { + let def = self.augmented_import_def(arg); let dst = import_obj.entry(module).or_insert(BTreeMap::new()); let prev = dst.insert(name, def); - assert!(prev.is_none()); + assert!( + prev.is_none(), + "unsupported duplicate import of `{module}::{name}`" + ); } let mut imports = String::new(); if !import_obj.is_empty() { @@ -617,12 +618,106 @@ impl<'a> Instantiator<'a, '_> { self.src.js("}"); } + fn augmented_import_def(&self, def: core::AugmentedImport<'_>) -> String { + match def { + core::AugmentedImport::CoreDef(def) => self.core_def(def), + core::AugmentedImport::Memory { mem, op } => { + let mem = self.core_def(mem); + match op { + core::AugmentedOp::I32Load => { + format!( + "(ptr, off) => new DataView({mem}.buffer).getInt32(ptr + off, true)" + ) + } + core::AugmentedOp::I32Load8U => { + format!( + "(ptr, off) => new DataView({mem}.buffer).getUint8(ptr + off, true)" + ) + } + core::AugmentedOp::I32Load8S => { + format!("(ptr, off) => new DataView({mem}.buffer).getInt8(ptr + off, true)") + } + core::AugmentedOp::I32Load16U => { + format!( + "(ptr, off) => new DataView({mem}.buffer).getUint16(ptr + off, true)" + ) + } + core::AugmentedOp::I32Load16S => { + format!( + "(ptr, off) => new DataView({mem}.buffer).getInt16(ptr + off, true)" + ) + } + core::AugmentedOp::I64Load => { + format!( + "(ptr, off) => new DataView({mem}.buffer).getBigInt64(ptr + off, true)" + ) + } + core::AugmentedOp::F32Load => { + format!( + "(ptr, off) => new DataView({mem}.buffer).getFloat32(ptr + off, true)" + ) + } + core::AugmentedOp::F64Load => { + format!( + "(ptr, off) => new DataView({mem}.buffer).getFloat64(ptr + off, true)" + ) + } + core::AugmentedOp::I32Store8 => { + format!( + "(ptr, val, offset) => {{ + new DataView({mem}.buffer).setInt8(ptr + offset, val, true); + }}" + ) + } + core::AugmentedOp::I32Store16 => { + format!( + "(ptr, val, offset) => {{ + new DataView({mem}.buffer).setInt16(ptr + offset, val, true); + }}" + ) + } + core::AugmentedOp::I32Store => { + format!( + "(ptr, val, offset) => {{ + new DataView({mem}.buffer).setInt32(ptr + offset, val, true); + }}" + ) + } + core::AugmentedOp::I64Store => { + format!( + "(ptr, val, offset) => {{ + new DataView({mem}.buffer).setBigInt64(ptr + offset, val, true); + }}" + ) + } + core::AugmentedOp::F32Store => { + format!( + "(ptr, val, offset) => {{ + new DataView({mem}.buffer).setFloat32(ptr + offset, val, true); + }}" + ) + } + core::AugmentedOp::F64Store => { + format!( + "(ptr, val, offset) => {{ + new DataView({mem}.buffer).setFloat64(ptr + offset, val, true); + }}" + ) + } + core::AugmentedOp::MemorySize => { + format!("ptr => {mem}.buffer.byteLength / 65536") + } + } + } + } + } + fn core_def(&self, def: &CoreDef) -> String { match def { CoreDef::Export(e) => self.core_export(e), CoreDef::Trampoline(i) => format!("trampoline{}", i.as_u32()), CoreDef::InstanceFlags(i) => { - format!("instance_flags{}", i.as_u32()) + format!("instanceFlags{}", i.as_u32()) } } } @@ -633,10 +728,10 @@ impl<'a> Instantiator<'a, '_> { { let name = match &export.item { ExportItem::Index(idx) => { - let module = &self.modules[self.instances[export.instance]].module; + let module = &self.modules[self.instances[export.instance]]; let idx = (*idx).into(); module - .exports + .exports() .iter() .filter_map(|(name, i)| if *i == idx { Some(name) } else { None }) .next() diff --git a/test/codegen.js b/test/codegen.js index a5106e245..319324e0f 100644 --- a/test/codegen.js +++ b/test/codegen.js @@ -25,7 +25,7 @@ export async function codegenTest (fixtures) { suite(`Transpiler codegen`, () => { for (const fixture of fixtures) { - const name = fixture.replace('.component.wasm', ''); + const name = fixture.replace('.component.wasm', '').replace('.wat', ''); test(`${fixture} transpile`, async () => { const flags = await readFlags(`test/runtime/${name}.ts`); var { stderr } = await exec(jcoPath, 'transpile', `test/fixtures/components/${fixture}`, '--name', name, ...flags, '-o', `test/output/${name}`); diff --git a/test/fixtures/components/list-adapter-fusion.wat b/test/fixtures/components/list-adapter-fusion.wat new file mode 100644 index 000000000..557911748 --- /dev/null +++ b/test/fixtures/components/list-adapter-fusion.wat @@ -0,0 +1,156 @@ +(component + (type $test (instance + (export "list-u8" (func (param "x" (list u8)) (result (list u8)))) + (export "list-s8" (func (param "x" (list s8)) (result (list s8)))) + (export "list-u16" (func (param "x" (list u16)) (result (list u16)))) + (export "list-s16" (func (param "x" (list s16)) (result (list s16)))) + (export "list-u32" (func (param "x" (list u32)) (result (list u32)))) + (export "list-s32" (func (param "x" (list s32)) (result (list s32)))) + (export "list-u64" (func (param "x" (list u64)) (result (list u64)))) + (export "list-s64" (func (param "x" (list s64)) (result (list s64)))) + (export "list-float32" (func (param "x" (list float32)) (result (list float32)))) + (export "list-float64" (func (param "x" (list float64)) (result (list float64)))) + )) + (import "test" (instance $test (type $test))) + + (component $C + (import "test" (instance $test (type $test))) + + (core module $libc + (memory (export "memory") 1) + (global $next (mut i32) i32.const 128) + (func $realloc (export "realloc") (param i32 i32 i32 i32) (result i32) + (local $ret i32) + (local $next i32) + (local $page i32) + + ;; assert no realloc is actually happening and this is only an + ;; allocation function + (if (local.get 0) (unreachable)) + (if (local.get 1) (unreachable)) + + (local.set $ret (global.get $next)) + + (local.set $next (i32.add (global.get $next) (local.get 3))) + (local.set $next (i32.add (local.get $next) (i32.const 15))) + (local.set $next (i32.and (local.get $next) (i32.const 0xfffffff0))) + (global.set $next (local.get $next)) + + (local.set $page (i32.div_u (local.get $next) (i32.const 65536))) + + (loop $grow + (i32.ge_u (local.get $page) (memory.size)) + if + i32.const 1 + memory.grow + drop + br $grow + end + ) + + (local.get $next) + ) + ) + (core instance $libc (instantiate $libc)) + (alias core export $libc "memory" (core memory $mem)) + (alias core export $libc "realloc" (core func $realloc)) + + (core func $list-u8 + (canon lower (func $test "list-u8") (memory $mem) (realloc (func $realloc)))) + (core func $list-s8 + (canon lower (func $test "list-s8") (memory $mem) (realloc (func $realloc)))) + (core func $list-u16 + (canon lower (func $test "list-u16") (memory $mem) (realloc (func $realloc)))) + (core func $list-s16 + (canon lower (func $test "list-s16") (memory $mem) (realloc (func $realloc)))) + (core func $list-u32 + (canon lower (func $test "list-u32") (memory $mem) (realloc (func $realloc)))) + (core func $list-s32 + (canon lower (func $test "list-s32") (memory $mem) (realloc (func $realloc)))) + (core func $list-u64 + (canon lower (func $test "list-u64") (memory $mem) (realloc (func $realloc)))) + (core func $list-s64 + (canon lower (func $test "list-s64") (memory $mem) (realloc (func $realloc)))) + (core func $list-float32 + (canon lower (func $test "list-float32") (memory $mem) (realloc (func $realloc)))) + (core func $list-float64 + (canon lower (func $test "list-float64") (memory $mem) (realloc (func $realloc)))) + + (core module $m + (import "" "list-u8" (func $list-u8 (param i32 i32 i32))) + (import "" "list-s8" (func $list-s8 (param i32 i32 i32))) + (import "" "list-u16" (func $list-u16 (param i32 i32 i32))) + (import "" "list-s16" (func $list-s16 (param i32 i32 i32))) + (import "" "list-u32" (func $list-u32 (param i32 i32 i32))) + (import "" "list-s32" (func $list-s32 (param i32 i32 i32))) + (import "" "list-u64" (func $list-u64 (param i32 i32 i32))) + (import "" "list-s64" (func $list-s64 (param i32 i32 i32))) + (import "" "list-float32" (func $list-float32 (param i32 i32 i32))) + (import "" "list-float64" (func $list-float64 (param i32 i32 i32))) + (import "libc" "memory" (memory 1)) + + (func (export "list-u8") (param i32 i32) (result i32) + (call $list-u8 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-s8") (param i32 i32) (result i32) + (call $list-s8 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-u16") (param i32 i32) (result i32) + (call $list-u16 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-s16") (param i32 i32) (result i32) + (call $list-s16 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-u32") (param i32 i32) (result i32) + (call $list-u32 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-s32") (param i32 i32) (result i32) + (call $list-s32 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-u64") (param i32 i32) (result i32) + (call $list-u64 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-s64") (param i32 i32) (result i32) + (call $list-s64 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-float32") (param i32 i32) (result i32) + (call $list-float32 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + (func (export "list-float64") (param i32 i32) (result i32) + (call $list-float64 (local.get 0) (local.get 1) (i32.const 8)) (i32.const 8)) + ) + + (core instance $i (instantiate $m + (with "libc" (instance $libc)) + (with "" (instance + (export "list-u8" (func $list-u8)) + (export "list-s8" (func $list-s8)) + (export "list-u16" (func $list-u16)) + (export "list-s16" (func $list-s16)) + (export "list-u32" (func $list-u32)) + (export "list-s32" (func $list-s32)) + (export "list-u64" (func $list-u64)) + (export "list-s64" (func $list-s64)) + (export "list-float32" (func $list-float32)) + (export "list-float64" (func $list-float64)) + )) + )) + + (func (export "list-u8") (param "x" (list u8)) (result (list u8)) + (canon lift (core func $i "list-u8") (memory $mem) (realloc (func $realloc)))) + (func (export "list-s8") (param "x" (list s8)) (result (list s8)) + (canon lift (core func $i "list-s8") (memory $mem) (realloc (func $realloc)))) + (func (export "list-u16") (param "x" (list u16)) (result (list u16)) + (canon lift (core func $i "list-u16") (memory $mem) (realloc (func $realloc)))) + (func (export "list-s16") (param "x" (list s16)) (result (list s16)) + (canon lift (core func $i "list-s16") (memory $mem) (realloc (func $realloc)))) + (func (export "list-u32") (param "x" (list u32)) (result (list u32)) + (canon lift (core func $i "list-u32") (memory $mem) (realloc (func $realloc)))) + (func (export "list-s32") (param "x" (list s32)) (result (list s32)) + (canon lift (core func $i "list-s32") (memory $mem) (realloc (func $realloc)))) + (func (export "list-u64") (param "x" (list u64)) (result (list u64)) + (canon lift (core func $i "list-u64") (memory $mem) (realloc (func $realloc)))) + (func (export "list-s64") (param "x" (list s64)) (result (list s64)) + (canon lift (core func $i "list-s64") (memory $mem) (realloc (func $realloc)))) + (func (export "list-float32") (param "x" (list float32)) (result (list float32)) + (canon lift (core func $i "list-float32") (memory $mem) (realloc (func $realloc)))) + (func (export "list-float64") (param "x" (list float64)) (result (list float64)) + (canon lift (core func $i "list-float64") (memory $mem) (realloc (func $realloc)))) + ) + + (instance $i1 (instantiate $C (with "test" (instance $test)))) + (instance $i2 (instantiate $C (with "test" (instance $i1)))) + + (export "result" (instance $i2)) +) diff --git a/test/runtime.js b/test/runtime.js index c54518301..78420ffbe 100644 --- a/test/runtime.js +++ b/test/runtime.js @@ -4,10 +4,10 @@ import { exec } from './helpers.js'; export async function runtimeTest (fixtures) { suite('Runtime', () => { - + for (const fixture of fixtures) { if (fixture.startsWith('dummy_')) continue; - const runtimeJs = fixture.replace('.component.wasm', '.js'); + const runtimeJs = fixture.replace('.component.wasm', '.js').replace('.wat', '.js'); if (!existsSync(`test/output/${runtimeJs}`)) continue; test(runtimeJs, async () => { diff --git a/test/runtime/list-adapter-fusion.ts b/test/runtime/list-adapter-fusion.ts new file mode 100644 index 000000000..52f2c70bc --- /dev/null +++ b/test/runtime/list-adapter-fusion.ts @@ -0,0 +1,108 @@ +// Flags: --tla-compat --map test=../list-adapter-fusion.js + +import * as assert from 'assert'; + +let expected: any = null; + +export function listU8(f: Uint8Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listS8(f: Int8Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listU16(f: Uint16Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listS16(f: Int16Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listU32(f: Uint32Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listS32(f: Int32Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listU64(f: BigUint64Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listS64(f: BigInt64Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listFloat32(f: Float32Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +export function listFloat64(f: Float64Array) { + assert.deepStrictEqual(expected, f); + return f; +} + +async function run() { + const wasm = await import('../output/list-adapter-fusion/list-adapter-fusion.js'); + await wasm.$init; + + function test(f: (arg0: T) => void, arg: T) { + expected = arg; + const ret = f(arg); + expected = null; + assert.deepStrictEqual(arg, ret); + } + + test(wasm.result.listU8, new Uint8Array([])); + test(wasm.result.listU8, new Uint8Array([1])); + test(wasm.result.listU8, new Uint8Array([1, 2, 3, -1, -2, 0])); + + test(wasm.result.listS8, new Int8Array([])); + test(wasm.result.listS8, new Int8Array([1])); + test(wasm.result.listS8, new Int8Array([1, 2, 3, -1, -2, 0])); + + test(wasm.result.listU16, new Uint16Array([])); + test(wasm.result.listU16, new Uint16Array([1])); + test(wasm.result.listU16, new Uint16Array([1, 2, 3, -1, -2, 0])); + + test(wasm.result.listS16, new Int16Array([])); + test(wasm.result.listS16, new Int16Array([1])); + test(wasm.result.listS16, new Int16Array([1, 2, 3, -1, -2, 0])); + + test(wasm.result.listU32, new Uint32Array([])); + test(wasm.result.listU32, new Uint32Array([1])); + test(wasm.result.listU32, new Uint32Array([1, 2, 3, -1, -2, 0])); + + test(wasm.result.listS32, new Int32Array([])); + test(wasm.result.listS32, new Int32Array([1])); + test(wasm.result.listS32, new Int32Array([1, 2, 3, -1, -2, 0])); + + test(wasm.result.listU64, new BigUint64Array([])); + test(wasm.result.listU64, new BigUint64Array([1n])); + test(wasm.result.listU64, new BigUint64Array([1n, 2n, 3n, -1n, -2n, 0n])); + + test(wasm.result.listS64, new BigInt64Array([])); + test(wasm.result.listS64, new BigInt64Array([1n])); + test(wasm.result.listS64, new BigInt64Array([1n, 2n, 3n, -1n, -2n, 0n])); + + test(wasm.result.listFloat32, new Float32Array([])); + test(wasm.result.listFloat32, new Float32Array([1, 1.2, 0.3])); + + test(wasm.result.listFloat64, new Float64Array([])); + test(wasm.result.listFloat64, new Float64Array([1, 1.2, 0.3])); +} + +// Async cycle handling +setTimeout(run); From c3c4f0506d836d9b32c2c65faafa0fe3701eb7a7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 8 Aug 2023 07:46:51 -0700 Subject: [PATCH 3/3] Fill out comments --- crates/js-component-bindgen/src/core.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/js-component-bindgen/src/core.rs b/crates/js-component-bindgen/src/core.rs index f881f5eb1..7a90be0f3 100644 --- a/crates/js-component-bindgen/src/core.rs +++ b/crates/js-component-bindgen/src/core.rs @@ -610,8 +610,12 @@ struct Translator<'a, 'b> { augmenter: &'a Augmenter<'b>, } -// TODO +// Helper macro to create a wasmparser visitor which will translate each +// individual instruction from wasmparser to wasm-encoder. macro_rules! define_translate { + // This is the base case where all methods are defined and the body of each + // method delegates to a recursive invocation of this macro to hit one of + // the cases below. ($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { $( #[allow(clippy::drop_copy)] @@ -624,6 +628,9 @@ macro_rules! define_translate { )* }; + // Memory-related operations are translated to augmentations which are a + // `Call` of an imported function (or are passed through natively as the + // same instruction if they use memory 0). (translate $self:ident I32Load $memarg:ident) => {{ $self.augment(AugmentedOp::I32Load, I32Load, $memarg) }}; @@ -676,6 +683,9 @@ macro_rules! define_translate { } }}; + // All other instructions not listed above are caught here and fall through + // to below cases to translate arguments/types from wasmparser to + // wasm-encoder and then create the new instruction. (translate $self:ident $op:ident $($arg:ident)*) => {{ $( let $arg = define_translate!(map $self $arg $arg);