diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..9de3386bf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "wit-bindgen"] + path = wit-bindgen + url = git@github.com:bytecodealliance/wit-bindgen diff --git a/Cargo.lock b/Cargo.lock index 12cf910ed..c7e073980 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,8 +93,8 @@ dependencies = [ [[package]] name = "cranelift-entity" -version = "0.91.0" -source = "git+https://github.com/bytecodealliance/wasmtime#28cf995fd3f9926a95036758c78390f54a0289ae" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#ec6922ff2407bb81f148bd8b14ee66642ea38a5f" dependencies = [ "serde", ] @@ -209,8 +209,8 @@ dependencies = [ "indexmap", "wasmtime-environ", "wit-bindgen-guest-rust", - "wit-component", - "wit-parser", + "wit-component 0.4.2", + "wit-parser 0.4.0", ] [[package]] @@ -225,8 +225,8 @@ dependencies = [ "js-component-bindgen", "wasmtime-environ", "wit-bindgen-guest-rust", - "wit-component", - "wit-parser", + "wit-component 0.4.2", + "wit-parser 0.4.0", ] [[package]] @@ -512,17 +512,35 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ab2fe77b325731603297debb4573e002d06ae0aa1f4dc108585c81961e0609" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef126be0e14bdf355ac1a8b41afc89195289e5c7179f80118e3abddb472f0810" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-tools-js" version = "0.1.0" dependencies = [ "anyhow", - "wasmparser", + "wasmparser 0.97.0", "wasmprinter", "wat", "wit-bindgen-guest-rust", - "wit-component", - "wit-parser", + "wit-component 0.4.2", + "wit-parser 0.4.0", ] [[package]] @@ -535,25 +553,45 @@ dependencies = [ "url", ] +[[package]] +name = "wasmparser" +version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98123a0d2bacf9286239231b116cbd66c65d9b89793f7c9bba3a3ae7f1b15f3" +dependencies = [ + "indexmap", + "url", +] + +[[package]] +name = "wasmparser" +version = "0.98.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8724c724dc595495979c055f4bd8b7ed9fab1069623178a28016ae43a9666f36" +dependencies = [ + "indexmap", + "url", +] + [[package]] name = "wasmprinter" -version = "0.2.44" +version = "0.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae24500f9cc27a4b2b338e66693ff53c08b17cf920bdc81e402a09fe7a204eea" +checksum = "322949f382cd5e4bad4330e144bf2124b3182846194ac01e2423c07a6a15ba85" dependencies = [ "anyhow", - "wasmparser", + "wasmparser 0.98.1", ] [[package]] name = "wasmtime-component-util" -version = "4.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#28cf995fd3f9926a95036758c78390f54a0289ae" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#ec6922ff2407bb81f148bd8b14ee66642ea38a5f" [[package]] name = "wasmtime-environ" -version = "4.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#28cf995fd3f9926a95036758c78390f54a0289ae" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#ec6922ff2407bb81f148bd8b14ee66642ea38a5f" dependencies = [ "anyhow", "cranelift-entity", @@ -564,8 +602,8 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.21.0", + "wasmparser 0.97.0", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -573,32 +611,32 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "4.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#28cf995fd3f9926a95036758c78390f54a0289ae" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#ec6922ff2407bb81f148bd8b14ee66642ea38a5f" dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser", + "wasmparser 0.97.0", ] [[package]] name = "wast" -version = "50.0.0" +version = "52.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34" +checksum = "829fb867c8e82d21557a2c6c5b3ed8e8f7cdd534ea782b9ecf68bede5607fe4b" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.22.0", ] [[package]] name = "wat" -version = "1.0.52" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15" +checksum = "3493e7c82d8e9a75e69ecbfe6f324ca1c4e2ae89f67ccbb22f92282e2e27bb23" dependencies = [ "wast", ] @@ -640,8 +678,8 @@ version = "0.3.0" source = "git+https://github.com/bytecodealliance/wit-bindgen#04da8c22848ba931a2601c6641af7d113f156b0e" dependencies = [ "anyhow", - "wit-component", - "wit-parser", + "wit-component 0.3.2", + "wit-parser 0.3.1", ] [[package]] @@ -652,7 +690,7 @@ dependencies = [ "heck", "wit-bindgen-core", "wit-bindgen-gen-rust-lib", - "wit-component", + "wit-component 0.3.2", ] [[package]] @@ -693,7 +731,7 @@ dependencies = [ "proc-macro2", "syn", "wit-bindgen-core", - "wit-component", + "wit-component 0.3.2", ] [[package]] @@ -706,9 +744,25 @@ dependencies = [ "bitflags", "indexmap", "log", - "wasm-encoder", - "wasmparser", - "wit-parser", + "wasm-encoder 0.20.0", + "wasmparser 0.95.0", + "wit-parser 0.3.1", +] + +[[package]] +name = "wit-component" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f1781394df3f08797267cb455bac2b67cd13051d16279bd93e6c63d632c98e1" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "url", + "wasm-encoder 0.22.0", + "wasmparser 0.98.1", + "wit-parser 0.4.0", ] [[package]] @@ -723,3 +777,18 @@ dependencies = [ "pulldown-cmark", "unicode-xid", ] + +[[package]] +name = "wit-parser" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cfa79275011530f37e0e164183c606bae1cdc466ea90bcd364d50605486a4d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "pulldown-cmark", + "unicode-xid", + "url", +] diff --git a/Cargo.toml b/Cargo.toml index 4d140037a..d62dc220c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,10 @@ env_logger = "0.9.1" indexmap = "1.9" wasmtime = { git = 'https://github.com/bytecodealliance/wasmtime', features = ["component-model"] } wasmtime-environ = { git = 'https://github.com/bytecodealliance/wasmtime' } -wasmprinter = "0.2.44" -wasmparser = "0.95.0" -wasm-encoder = "0.20.0" -wat = "1.0.52" +wasmprinter = "0.2.46" +wasmparser = "0.97.0" +wasm-encoder = "0.21.0" +wat = "1.0.53" wit-bindgen-guest-rust = { git = 'https://github.com/bytecodealliance/wit-bindgen', default-features = false, features = ['macros'] } -wit-component = "0.3.2" -wit-parser = "0.3.1" +wit-component = "0.4.1" +wit-parser = "0.4.0" diff --git a/README.md b/README.md index 4861bc7bf..a8fb48446 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,11 @@ Parse a compoment WAT to output a Component binary. Print the WAT for a Component binary. -#### `componentNew(coreWasm: Uint8Array | null, opts?): Uint8Array` +#### `componentNew(coreWasm: Uint8Array | null, adapters?: [String, Uint8Array][]): Uint8Array` -"WIT Component" Component creation tool. +"WIT Component" Component creation tool, optionally providing a set of named adapter binaries. -#### `componentWit(component: Uint8Array): string` +#### `componentWit(component: Uint8Array, document?: string): string` Extract the WIT world from a component binary. @@ -102,6 +102,14 @@ Commands: help [command] display help for command ``` +## Contributing + +Development is based on a standard `npm install && npm run build && npm run test` workflow. + +Tests can be run without bundling via `npm run build:dev && npm run test:dev`. + +Specific tests can be run adding the mocha `--grep` flag, for example: `npm run test:dev -- --grep exports_only`. + # License This project is licensed under the Apache 2.0 license with the LLVM exception. diff --git a/api.d.ts b/api.d.ts index d9734c688..d166e2458 100644 --- a/api.d.ts +++ b/api.d.ts @@ -36,7 +36,11 @@ export interface TranspileOpts { /** * Transpile a Component into a JS-executable package */ -export function transpile(component: Uint8Array, opts?: TranspileOpts): Promise<{ files, imports, exports }>; +export function transpile(component: Uint8Array, opts?: TranspileOpts): Promise<{ + files: Record, + imports: string[], + exports: [string, 'function' | 'instance'][] +}>; /** * Parse a WAT string into a Wasm binary @@ -51,27 +55,13 @@ export function print(binary: Uint8Array | ArrayBuffer): string; /** * WIT Component - create a Component from a Wasm core binary */ -export function componentNew(binary: Uint8Array | ArrayBuffer | null, opts: ComponentOpts | null): Uint8Array; +export function componentNew(binary: Uint8Array | ArrayBuffer, adapters?: [string, Uint8Array][] | null): Uint8Array; /** * Extract the WIT world from a Wasm Component */ -export function componentWit(binary: Uint8Array | ArrayBuffer): string; +export function componentWit(binary: Uint8Array | ArrayBuffer, document?: string | null): string; export type StringEncoding = 'utf8' | 'utf16' | 'compact-utf16'; -export interface ComponentOpts { - /// wit world for the Component - /// (only needed if not provided in the Component itself, - /// which it usually is) - wit?: string, - /// create a type only Component shell, without implementations - typesOnly?: boolean, - /// adapters to use - adapters?: [string, Uint8Array][], - /// string encoding used by the Component (this should - /// also be picked up from the Component itself) - stringEncoding?: StringEncoding, -} - export const $init: Promise; diff --git a/crates/js-component-bindgen-component/js-component-bindgen.wit b/crates/js-component-bindgen-component/js-component-bindgen.wit index f6117ed0d..686d0a0e3 100644 --- a/crates/js-component-bindgen-component/js-component-bindgen.wit +++ b/crates/js-component-bindgen-component/js-component-bindgen.wit @@ -42,10 +42,15 @@ world js-component-bindgen-component { valid-lifting-optimization: option, } + enum export-type { + function, + instance, + } + record transpiled { files: files, imports: list, - exports: list + exports: list> } /// Generate the file structure for the transpilation of a component diff --git a/crates/js-component-bindgen-component/src/lib.rs b/crates/js-component-bindgen-component/src/lib.rs index 18df95bc2..fa348cd7e 100644 --- a/crates/js-component-bindgen-component/src/lib.rs +++ b/crates/js-component-bindgen-component/src/lib.rs @@ -69,7 +69,7 @@ impl js_component_bindgen_component::JsComponentBindgenComponent for JsComponent let js_component_bindgen::Transpiled { files, imports, - exports, + mut exports, } = transpile(component, opts) .map_err(|e| format!("{:?}", e)) .map_err(|e| e.to_string())?; @@ -77,7 +77,23 @@ impl js_component_bindgen_component::JsComponentBindgenComponent for JsComponent Ok(Transpiled { files, imports, - exports, + exports: exports + .drain(..) + .map(|(name, expt)| { + ( + name, + match expt { + wasmtime_environ::component::Export::LiftedFunction { .. } => { + ExportType::Function + } + wasmtime_environ::component::Export::Instance(_) => { + ExportType::Instance + } + _ => panic!("Unexpected export type"), + }, + ) + }) + .collect(), }) } } diff --git a/crates/js-component-bindgen/src/bindgen.rs b/crates/js-component-bindgen/src/bindgen.rs index 98954d7f9..59a872001 100644 --- a/crates/js-component-bindgen/src/bindgen.rs +++ b/crates/js-component-bindgen/src/bindgen.rs @@ -169,7 +169,7 @@ impl Intrinsic { struct JsInterface<'a> { src: Source, gen: &'a mut JsBindgen, - iface: &'a Interface, + resolve: &'a Resolve, needs_ty_option: bool, needs_ty_result: bool, } @@ -179,9 +179,11 @@ impl JsBindgen { &mut self, component: &Component, modules: &PrimaryMap>, - world: &World, + resolve: &Resolve, + id: WorldId, ) { self.core_module_cnt = modules.len(); + let world = &resolve.worlds[id]; // Generate the TypeScript definition of the `instantiate` function // which is the main workhorse of the generated bindings. @@ -222,12 +224,15 @@ impl JsBindgen { // structure. let mut instantiator = Instantiator { src: Source::default(), + sizes: SizeAlign::default(), gen: self, modules, instances: Default::default(), - world, + resolve, + world: id, component, }; + instantiator.sizes.fill(resolve); instantiator.instantiate(); instantiator.gen.src.js(&instantiator.src.js); instantiator.gen.src.js_init(&instantiator.src.js_init); @@ -374,10 +379,17 @@ impl JsBindgen { } impl JsBindgen { - fn import(&mut self, name: &str, iface: &Interface, files: &mut Files) { + fn import_interface( + &mut self, + resolve: &Resolve, + name: &str, + id: InterfaceId, + files: &mut Files, + ) { self.generate_interface( name, - iface, + resolve, + id, "imports", "Imports", files, @@ -390,37 +402,62 @@ impl JsBindgen { ); } - fn export(&mut self, name: &str, iface: &Interface, files: &mut Files) { + fn import_funcs( + &mut self, + resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + let mut gen = self.js_interface(resolve); + for (_, func) in funcs { + gen.ts_func(func, AbiVariant::GuestImport); + } + gen.gen.import_object.push_str(&gen.src.ts); + assert!(gen.src.js.is_empty()); + } + + fn export_interface( + &mut self, + resolve: &Resolve, + name: &str, + id: InterfaceId, + files: &mut Files, + ) { self.generate_interface( name, - iface, + resolve, + id, "exports", "Exports", files, AbiVariant::GuestExport, ); let camel = name.to_upper_camel_case(); - uwriteln!(self.src.ts, "export const {name}: typeof {camel}Exports;"); + uwriteln!( + self.export_object, + "export const {name}: typeof {camel}Exports;" + ); } - fn export_default(&mut self, _name: &str, iface: &Interface, _files: &mut Files) { - let instantiation = self.opts.instantiation; - let mut gen = self.js_interface(iface); - for func in iface.functions.iter() { + fn export_funcs( + &mut self, + resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + let mut gen = self.js_interface(resolve); + for (_, func) in funcs { gen.ts_func(func, AbiVariant::GuestExport); } - if instantiation { - gen.gen.export_object.push_str(&mem::take(&mut gen.src.ts)); - } - // After the default interface has its function definitions - // inlined the rest of the types are generated here as well. - gen.types(); - gen.post_types(); - gen.gen.src.ts(&mem::take(&mut gen.src.ts)); + gen.gen.export_object.push_str(&gen.src.ts); + assert!(gen.src.js.is_empty()); } - fn finish(&mut self, world: &World, _files: &mut Files) { + fn finish(&mut self, resolve: &Resolve, id: WorldId, _files: &mut Files) { + let world = &resolve.worlds[id]; let camel = world.name.to_upper_camel_case(); // Generate a type definition for the import object to type-check @@ -441,45 +478,63 @@ impl JsBindgen { uwriteln!(self.src.ts, "export namespace {camel} {{",); self.src.ts(&self.export_object); uwriteln!(self.src.ts, "}}"); + } else { + self.src.ts(&self.export_object); } } } impl JsBindgen { - pub fn generate(&mut self, world: &World, files: &mut Files) { - self.preprocess(&world.name); + pub fn generate(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) { + let world = &resolve.worlds[id]; + self.preprocess(resolve, &world.name); + + let mut funcs = Vec::new(); + for (name, import) in world.imports.iter() { - self.import(name, import, files); + match import { + WorldItem::Function(f) => funcs.push((name.as_str(), f)), + WorldItem::Interface(id) => self.import_interface(resolve, name, *id, files), + } + } + if !funcs.is_empty() { + self.import_funcs(resolve, id, &funcs, files); } + funcs.clear(); for (name, export) in world.exports.iter() { - self.export(name, export, files); + match export { + WorldItem::Function(f) => funcs.push((name.as_str(), f)), + WorldItem::Interface(id) => self.export_interface(resolve, name, *id, files), + } } - if let Some(iface) = &world.default { - self.export_default(&world.name, iface, files); + if !funcs.is_empty() { + self.export_funcs(resolve, id, &funcs, files); } - self.finish(world, files); + self.finish(resolve, id, files); } - fn preprocess(&mut self, name: &str) { + fn preprocess(&mut self, resolve: &Resolve, name: &str) { + drop(resolve); drop(name); } fn generate_interface( &mut self, name: &str, - iface: &Interface, + resolve: &Resolve, + id: InterfaceId, dir: &str, extra: &str, files: &mut Files, abi: AbiVariant, ) { let camel = name.to_upper_camel_case(); - let mut gen = self.js_interface(iface); - gen.types(); + let mut gen = self.js_interface(resolve); + gen.types(id); gen.post_types(); uwriteln!(gen.src.ts, "export namespace {camel} {{"); - for func in iface.functions.iter() { + for (_, func) in resolve.interfaces[id].functions.iter() { gen.ts_func(func, abi); } uwriteln!(gen.src.ts, "}}"); @@ -491,15 +546,7 @@ impl JsBindgen { uwriteln!( self.src.ts, - "{} {{ {camel} as {camel}{extra} }} from './{dir}/{name}';", - // In instance mode, we have no way to assert the imported types - // in the ambient declaration file. Instead we just export the - // import namespace types for users to use. - if self.opts.instantiation { - "import" - } else { - "export" - } + "import {{ {camel} as {camel}{extra} }} from './{dir}/{name}';", ); } @@ -512,11 +559,11 @@ impl JsBindgen { impt.into() } - fn js_interface<'a>(&'a mut self, iface: &'a Interface) -> JsInterface<'a> { + fn js_interface<'a>(&'a mut self, resolve: &'a Resolve) -> JsInterface<'a> { JsInterface { src: Source::default(), gen: self, - iface, + resolve, needs_ty_option: false, needs_ty_result: false, } @@ -801,7 +848,7 @@ impl JsBindgen { name } - fn array_ty(&self, iface: &Interface, ty: &Type) -> Option<&'static str> { + fn array_ty(&self, resolve: &Resolve, ty: &Type) -> Option<&'static str> { match ty { Type::Bool => None, Type::U8 => Some("Uint8Array"), @@ -816,28 +863,28 @@ impl JsBindgen { Type::Float64 => Some("Float64Array"), Type::Char => None, Type::String => None, - Type::Id(id) => match &iface.types[*id].kind { - TypeDefKind::Type(t) => self.array_ty(iface, t), + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Type(t) => self.array_ty(resolve, t), _ => None, }, } } /// Returns whether `null` is a valid value of type `ty` - fn maybe_null(&self, iface: &Interface, ty: &Type) -> bool { - self.as_nullable(iface, ty).is_some() + fn maybe_null(&self, resolve: &Resolve, ty: &Type) -> bool { + self.as_nullable(resolve, ty).is_some() } /// Tests whether `ty` can be represented with `null`, and if it can then /// the "other type" is returned. If `Some` is returned that means that `ty` /// is `null | `. If `None` is returned that means that `null` can't /// be used to represent `ty`. - fn as_nullable<'a>(&self, iface: &'a Interface, ty: &'a Type) -> Option<&'a Type> { + fn as_nullable<'a>(&self, resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> { let id = match ty { Type::Id(id) => *id, _ => return None, }; - match &iface.types[id].kind { + match &resolve.types[id].kind { // If `ty` points to an `option`, then `ty` can be represented // with `null` if `t` itself can't be represented with null. For // example `option>` can't be represented with `null` @@ -854,13 +901,13 @@ impl JsBindgen { // It's doubtful anyone would actually rely on that though due to // how confusing it is. TypeDefKind::Option(t) => { - if !self.maybe_null(iface, t) { + if !self.maybe_null(resolve, t) { Some(t) } else { None } } - TypeDefKind::Type(t) => self.as_nullable(iface, t), + TypeDefKind::Type(t) => self.as_nullable(resolve, t), _ => None, } } @@ -874,7 +921,9 @@ struct Instantiator<'a> { gen: &'a mut JsBindgen, modules: &'a PrimaryMap>, instances: PrimaryMap, - world: &'a World, + resolve: &'a Resolve, + world: WorldId, + sizes: SizeAlign, component: &'a Component, } @@ -903,11 +952,7 @@ impl Instantiator<'_> { self.src.js("return "); } - self.exports( - &self.component.exports, - 0, - self.world.default.as_ref().map(|i| (None, i)), - ); + self.exports(&self.component.exports); } fn instantiation_global_initializer(&mut self, init: &GlobalInitializer) { @@ -1005,9 +1050,7 @@ impl Instantiator<'_> { uwriteln!(self.src.js, "let exports{iu32};"); uwriteln!( self.src.js_init, - " - ({{ exports: exports{iu32} }} = await {instantiate}(await module{}{imports}));\ - ", + "({{ exports: exports{iu32} }} = await {instantiate}(await module{}{imports}));", idx.as_u32() ); } @@ -1018,9 +1061,16 @@ impl Instantiator<'_> { // where instances export functions. let (import_index, path) = &self.component.imports[import.import]; let (import_name, _import_ty) = &self.component.import_types[*import_index]; - assert_eq!(path.len(), 1); - let iface = &self.world.imports[import_name.as_str()]; - let func = iface.functions.iter().find(|f| f.name == path[0]).unwrap(); + let func = match &self.resolve.worlds[self.world].imports[import_name.as_str()] { + WorldItem::Function(f) => { + assert_eq!(path.len(), 0); + f + } + WorldItem::Interface(i) => { + assert_eq!(path.len(), 1); + &self.resolve.interfaces[*i].functions[&path[0]] + } + }; let index = import.index.as_u32(); let callee = format!("lowering{index}Callee"); @@ -1051,7 +1101,8 @@ impl Instantiator<'_> { } uwrite!(self.src.js_init, "\nfunction lowering{index}"); - let nparams = iface + let nparams = self + .resolve .wasm_signature(AbiVariant::GuestImport, func) .params .len(); @@ -1060,7 +1111,6 @@ impl Instantiator<'_> { nparams, callee, &import.options, - iface, func, AbiVariant::GuestImport, ); @@ -1077,7 +1127,6 @@ impl Instantiator<'_> { nparams: usize, callee: String, opts: &CanonicalOptions, - iface: &Interface, func: &Function, abi: AbiVariant, ) { @@ -1116,12 +1165,10 @@ impl Instantiator<'_> { ); } - let mut sizes = SizeAlign::default(); - sizes.fill(iface); let mut f = FunctionBindgen { - sizes, + sizes: &self.sizes, gen: self.gen, - err: if func.results.throws(iface).is_some() { + err: if func.results.throws(self.resolve).is_some() { match abi { AbiVariant::GuestExport => ErrHandling::ThrowResultErr, AbiVariant::GuestImport => ErrHandling::ResultCatchHandler, @@ -1140,7 +1187,7 @@ impl Instantiator<'_> { encoding: opts.string_encoding, src: Source::default(), }; - iface.call( + self.resolve.call( abi, match abi { AbiVariant::GuestImport => LiftLower::LiftArgsLowerResults, @@ -1191,12 +1238,7 @@ impl Instantiator<'_> { } } - fn exports( - &mut self, - exports: &IndexMap, - depth: usize, - iface: Option<(Option<&str>, &Interface)>, - ) { + fn exports(&mut self, exports: &IndexMap) { if exports.is_empty() { if self.gen.opts.instantiation { self.src.js("{}"); @@ -1209,54 +1251,54 @@ impl Instantiator<'_> { } for (name, export) in exports { - let js_name = if self.gen.opts.instantiation { - name.clone() - } else { - // When generating direct ES-module exports namespace functions - // by their exported interface name, if applicable. - match iface { - Some((Some(iface_name), _)) => format!("{iface_name}-{name}"), - _ => name.clone(), - } - }; - let camel = js_name.to_lower_camel_case(); + let item = &self.resolve.worlds[self.world].exports[name]; + let camel = name.to_lower_camel_case(); match export { Export::LiftedFunction { ty: _, func, options, } => { - assert!(depth < 2); - if self.gen.opts.instantiation { - uwrite!(self.src.js, "{camel}"); - } else { - uwrite!(self.src.js, "\nexport function {camel}"); - } - let callee = self.core_def(func); - let (_, iface) = iface.unwrap(); - let func = iface.functions.iter().find(|f| f.name == *name).unwrap(); - self.bindgen( - func.params.len(), - callee, - options, - iface, + self.export_bindgen( + name, + None, func, - AbiVariant::GuestExport, + options, + match item { + WorldItem::Function(f) => f, + WorldItem::Interface(_) => unreachable!(), + }, ); - if self.gen.opts.instantiation { - self.src.js(",\n"); - } else { - self.src.js("\n"); - } } Export::Instance(exports) => { + let id = match item { + WorldItem::Interface(id) => *id, + WorldItem::Function(_) => unreachable!(), + }; if self.gen.opts.instantiation { - uwrite!(self.src.js, "{camel}: "); + uwriteln!(self.src.js, "{camel}: {{"); + } else { + uwriteln!(self.src.js, "export const {camel} = {{"); } - let iface = &self.world.exports[name.as_str()]; - self.exports(exports, depth + 1, Some((Some(name.as_str()), iface))); + for (func_name, export) in exports { + let (func, options) = match export { + Export::LiftedFunction { func, options, .. } => (func, options), + Export::Type(_) => continue, // ignored + _ => unreachable!(), + }; + self.export_bindgen( + func_name, + Some(name), + func, + options, + &self.resolve.interfaces[id].functions[func_name], + ); + } + self.src.js("\n}"); if self.gen.opts.instantiation { self.src.js(",\n"); + } else { + self.src.js(";\n"); } } @@ -1271,6 +1313,35 @@ impl Instantiator<'_> { self.src.js("}"); } } + + fn export_bindgen( + &mut self, + name: &str, + instance_name: Option<&str>, + def: &CoreDef, + options: &CanonicalOptions, + func: &Function, + ) { + let name = name.to_lower_camel_case(); + if self.gen.opts.instantiation || instance_name.is_some() { + self.src.js.push_str(&name); + } else { + uwrite!(self.src.js, "\nexport function {name}"); + } + let callee = self.core_def(def); + self.bindgen( + func.params.len(), + callee, + options, + func, + AbiVariant::GuestExport, + ); + if self.gen.opts.instantiation || instance_name.is_some() { + self.src.js(",\n"); + } else { + self.src.js("\n"); + } + } } #[derive(Copy, Clone)] @@ -1295,8 +1366,31 @@ impl<'a> JsInterface<'a> { } } + fn types(&mut self, iface: InterfaceId) { + let iface = &self.resolve().interfaces[iface]; + for (name, id) in iface.types.iter() { + let id = *id; + let ty = &self.resolve().types[id]; + match &ty.kind { + TypeDefKind::Record(record) => self.type_record(id, name, record, &ty.docs), + TypeDefKind::Flags(flags) => self.type_flags(id, name, flags, &ty.docs), + TypeDefKind::Tuple(tuple) => self.type_tuple(id, name, tuple, &ty.docs), + TypeDefKind::Enum(enum_) => self.type_enum(id, name, enum_, &ty.docs), + TypeDefKind::Variant(variant) => self.type_variant(id, name, variant, &ty.docs), + TypeDefKind::Option(t) => self.type_option(id, name, t, &ty.docs), + TypeDefKind::Result(r) => self.type_result(id, name, r, &ty.docs), + TypeDefKind::Union(u) => self.type_union(id, name, u, &ty.docs), + TypeDefKind::List(t) => self.type_list(id, name, t, &ty.docs), + TypeDefKind::Type(t) => self.type_alias(id, name, t, &ty.docs), + TypeDefKind::Future(_) => todo!("generate for future"), + TypeDefKind::Stream(_) => todo!("generate for stream"), + TypeDefKind::Unknown => unreachable!(), + } + } + } + fn array_ty(&self, ty: &Type) -> Option<&'static str> { - self.gen.array_ty(self.iface, ty) + self.gen.array_ty(self.resolve, ty) } fn print_ty(&mut self, ty: &Type, mode: Mode) { @@ -1314,7 +1408,7 @@ impl<'a> JsInterface<'a> { Type::Char => self.src.ts("string"), Type::String => self.src.ts("string"), Type::Id(id) => { - let ty = &self.iface.types[*id]; + let ty = &self.resolve.types[*id]; if let Some(name) = &ty.name { return self.src.ts(&name.to_upper_camel_case()); } @@ -1348,6 +1442,7 @@ impl<'a> JsInterface<'a> { TypeDefKind::List(v) => self.print_list(v, mode), TypeDefKind::Future(_) => todo!("anonymous future"), TypeDefKind::Stream(_) => todo!("anonymous stream"), + TypeDefKind::Unknown => unreachable!(), } } } @@ -1415,7 +1510,7 @@ impl<'a> JsInterface<'a> { AbiVariant::GuestExport => Mode::Lift, AbiVariant::GuestImport => Mode::Lower, }; - if let Some((ok_ty, _)) = func.results.throws(self.iface) { + if let Some((ok_ty, _)) = func.results.throws(self.resolve) { self.print_optional_ty(ok_ty, result_mode); } else { match func.results.len() { @@ -1437,14 +1532,14 @@ impl<'a> JsInterface<'a> { } fn maybe_null(&self, ty: &Type) -> bool { - self.gen.maybe_null(self.iface, ty) + self.gen.maybe_null(self.resolve, ty) } fn as_nullable<'b>(&self, ty: &'b Type) -> Option<&'b Type> where 'a: 'b, { - self.gen.as_nullable(self.iface, ty) + self.gen.as_nullable(self.resolve, ty) } fn post_types(&mut self) { @@ -1460,31 +1555,8 @@ impl<'a> JsInterface<'a> { } impl<'a> JsInterface<'a> { - fn iface(&self) -> &'a Interface { - self.iface - } - - fn types(&mut self) { - for (id, ty) in self.iface().types.iter() { - let name = match &ty.name { - Some(name) => name, - None => continue, - }; - match &ty.kind { - TypeDefKind::Record(record) => self.type_record(id, name, record, &ty.docs), - TypeDefKind::Flags(flags) => self.type_flags(id, name, flags, &ty.docs), - TypeDefKind::Tuple(tuple) => self.type_tuple(id, name, tuple, &ty.docs), - TypeDefKind::Enum(enum_) => self.type_enum(id, name, enum_, &ty.docs), - TypeDefKind::Variant(variant) => self.type_variant(id, name, variant, &ty.docs), - TypeDefKind::Option(t) => self.type_option(id, name, t, &ty.docs), - TypeDefKind::Result(r) => self.type_result(id, name, r, &ty.docs), - TypeDefKind::Union(u) => self.type_union(id, name, u, &ty.docs), - TypeDefKind::List(t) => self.type_list(id, name, t, &ty.docs), - TypeDefKind::Type(t) => self.type_alias(id, name, t, &ty.docs), - TypeDefKind::Future(_) => todo!("generate for future"), - TypeDefKind::Stream(_) => todo!("generate for stream"), - } - } + fn resolve(&self) -> &'a Resolve { + self.resolve } fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) { @@ -1671,7 +1743,7 @@ enum ErrHandling { struct FunctionBindgen<'a> { gen: &'a mut JsBindgen, - sizes: SizeAlign, + sizes: &'a SizeAlign, err: ErrHandling, tmp: usize, src: Source, @@ -1760,17 +1832,17 @@ impl Bindgen for FunctionBindgen<'_> { self.blocks.push((src.into(), mem::take(operands))); } - fn return_pointer(&mut self, _iface: &Interface, _size: usize, _align: usize) -> String { + fn return_pointer(&mut self, _size: usize, _align: usize) -> String { unimplemented!() } - fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { - self.gen.array_ty(iface, ty).is_some() + fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool { + self.gen.array_ty(resolve, ty).is_some() } fn emit( &mut self, - iface: &Interface, + resolve: &Resolve, inst: &Instruction<'_>, operands: &mut Vec, results: &mut Vec, @@ -2267,7 +2339,7 @@ impl Bindgen for FunctionBindgen<'_> { uwriteln!(none, "variant{tmp}_{i} = {none_result};"); } - if self.gen.maybe_null(iface, payload) { + if self.gen.maybe_null(resolve, payload) { uwriteln!( self.src.js, "switch (variant{tmp}.tag) {{ @@ -2308,7 +2380,7 @@ impl Bindgen for FunctionBindgen<'_> { let tmp = self.tmp(); let operand = &operands[0]; - let (v_none, v_some) = if self.gen.maybe_null(iface, payload) { + let (v_none, v_some) = if self.gen.maybe_null(resolve, payload) { ( "{ tag: 'none' }", format!( @@ -2578,7 +2650,7 @@ impl Bindgen for FunctionBindgen<'_> { uwriteln!(self.src.js, "const ptr{tmp} = {};", operands[0]); uwriteln!(self.src.js, "const len{tmp} = {};", operands[1]); // TODO: this is the wrong endianness - let array_ty = self.gen.array_ty(iface, element).unwrap(); + let array_ty = self.gen.array_ty(resolve, element).unwrap(); uwriteln!( self.src.js, "const result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {}));", diff --git a/crates/js-component-bindgen/src/component.rs b/crates/js-component-bindgen/src/component.rs index a0e052c8d..4a7d9a140 100644 --- a/crates/js-component-bindgen/src/component.rs +++ b/crates/js-component-bindgen/src/component.rs @@ -9,15 +9,16 @@ use crate::bindgen::JsBindgen; use crate::files::Files; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use heck::*; use wasmtime_environ::component::{ComponentTypesBuilder, Export, Translator}; use wasmtime_environ::wasmparser::{Validator, WasmFeatures}; use wasmtime_environ::{ScopeVec, Tunables}; +use wit_component::DecodedWasm; pub struct ComponentInfo { pub imports: Vec, - pub exports: Vec, + pub exports: Vec<(String, wasmtime_environ::component::Export)>, } /// Generate bindings to load and instantiate the specific binary component @@ -29,14 +30,17 @@ pub fn generate( files: &mut Files, ) -> Result { // Use the `wit-component` crate here to parse `binary` and discover - // the type-level descriptions and `Interface`s corresponding to the - // component binary. This is effectively a step that infers a "world" of - // a component. Right now `interfaces` is a world-like thing and this - // will likely change as worlds are iterated on in the component model - // standard. Regardless though this is the step where types are learned - // and `Interface`s are constructed for further code generation below. - let world = wit_component::decode_world(name, binary) + // the type-level descriptions and `Resolve` corresponding to the + // component binary. This will synthesize a `Resolve` which has a top-level + // package which has a single document and `world` within it which describes + // the state of the component. This is then further used afterwards for + // bindings generation as-if a `*.wit` file was input. + let decoded = wit_component::decode(name, binary) .context("failed to extract interface information from component")?; + let (resolve, world_id) = match decoded { + DecodedWasm::WitPackage(..) => bail!("unexpected wit package as input"), + DecodedWasm::Component(resolve, world_id) => (resolve, world_id), + }; // Components are complicated, there's no real way around that. To // handle all the work of parsing a component and figuring out how to @@ -72,15 +76,16 @@ pub fn generate( // With all that prep work delegate to `WorldGenerator::generate` here // to generate all the type-level descriptions for this component now // that the interfaces in/out are understood. - gen.generate(&world, files); + gen.generate(&resolve, world_id, files); // And finally generate the code necessary to instantiate the given // component to this method using the `Component` that // `wasmtime-environ` parsed. - gen.instantiate(&component, &modules, &world); + gen.instantiate(&component, &modules, &resolve, world_id); gen.finish_component(name, files); + let world = &resolve.worlds[world_id]; let imports = world .imports .iter() @@ -106,7 +111,7 @@ pub fn generate( Export::Instance(_) | Export::Module(_) | Export::LiftedFunction { .. } ) }) - .map(|expt| expt.0.to_lower_camel_case()) + .map(|expt| (expt.0.to_lower_camel_case(), expt.1.clone())) .collect(); Ok(ComponentInfo { imports, exports }) diff --git a/crates/js-component-bindgen/src/lib.rs b/crates/js-component-bindgen/src/lib.rs index 761a69198..1e12233a6 100644 --- a/crates/js-component-bindgen/src/lib.rs +++ b/crates/js-component-bindgen/src/lib.rs @@ -6,6 +6,7 @@ mod ns; mod source; pub use bindgen::GenerationOpts; +use wasmtime_environ::component::Export; /// Calls [`write!`] with the passed arguments and unwraps the result. /// @@ -36,7 +37,7 @@ macro_rules! uwriteln { pub struct Transpiled { pub files: Vec<(String, Vec)>, pub imports: Vec, - pub exports: Vec, + pub exports: Vec<(String, Export)>, } /// Generate the JS transpilation bindgen for a given Wasm component binary diff --git a/crates/wasm-tools-component/src/lib.rs b/crates/wasm-tools-component/src/lib.rs index fbfe59e13..33c4d6ba1 100644 --- a/crates/wasm-tools-component/src/lib.rs +++ b/crates/wasm-tools-component/src/lib.rs @@ -1,7 +1,6 @@ -use std::{path::Path, sync::Once}; +use std::sync::Once; use wasmparser; -use wit_component::{decode_world, ComponentEncoder, StringEncoding, WorldPrinter}; -use wit_parser::Document; +use wit_component::{ComponentEncoder, DecodedWasm, DocumentPrinter}; wit_bindgen_guest_rust::generate!("wasm-tools.wit"); @@ -34,61 +33,47 @@ impl wasm_tools_js::WasmToolsJs for WasmToolsJs { } fn component_new( - binary: Option>, - opts: Option, + binary: Vec, + adapters: Option)>>, ) -> Result, String> { init(); - let mut encoder = ComponentEncoder::default(); + let mut encoder = ComponentEncoder::default() + .validate(true) + .module(&binary) + .map_err(|e| format!("Failed to decode Wasm\n{:?}", e))?; - if let Some(opts) = opts { - if let Some(adapters) = opts.adapters { - for (name, binary) in adapters { - encoder = encoder - .adapter(&name, &binary) - .map_err(|e| format!("{:?}", e))?; - } - } - if let Some(types_only) = opts.types_only { - if opts.wit.is_none() { - return Err("Must provide a WIT for types-only component generation.".into()); - } - encoder = encoder.types_only(types_only); - } - if let Some(wit) = opts.wit { - let encoding = match opts.string_encoding { - Some(wasm_tools_js::StringEncoding::Utf8) | None => StringEncoding::UTF8, - Some(wasm_tools_js::StringEncoding::Utf16) => StringEncoding::UTF16, - Some(wasm_tools_js::StringEncoding::CompactUtf16) => { - StringEncoding::CompactUTF16 - } - }; - let doc = - Document::parse(Path::new(""), &wit).map_err(|e| format!("{:?}", e))?; + if let Some(adapters) = adapters { + for (name, binary) in adapters { encoder = encoder - .world(doc.into_world().map_err(|e| format!("{:?}", e))?, encoding) + .adapter(&name, &binary) .map_err(|e| format!("{:?}", e))?; } } - if let Some(binary) = binary { - encoder = encoder.module(&binary).map_err(|e| format!("{:?}", e))?; - } - - let bytes = encoder.encode().map_err(|e| format!("{:?}", e))?; + let bytes = encoder + .encode() + .map_err(|e| format!("failed to encode a component from module\n${:?}", e))?; Ok(bytes) } - fn component_wit(binary: Vec) -> Result { + fn component_wit(binary: Vec, name: Option) -> Result { init(); - let world = decode_world("component", &binary) - .map_err(|e| format!("Failed to decode world\n{:?}", e))?; - let mut printer = WorldPrinter::default(); - let output = printer - .print(&world) - .map_err(|e| format!("Failed to print world\n{:?}", e))?; + let decoded = wit_component::decode(&name.unwrap_or(String::from("component")), &binary) + .map_err(|e| format!("Failed to decode wit component\n{:?}", e))?; + + // let world = decode_world("component", &binary); + + let doc = match &decoded { + DecodedWasm::WitPackage(_resolve, _pkg) => panic!("Unexpected wit package"), + DecodedWasm::Component(resolve, world) => resolve.worlds[*world].document, + }; + + let output = DocumentPrinter::default() + .print(decoded.resolve(), doc) + .map_err(|e| format!("Unable to print wit\n${:?}", e))?; Ok(output) } diff --git a/crates/wasm-tools-component/wasm-tools.wit b/crates/wasm-tools-component/wasm-tools.wit index 287183cd4..49c8f94be 100644 --- a/crates/wasm-tools-component/wasm-tools.wit +++ b/crates/wasm-tools-component/wasm-tools.wit @@ -11,28 +11,6 @@ world wasm-tools-js { /// Translate the WebAssembly binary format to text print: func(binary: list) -> result - record component-opts { - /// The "world" that the input binary implements. - /// - /// This argument is a `*.wit` file which describes the imports and exports - /// of the core wasm module. Users of `wit-bindgen` don't need this as those - /// generators already embed this information into the input core wasm - /// binary. - wit: option, - /// Generate a "types only" component which is a binary encoding of the - /// input wit file or the wit already encoded into the module. - types-only: option, - /// An adapter module can be used to translate the `wasi_snapshot_preview1` - /// ABI, for example, to one that uses the component model. - adapters: option>>>, - /// The expected string encoding format for the component. - /// - /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. - /// This is only applicable to the `--wit` argument to describe the string - /// encoding of the functions in that world. - string-encoding: option - } - enum string-encoding { utf8, utf16, @@ -40,10 +18,10 @@ world wasm-tools-js { } /// Create a component from a core wasm binary - component-new: func(binary: option>, opts: option) -> result, string> + component-new: func(binary: list, adapters: option>>>) -> result, string> - /// Extract a *.wit interface from a component - component-wit: func(binary: list) -> result + /// Extract a *.wit interface from a component, optionally providing a document name to extract + component-wit: func(binary: list, document: option) -> result /// Extract the core modules from a component /// (strictly speaking this makes it Wasm Tools + Extract Core Modules) diff --git a/package.json b/package.json index d6a0b505f..6763f703e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "build:transpile:js-component-bindgen-component": "jsct transpile obj/js-component-bindgen-component.wasm --map console=../lib/console.js --out-dir obj", "build:transpile:wasm-tools": "jsct transpile obj/wasm-tools.wasm --map console=../lib/console.js --out-dir obj", "lint": "eslint -c eslintrc.cjs src/**/*.js", - "test": "mocha -u tdd test/test.js --timeout 60000", - "test:dev": "mocha -u tdd -b test/test.js --timeout 60000 -n conditions=test" + "test": "mocha -u tdd test/test.js --timeout 120000", + "test:dev": "mocha -u tdd -b test/test.js --timeout 120000 -n conditions=test" } } diff --git a/src/cmd/transpile.js b/src/cmd/transpile.js index 2a3a666ad..27d6cf60d 100644 --- a/src/cmd/transpile.js +++ b/src/cmd/transpile.js @@ -75,7 +75,7 @@ async function wasm2Js (source) { * optimize?: bool, * optArgs?: string[], * }} opts - * @returns {Promise<{ files: { [filename: string]: Uint8Array }, imports: string[], exports: string[] }>} + * @returns {Promise<{ files: { [filename: string]: Uint8Array }, imports: string[], exports: [string, 'function' | 'instance'][] }>} */ export async function transpileComponent (component, opts = {}) { let spinner; @@ -153,17 +153,17 @@ export async function transpileComponent (component, opts = {}) { imports.map((impt, i) => `import * as import${i} from '${impt}';`).join('\n')} ${source.replace('export async function instantiate', 'async function instantiate')} -let ${exports.map(name => '_' + name).join(', ')}; +let ${exports.filter(([, ty]) => ty === 'function').map(([name]) => '_' + name).join(', ')}; -${exports.map(name => `\nexport function ${name} () { +${exports.map(([name, ty]) => ty === 'function' ? `\nexport function ${name} () { return _${name}.apply(this, arguments); -}`).join('\n')} +}` : `\nexport let ${name};`).join('\n')} const asmInit = [${asms}]; ${opts.tlaCompat ? 'export ' : ''}const $init = (async () => { let idx = 0; - ({ ${exports.map(name => `${name}: _${name}`).join(',\n')} } = await instantiate(n => idx++, { + ({ ${exports.map(([name, ty]) => ty === 'function' ? `${name}: _${name}` : `${name}`).join(',\n')} } = await instantiate(n => idx++, { ${imports.map((impt, i) => ` '${impt}': import${i},`).join('\n')} }, (i, imports) => ({ exports: asmInit[i](imports) }))); })(); diff --git a/src/cmd/wasm-tools.js b/src/cmd/wasm-tools.js index 3a253d986..73c204cd3 100644 --- a/src/cmd/wasm-tools.js +++ b/src/cmd/wasm-tools.js @@ -25,7 +25,7 @@ export async function print(file, opts) { export async function componentWit(file, opts) { const source = await readFile(file); - const output = componentWitFn(source); + const output = componentWitFn(source, opts.document); if (opts.output) { await writeFile(opts.output, output); } else { @@ -35,10 +35,9 @@ export async function componentWit(file, opts) { export async function componentNew(file, opts) { const source = file ? await readFile(file) : null; - if (opts.wit) - opts.wit = await readFile(opts.wit, 'utf8'); - if (opts.adapt) { - opts.adapters = await Promise.all(opts.adapt.map(async adapt => { + let adapters = null; + if (opts.adapt) + adapters = await Promise.all(opts.adapt.map(async adapt => { let adapter; if (adapt.includes('=')) adapter = adapt.split('='); @@ -47,9 +46,6 @@ export async function componentNew(file, opts) { adapter[1] = await readFile(adapter[1]); return adapter; })); - } - if (opts.encoding) - opts.stringEncoding = opts.encoding; - const output = componentNewFn(source, opts); + const output = componentNewFn(source, adapters); await writeFile(opts.output, output); } diff --git a/src/jsct.js b/src/jsct.js index ecb74cee2..681384179 100755 --- a/src/jsct.js +++ b/src/jsct.js @@ -48,6 +48,7 @@ program.command('opt') program.command('wit') .description('extract the WIT from a WebAssembly Component [wasm-tools component wit]') .argument('', 'Wasm component binary filepath') + .option('-d, --document ', 'WIT document of a package to print') .option('-o, --output ', 'WIT output file path') .action(asyncAction(componentWit)); @@ -65,13 +66,10 @@ program.command('parse') program.command('new') .description('create a WebAssembly component adapted from a component core Wasm [wasm-tools component new]') - .argument('[module]', 'Wasm core module filepath') + .argument('[core-module]', 'Wasm core module filepath') .requiredOption('-o, --output ', 'Wasm component output filepath') .option('--name ', 'custom output name') - .option('--wit ', 'WIT file to use') - .option('--types-only', 'types only component generation') .option('--adapt <[NAME=]adapter...>', 'component adapters to apply') - .option('--encoding ', 'string encoding for WIT') .action(asyncAction(componentNew)); program.parse(); diff --git a/test/api.js b/test/api.js index 40bce8c5f..497ab968b 100644 --- a/test/api.js +++ b/test/api.js @@ -1,22 +1,22 @@ -import { ok, strictEqual } from 'node:assert'; +import { deepStrictEqual, ok, strictEqual } from 'node:assert'; import { readFile } from 'node:fs/promises'; import { transpile, opt, print, parse, componentWit, componentNew } from 'js-component-tools'; export async function apiTest (fixtures) { suite('API', () => { test('Transpile', async () => { - const name = fixtures[0].replace('.component.wasm', ''); - const component = await readFile(`test/fixtures/${fixtures[0]}`); + const name = 'exports_only'; + const component = await readFile(`test/fixtures/${name}.component.wasm`); const { files, imports, exports } = await transpile(component, { name }); strictEqual(imports.length, 0); strictEqual(exports.length, 1); - strictEqual(exports[0], 'thunk'); + deepStrictEqual(exports[0], ['thunk', 'function']); ok(files[name + '.js']); }); test('Transpile & Optimize & Minify', async () => { - const name = fixtures[0].replace('.component.wasm', ''); - const component = await readFile(`test/fixtures/${fixtures[0]}`); + const name = 'exports_only'; + const component = await readFile(`test/fixtures/${name}.component.wasm`); const { files, imports, exports } = await transpile(component, { name, minify: true, @@ -27,13 +27,13 @@ export async function apiTest (fixtures) { }); strictEqual(imports.length, 0); strictEqual(exports.length, 1); - strictEqual(exports[0], 'thunk'); + deepStrictEqual(exports[0], ['thunk', 'function']); ok(files[name + '.js'].length < 8000); }); test('Transpile to JS', async () => { - const name = fixtures[1].replace('.component.wasm', ''); - const component = await readFile(`test/fixtures/${fixtures[1]}`); + const name = 'flavorful'; + const component = await readFile(`test/fixtures/${name}.component.wasm`); const { files, imports, exports } = await transpile(component, { map: { 'testwasi': './wasi.js' @@ -45,24 +45,25 @@ export async function apiTest (fixtures) { js: true, }); strictEqual(imports.length, 2); - strictEqual(exports.length, 10); - strictEqual(exports[0], 'testImports'); + strictEqual(exports.length, 2); + deepStrictEqual(exports[0], ['exports', 'instance']); + deepStrictEqual(exports[1], ['testImports', 'function']); const source = Buffer.from(files[name + '.js']).toString(); ok(source.includes('./wasi.js')); ok(source.includes('testwasi')); ok(source.includes('FUNCTION_TABLE')); - for (let i = 0; i < 10; i++) - ok(source.includes(exports[i])); + for (let i = 0; i < 2; i++) + ok(source.includes(exports[i][0])); }); test('Optimize', async () => { - const component = await readFile(`test/fixtures/${fixtures[0]}`); + const component = await readFile(`test/fixtures/exports_only.component.wasm`); const { component: optimizedComponent } = await opt(component); ok(optimizedComponent.byteLength < component.byteLength); }); test('Print & Parse', async () => { - const component = await readFile(`test/fixtures/${fixtures[0]}`); + const component = await readFile(`test/fixtures/exports_only.component.wasm`); const output = await print(component); strictEqual(output.slice(0, 10), '(component'); @@ -71,21 +72,20 @@ export async function apiTest (fixtures) { }); test('Wit & New', async () => { - const component = await readFile(`test/fixtures/${fixtures[0]}`); + const component = await readFile(`test/fixtures/exports_only.component.wasm`); const wit = await componentWit(component); - strictEqual(wit.slice(0, 17), 'world component {'); + strictEqual(wit.slice(0, 25), 'default world component {'); - const generatedComponent = await componentNew(null, { wit, typesOnly: true }); - const output = await print(generatedComponent); - strictEqual(output.slice(0, 10), '(component'); + // TODO: reenable when dummy is supported + // const generatedComponent = await componentNew(null, { wit }); + // const output = await print(generatedComponent); + // strictEqual(output.slice(0, 10), '(component'); }); test('Component new adapt', async () => { const component = await readFile(`test/fixtures/exitcode.wasm`); - const generatedComponent = await componentNew(component, { - adapters: [['wasi_snapshot_preview1', await readFile('test/fixtures/wasi_snapshot_preview1.wasm')]] - }); + const generatedComponent = await componentNew(component, [['wasi_snapshot_preview1', await readFile('test/fixtures/wasi_snapshot_preview1.wasm')]]); await print(generatedComponent); }); diff --git a/test/cli.js b/test/cli.js index d6b37dcad..7f7abeb18 100644 --- a/test/cli.js +++ b/test/cli.js @@ -19,8 +19,8 @@ export async function cliTest (fixtures) { test('Transpile', async () => { try { - const name = fixtures[0].replace('.component.wasm', ''); - const { stderr } = await exec(jsctPath, 'transpile', `test/fixtures/${fixtures[0]}`, '--name', name, '-o', outDir); + const name = 'exports_only'; + const { stderr } = await exec(jsctPath, 'transpile', `test/fixtures/${name}.component.wasm`, '--name', name, '-o', outDir); strictEqual(stderr, ''); const source = await readFile(`${outDir}/${name}.js`); ok(source.toString().includes('export function thunk')); @@ -32,8 +32,8 @@ export async function cliTest (fixtures) { test('Transpile & Optimize & Minify', async () => { try { - const name = fixtures[0].replace('.component.wasm', ''); - const { stderr } = await exec(jsctPath, 'transpile', `test/fixtures/${fixtures[0]}`, '--name', name, '--valid-lifting-optimization', '--tla-compat', '--optimize', '--minify', '--base64-cutoff=0', '-o', outDir); + const name = 'exports_only'; + const { stderr } = await exec(jsctPath, 'transpile', `test/fixtures/${name}.component.wasm`, '--name', name, '--valid-lifting-optimization', '--tla-compat', '--optimize', '--minify', '--base64-cutoff=0', '-o', outDir); strictEqual(stderr, ''); const source = await readFile(`${outDir}/${name}.js`); ok(source.toString().includes('export function thunk')); @@ -45,8 +45,8 @@ export async function cliTest (fixtures) { test('Transpile to JS', async () => { try { - const name = fixtures[1].replace('.component.wasm', ''); - const { stderr } = await exec(jsctPath, 'transpile', `test/fixtures/${fixtures[1]}`, '--name', name, '--map', 'testwasi=./wasi.js', '--valid-lifting-optimization', '--tla-compat', '--js', '--base64-cutoff=0', '-o', outDir); + const name = 'flavorful'; + const { stderr } = await exec(jsctPath, 'transpile', `test/fixtures/${name}.component.wasm`, '--name', name, '--map', 'testwasi=./wasi.js', '--valid-lifting-optimization', '--tla-compat', '--js', '--base64-cutoff=0', '-o', outDir); strictEqual(stderr, ''); const source = await readFile(`${outDir}/${name}.js`, 'utf8'); ok(source.includes('./wasi.js')); @@ -61,8 +61,8 @@ export async function cliTest (fixtures) { test('Optimize', async () => { try { - const component = await readFile(`test/fixtures/${fixtures[0]}`); - const { stderr, stdout } = await exec(jsctPath, 'opt', `test/fixtures/${fixtures[0]}`, '-o', outFile); + const component = await readFile(`test/fixtures/exports_only.component.wasm`); + const { stderr, stdout } = await exec(jsctPath, 'opt', `test/fixtures/exports_only.component.wasm`, '-o', outFile); strictEqual(stderr, ''); ok(stdout.includes('Core Module 1:')); const optimizedComponent = await readFile(outFile); @@ -75,11 +75,11 @@ export async function cliTest (fixtures) { test('Print & Parse', async () => { try { - const { stderr, stdout } = await exec(jsctPath, 'print', `test/fixtures/${fixtures[0]}`); + const { stderr, stdout } = await exec(jsctPath, 'print', `test/fixtures/exports_only.component.wasm`); strictEqual(stderr, ''); strictEqual(stdout.slice(0, 10), '(component'); { - const { stderr, stdout } = await exec(jsctPath, 'print', `test/fixtures/${fixtures[0]}`, '-o', outFile); + const { stderr, stdout } = await exec(jsctPath, 'print', `test/fixtures/exports_only.component.wasm`, '-o', outFile); strictEqual(stderr, ''); strictEqual(stdout, ''); } @@ -97,27 +97,28 @@ export async function cliTest (fixtures) { test('Wit & New', async () => { try { - const { stderr, stdout } = await exec(jsctPath, 'wit', `test/fixtures/${fixtures[0]}`); + const { stderr, stdout } = await exec(jsctPath, 'wit', `test/fixtures/exports_only.component.wasm`); strictEqual(stderr, ''); ok(stdout.includes('world component {')); { - const { stderr, stdout } = await exec(jsctPath, 'wit', `test/fixtures/${fixtures[0]}`, '-o', outFile); + const { stderr, stdout } = await exec(jsctPath, 'wit', `test/fixtures/exports_only.component.wasm`, '-o', outFile); strictEqual(stderr, ''); strictEqual(stdout, ''); } - { - const { stderr, stdout } = await exec(jsctPath, 'new', '--types-only', '--wit', outFile, '-o', outFile); - strictEqual(stderr, ''); - strictEqual(stdout, ''); - } + // TODO: reenable for dummy generation + // { + // const { stderr, stdout } = await exec(jsctPath, 'new', '--types-only', '--wit', outFile, '-o', outFile); + // strictEqual(stderr, ''); + // strictEqual(stdout, ''); + // } - { - const { stderr, stdout } = await exec(jsctPath, 'print', outFile); - strictEqual(stderr, ''); - strictEqual(stdout.slice(0, 10), '(component'); - } + // { + // const { stderr, stdout } = await exec(jsctPath, 'print', outFile); + // strictEqual(stderr, ''); + // strictEqual(stdout.slice(0, 10), '(component'); + // } } finally { await cleanup(); diff --git a/test/codegen.js b/test/codegen.js index e05d698ca..5315fc5f8 100644 --- a/test/codegen.js +++ b/test/codegen.js @@ -42,6 +42,7 @@ export async function codegenTest (fixtures) { } // TypeScript tests _must_ run after codegen to complete successfully + // This is due to type checking against generated bindings test('TypeScript Compilation', async () => { var { stderr } = await exec(tscPath, '-p', 'test/tsconfig.json'); strictEqual(stderr, ''); diff --git a/test/fixtures/char.component.wasm b/test/fixtures/char.component.wasm new file mode 100644 index 000000000..02c559067 Binary files /dev/null and b/test/fixtures/char.component.wasm differ diff --git a/test/fixtures/conventions.component.wasm b/test/fixtures/conventions.component.wasm new file mode 100644 index 000000000..77f932ea2 Binary files /dev/null and b/test/fixtures/conventions.component.wasm differ diff --git a/test/fixtures/empty.component.wasm b/test/fixtures/empty.component.wasm new file mode 100644 index 000000000..9d1dc4e9c Binary files /dev/null and b/test/fixtures/empty.component.wasm differ diff --git a/test/fixtures/exports_only.component.wasm b/test/fixtures/exports_only.component.wasm index 5f6b2d2f2..1545d8054 100644 Binary files a/test/fixtures/exports_only.component.wasm and b/test/fixtures/exports_only.component.wasm differ diff --git a/test/fixtures/flags.component.wasm b/test/fixtures/flags.component.wasm new file mode 100644 index 000000000..ae4f2a7b6 Binary files /dev/null and b/test/fixtures/flags.component.wasm differ diff --git a/test/fixtures/flavorful.component.wasm b/test/fixtures/flavorful.component.wasm index e76a86e31..3fbde1f05 100644 Binary files a/test/fixtures/flavorful.component.wasm and b/test/fixtures/flavorful.component.wasm differ diff --git a/test/fixtures/floats.component.wasm b/test/fixtures/floats.component.wasm new file mode 100644 index 000000000..bd1b03b83 Binary files /dev/null and b/test/fixtures/floats.component.wasm differ diff --git a/test/fixtures/import-func.component.wasm b/test/fixtures/import-func.component.wasm new file mode 100644 index 000000000..fbe9272cd Binary files /dev/null and b/test/fixtures/import-func.component.wasm differ diff --git a/test/fixtures/integers.component.wasm b/test/fixtures/integers.component.wasm new file mode 100644 index 000000000..77e171e35 Binary files /dev/null and b/test/fixtures/integers.component.wasm differ diff --git a/test/fixtures/invalid.component.wasm b/test/fixtures/invalid.component.wasm index e4fc34d90..7389ef32d 100644 Binary files a/test/fixtures/invalid.component.wasm and b/test/fixtures/invalid.component.wasm differ diff --git a/test/fixtures/just-export.component.wasm b/test/fixtures/just-export.component.wasm new file mode 100644 index 000000000..f52c6efbb Binary files /dev/null and b/test/fixtures/just-export.component.wasm differ diff --git a/test/fixtures/keywords.component.wasm b/test/fixtures/keywords.component.wasm new file mode 100644 index 000000000..006054cbf Binary files /dev/null and b/test/fixtures/keywords.component.wasm differ diff --git a/test/fixtures/lists.component.wasm b/test/fixtures/lists.component.wasm index 61dfda9ad..4620520f8 100644 Binary files a/test/fixtures/lists.component.wasm and b/test/fixtures/lists.component.wasm differ diff --git a/test/fixtures/many-arguments.component.wasm b/test/fixtures/many-arguments.component.wasm new file mode 100644 index 000000000..3fc952da8 Binary files /dev/null and b/test/fixtures/many-arguments.component.wasm differ diff --git a/test/fixtures/many_arguments.component.wasm b/test/fixtures/many_arguments.component.wasm index 5e6616b59..b52b9e6d1 100644 Binary files a/test/fixtures/many_arguments.component.wasm and b/test/fixtures/many_arguments.component.wasm differ diff --git a/test/fixtures/multi-return.component.wasm b/test/fixtures/multi-return.component.wasm new file mode 100644 index 000000000..46a847242 Binary files /dev/null and b/test/fixtures/multi-return.component.wasm differ diff --git a/test/fixtures/numbers.component.wasm b/test/fixtures/numbers.component.wasm index db4f18d8e..b50b9e261 100644 Binary files a/test/fixtures/numbers.component.wasm and b/test/fixtures/numbers.component.wasm differ diff --git a/test/fixtures/records.component.wasm b/test/fixtures/records.component.wasm index 80711d85c..584ba41a1 100644 Binary files a/test/fixtures/records.component.wasm and b/test/fixtures/records.component.wasm differ diff --git a/test/fixtures/rename-interface.component.wasm b/test/fixtures/rename-interface.component.wasm new file mode 100644 index 000000000..1418df0a5 Binary files /dev/null and b/test/fixtures/rename-interface.component.wasm differ diff --git a/test/fixtures/results.component.wasm b/test/fixtures/results.component.wasm index 85f84610e..30d79cc9a 100644 Binary files a/test/fixtures/results.component.wasm and b/test/fixtures/results.component.wasm differ diff --git a/test/fixtures/simple-functions.component.wasm b/test/fixtures/simple-functions.component.wasm new file mode 100644 index 000000000..06c423fd5 Binary files /dev/null and b/test/fixtures/simple-functions.component.wasm differ diff --git a/test/fixtures/simple-lists.component.wasm b/test/fixtures/simple-lists.component.wasm new file mode 100644 index 000000000..2f8b8b293 Binary files /dev/null and b/test/fixtures/simple-lists.component.wasm differ diff --git a/test/fixtures/small-anonymous.component.wasm b/test/fixtures/small-anonymous.component.wasm new file mode 100644 index 000000000..52141888a Binary files /dev/null and b/test/fixtures/small-anonymous.component.wasm differ diff --git a/test/fixtures/smoke-default.component.wasm b/test/fixtures/smoke-default.component.wasm new file mode 100644 index 000000000..a89c376de Binary files /dev/null and b/test/fixtures/smoke-default.component.wasm differ diff --git a/test/fixtures/smoke-export.component.wasm b/test/fixtures/smoke-export.component.wasm new file mode 100644 index 000000000..c83b373b3 Binary files /dev/null and b/test/fixtures/smoke-export.component.wasm differ diff --git a/test/fixtures/smoke.component.wasm b/test/fixtures/smoke.component.wasm index 0138fe313..6288ab540 100644 Binary files a/test/fixtures/smoke.component.wasm and b/test/fixtures/smoke.component.wasm differ diff --git a/test/fixtures/strings.component.wasm b/test/fixtures/strings.component.wasm new file mode 100644 index 000000000..5d734f308 Binary files /dev/null and b/test/fixtures/strings.component.wasm differ diff --git a/test/fixtures/strings_utf16.component.wasm b/test/fixtures/strings_utf16.component.wasm index 694ddf6a4..f096b6eab 100644 Binary files a/test/fixtures/strings_utf16.component.wasm and b/test/fixtures/strings_utf16.component.wasm differ diff --git a/test/fixtures/unions.component.wasm b/test/fixtures/unions.component.wasm index b5affbbfe..9bab174c0 100644 Binary files a/test/fixtures/unions.component.wasm and b/test/fixtures/unions.component.wasm differ diff --git a/test/fixtures/use-across-interfaces.component.wasm b/test/fixtures/use-across-interfaces.component.wasm new file mode 100644 index 000000000..149e2816f Binary files /dev/null and b/test/fixtures/use-across-interfaces.component.wasm differ diff --git a/test/fixtures/variants.component.wasm b/test/fixtures/variants.component.wasm index 1dde0425f..1df53a382 100644 Binary files a/test/fixtures/variants.component.wasm and b/test/fixtures/variants.component.wasm differ diff --git a/test/fixtures/wasi_snapshot_preview1.wasm b/test/fixtures/wasi_snapshot_preview1.wasm old mode 100755 new mode 100644 index bd4078db6..dcd242986 Binary files a/test/fixtures/wasi_snapshot_preview1.wasm and b/test/fixtures/wasi_snapshot_preview1.wasm differ diff --git a/test/runtime/flavorful.ts b/test/runtime/flavorful.ts index 14a62d4df..26a28d37e 100644 --- a/test/runtime/flavorful.ts +++ b/test/runtime/flavorful.ts @@ -51,26 +51,26 @@ export async function run () { const wasm = await import('../output/flavorful/flavorful.js'); wasm.testImports(); - wasm.fListInRecord1({ a: "list_in_record1" }); - assert.deepStrictEqual(wasm.fListInRecord2(), { a: "list_in_record2" }); + wasm.exports.fListInRecord1({ a: "list_in_record1" }); + assert.deepStrictEqual(wasm.exports.fListInRecord2(), { a: "list_in_record2" }); assert.deepStrictEqual( - wasm.fListInRecord3({ a: "list_in_record3 input" }), + wasm.exports.fListInRecord3({ a: "list_in_record3 input" }), { a: "list_in_record3 output" }, ); assert.deepStrictEqual( - wasm.fListInRecord4({ a: "input4" }), + wasm.exports.fListInRecord4({ a: "input4" }), { a: "result4" }, ); - wasm.fListInVariant1("foo", { tag: 'err', val: 'bar' }, { tag: 0, val: 'baz' }); + wasm.exports.fListInVariant1("foo", { tag: 'err', val: 'bar' }, { tag: 0, val: 'baz' }); - assert.deepStrictEqual(wasm.fListInVariant2(), "list_in_variant2"); - assert.deepStrictEqual(wasm.fListInVariant3("input3"), "output3"); + assert.deepStrictEqual(wasm.exports.fListInVariant2(), "list_in_variant2"); + assert.deepStrictEqual(wasm.exports.fListInVariant3("input3"), "output3"); try { - wasm.errnoResult(); + wasm.exports.errnoResult(); assert.ok(false); } catch (e: any) { @@ -79,7 +79,7 @@ export async function run () { assert.strictEqual(e.payload, 'b'); } - const [r1, r2] = wasm.listTypedefs("typedef1", ["typedef2"]); + const [r1, r2] = wasm.exports.listTypedefs("typedef1", ["typedef2"]); assert.deepStrictEqual(r1, (new TextEncoder()).encode('typedef3')); assert.deepStrictEqual(r2, ['typedef4']); } diff --git a/test/runtime/lists.ts b/test/runtime/lists.ts index df2b28aef..7820bac3c 100644 --- a/test/runtime/lists.ts +++ b/test/runtime/lists.ts @@ -104,27 +104,27 @@ async function run() { const bytes = wasm.allocatedBytes(); wasm.testImports(); - wasm.emptyListParam(new Uint8Array([])); - wasm.emptyStringParam(''); - wasm.listParam(new Uint8Array([1, 2, 3, 4]).buffer); - wasm.listParam2("foo"); - wasm.listParam3(["foo", "bar", "baz"]); - wasm.listParam4([["foo", "bar"], ["baz"]]); - assert.deepStrictEqual(Array.from(wasm.emptyListResult()), []); - assert.deepStrictEqual(wasm.emptyStringResult(), ""); - assert.deepStrictEqual(Array.from(wasm.listResult()), [1, 2, 3, 4, 5]); - assert.deepStrictEqual(wasm.listResult2(), "hello!"); - assert.deepStrictEqual(wasm.listResult3(), ["hello,", "world!"]); + wasm.exports.emptyListParam(new Uint8Array([])); + wasm.exports.emptyStringParam(''); + wasm.exports.listParam(new Uint8Array([1, 2, 3, 4]).buffer); + wasm.exports.listParam2("foo"); + wasm.exports.listParam3(["foo", "bar", "baz"]); + wasm.exports.listParam4([["foo", "bar"], ["baz"]]); + assert.deepStrictEqual(Array.from(wasm.exports.emptyListResult()), []); + assert.deepStrictEqual(wasm.exports.emptyStringResult(), ""); + assert.deepStrictEqual(Array.from(wasm.exports.listResult()), [1, 2, 3, 4, 5]); + assert.deepStrictEqual(wasm.exports.listResult2(), "hello!"); + assert.deepStrictEqual(wasm.exports.listResult3(), ["hello,", "world!"]); const buffer = new ArrayBuffer(8); (new Uint8Array(buffer)).set(new Uint8Array([1, 2, 3, 4]), 2); // Create a view of the four bytes in the middle of the buffer const view = new Uint8Array(buffer, 2, 4); - assert.deepStrictEqual(Array.from(wasm.listRoundtrip(view)), [1, 2, 3, 4]); + assert.deepStrictEqual(Array.from(wasm.exports.listRoundtrip(view)), [1, 2, 3, 4]); - assert.deepStrictEqual(wasm.stringRoundtrip("x"), "x"); - assert.deepStrictEqual(wasm.stringRoundtrip(""), ""); - assert.deepStrictEqual(wasm.stringRoundtrip("hello ⚑ world"), "hello ⚑ world"); + assert.deepStrictEqual(wasm.exports.stringRoundtrip("x"), "x"); + assert.deepStrictEqual(wasm.exports.stringRoundtrip(""), ""); + assert.deepStrictEqual(wasm.exports.stringRoundtrip("hello ⚑ world"), "hello ⚑ world"); // Ensure that we properly called `free` everywhere in all the glue that we // needed to. diff --git a/test/runtime/numbers.ts b/test/runtime/numbers.ts index 5ca81c19b..25b95b1fb 100644 --- a/test/runtime/numbers.ts +++ b/test/runtime/numbers.ts @@ -36,52 +36,52 @@ async function run() { wasm.testImports(); - assertEq(wasm.roundtripU8(1), 1); - assertEq(wasm.roundtripU8((1 << 8) - 1), (1 << 8) - 1); + assertEq(wasm.exports.roundtripU8(1), 1); + assertEq(wasm.exports.roundtripU8((1 << 8) - 1), (1 << 8) - 1); - assertEq(wasm.roundtripS8(1), 1); - assertEq(wasm.roundtripS8((1 << 7) - 1), (1 << 7) - 1); - assertEq(wasm.roundtripS8(-(1 << 7)), -(1 << 7)); + assertEq(wasm.exports.roundtripS8(1), 1); + assertEq(wasm.exports.roundtripS8((1 << 7) - 1), (1 << 7) - 1); + assertEq(wasm.exports.roundtripS8(-(1 << 7)), -(1 << 7)); - assertEq(wasm.roundtripU16(1), 1); - assertEq(wasm.roundtripU16((1 << 16) - 1), (1 << 16) - 1); + assertEq(wasm.exports.roundtripU16(1), 1); + assertEq(wasm.exports.roundtripU16((1 << 16) - 1), (1 << 16) - 1); - assertEq(wasm.roundtripS16(1), 1); - assertEq(wasm.roundtripS16((1 << 15) - 1), (1 << 15) - 1); - assertEq(wasm.roundtripS16(-(1 << 15)), -(1 << 15)); + assertEq(wasm.exports.roundtripS16(1), 1); + assertEq(wasm.exports.roundtripS16((1 << 15) - 1), (1 << 15) - 1); + assertEq(wasm.exports.roundtripS16(-(1 << 15)), -(1 << 15)); - assertEq(wasm.roundtripU32(1), 1); - assertEq(wasm.roundtripU32(~0 >>> 0), ~0 >>> 0); + assertEq(wasm.exports.roundtripU32(1), 1); + assertEq(wasm.exports.roundtripU32(~0 >>> 0), ~0 >>> 0); - assertEq(wasm.roundtripS32(1), 1); - assertEq(wasm.roundtripS32(((1 << 31) - 1) >>> 0), ((1 << 31) - 1) >>> 0); - assertEq(wasm.roundtripS32(1 << 31), 1 << 31); + assertEq(wasm.exports.roundtripS32(1), 1); + assertEq(wasm.exports.roundtripS32(((1 << 31) - 1) >>> 0), ((1 << 31) - 1) >>> 0); + assertEq(wasm.exports.roundtripS32(1 << 31), 1 << 31); - assertEq(wasm.roundtripU64(1n), 1n); - assertEq(wasm.roundtripU64((1n << 64n) - 1n), (1n << 64n) - 1n); + assertEq(wasm.exports.roundtripU64(1n), 1n); + assertEq(wasm.exports.roundtripU64((1n << 64n) - 1n), (1n << 64n) - 1n); - assertEq(wasm.roundtripS64(1n), 1n); - assertEq(wasm.roundtripS64((1n << 63n) - 1n), (1n << 63n) - 1n); - assertEq(wasm.roundtripS64(-(1n << 63n)), -(1n << 63n)); + assertEq(wasm.exports.roundtripS64(1n), 1n); + assertEq(wasm.exports.roundtripS64((1n << 63n) - 1n), (1n << 63n) - 1n); + assertEq(wasm.exports.roundtripS64(-(1n << 63n)), -(1n << 63n)); - assertEq(wasm.roundtripFloat32(1), 1); - assertEq(wasm.roundtripFloat32(Infinity), Infinity); - assertEq(wasm.roundtripFloat32(-Infinity), -Infinity); - assert(Number.isNaN(wasm.roundtripFloat32(NaN))); + assertEq(wasm.exports.roundtripFloat32(1), 1); + assertEq(wasm.exports.roundtripFloat32(Infinity), Infinity); + assertEq(wasm.exports.roundtripFloat32(-Infinity), -Infinity); + assert(Number.isNaN(wasm.exports.roundtripFloat32(NaN))); - assertEq(wasm.roundtripFloat64(1), 1); - assertEq(wasm.roundtripFloat64(Infinity), Infinity); - assertEq(wasm.roundtripFloat64(-Infinity), -Infinity); - assert(Number.isNaN(wasm.roundtripFloat64(NaN))); + assertEq(wasm.exports.roundtripFloat64(1), 1); + assertEq(wasm.exports.roundtripFloat64(Infinity), Infinity); + assertEq(wasm.exports.roundtripFloat64(-Infinity), -Infinity); + assert(Number.isNaN(wasm.exports.roundtripFloat64(NaN))); - assertEq(wasm.roundtripChar('a'), 'a'); - assertEq(wasm.roundtripChar(' '), ' '); - assertEq(wasm.roundtripChar('🚩'), '🚩'); + assertEq(wasm.exports.roundtripChar('a'), 'a'); + assertEq(wasm.exports.roundtripChar(' '), ' '); + assertEq(wasm.exports.roundtripChar('🚩'), '🚩'); - wasm.setScalar(2); - assertEq(wasm.getScalar(), 2); - wasm.setScalar(4); - assertEq(wasm.getScalar(), 4); + wasm.exports.setScalar(2); + assertEq(wasm.exports.getScalar(), 2); + wasm.exports.setScalar(4); + assertEq(wasm.exports.getScalar(), 4); } await run() diff --git a/test/runtime/records.ts b/test/runtime/records.ts index 4bd2c1591..393c3d7ce 100644 --- a/test/runtime/records.ts +++ b/test/runtime/records.ts @@ -21,31 +21,31 @@ async function run() { }); wasm.testImports(); - assert.deepEqual(wasm.multipleResults(), [100, 200]); - assert.deepStrictEqual(wasm.swapTuple([1, 2]), [2, 1]); - assert.deepEqual(wasm.roundtripFlags1({ a: true }), { a: true, b: false }); - assert.deepEqual(wasm.roundtripFlags1({}), { a: false, b: false }); - assert.deepEqual(wasm.roundtripFlags1({ a: true, b: true }), { a: true, b: true }); + assert.deepEqual(wasm.exports.multipleResults(), [100, 200]); + assert.deepStrictEqual(wasm.exports.swapTuple([1, 2]), [2, 1]); + assert.deepEqual(wasm.exports.roundtripFlags1({ a: true }), { a: true, b: false }); + assert.deepEqual(wasm.exports.roundtripFlags1({}), { a: false, b: false }); + assert.deepEqual(wasm.exports.roundtripFlags1({ a: true, b: true }), { a: true, b: true }); - assert.deepEqual(wasm.roundtripFlags2({ c: true }), { c: true, d: false, e: false }); - assert.deepEqual(wasm.roundtripFlags2({}), { c: false, d: false, e: false }); - assert.deepEqual(wasm.roundtripFlags2({ d: true }), { c: false, d: true, e: false }); - assert.deepEqual(wasm.roundtripFlags2({ c: true, e: true }), { c: true, d: false, e: true }); + assert.deepEqual(wasm.exports.roundtripFlags2({ c: true }), { c: true, d: false, e: false }); + assert.deepEqual(wasm.exports.roundtripFlags2({}), { c: false, d: false, e: false }); + assert.deepEqual(wasm.exports.roundtripFlags2({ d: true }), { c: false, d: true, e: false }); + assert.deepEqual(wasm.exports.roundtripFlags2({ c: true, e: true }), { c: true, d: false, e: true }); { - const { a, b } = wasm.roundtripRecord1({ a: 8, b: {} }); + const { a, b } = wasm.exports.roundtripRecord1({ a: 8, b: {} }); assert.deepEqual(a, 8); assert.deepEqual(b, { a: false, b: false }); } { - const { a, b } = wasm.roundtripRecord1({ a: 0, b: { a: true, b: true } }); + const { a, b } = wasm.exports.roundtripRecord1({ a: 0, b: { a: true, b: true } }); assert.deepEqual(a, 0); assert.deepEqual(b, { a: true, b: true }); } - assert.deepStrictEqual(wasm.tuple0([]), []); - assert.deepStrictEqual(wasm.tuple1([1]), [1]); + assert.deepStrictEqual(wasm.exports.tuple0([]), []); + assert.deepStrictEqual(wasm.exports.tuple1([1]), [1]); } await run() diff --git a/test/runtime/variants.ts b/test/runtime/variants.ts index 51272cae2..9ee9070ba 100644 --- a/test/runtime/variants.ts +++ b/test/runtime/variants.ts @@ -36,19 +36,19 @@ async function run() { }); wasm.testImports(); - assert.deepStrictEqual(wasm.roundtripOption(1), 1); - assert.deepStrictEqual(wasm.roundtripOption(null), null); + assert.deepStrictEqual(wasm.exports.roundtripOption(1), 1); + assert.deepStrictEqual(wasm.exports.roundtripOption(null), null); // @ts-ignore - assert.deepStrictEqual(wasm.roundtripOption(undefined), null); + assert.deepStrictEqual(wasm.exports.roundtripOption(undefined), null); // @ts-ignore - assert.deepStrictEqual(wasm.roundtripOption(), null); - assert.deepStrictEqual(wasm.roundtripOption(2), 2); - assert.deepStrictEqual(wasm.roundtripResult({ tag: 'ok', val: 2 }), 2); - assert.deepStrictEqual(wasm.roundtripResult({ tag: 'ok', val: 4 }), 4); + assert.deepStrictEqual(wasm.exports.roundtripOption(), null); + assert.deepStrictEqual(wasm.exports.roundtripOption(2), 2); + assert.deepStrictEqual(wasm.exports.roundtripResult({ tag: 'ok', val: 2 }), 2); + assert.deepStrictEqual(wasm.exports.roundtripResult({ tag: 'ok', val: 4 }), 4); const f = Math.fround(5.2); try { - wasm.roundtripResult({ tag: 'err', val: f }); + wasm.exports.roundtripResult({ tag: 'err', val: f }); assert.fail('Expected an error'); } catch (e: any) { assert.strictEqual(e.constructor.name, 'ComponentError'); @@ -56,14 +56,14 @@ async function run() { assert.strictEqual(e.payload, 5); } - assert.deepStrictEqual(wasm.roundtripEnum("a"), "a"); - assert.deepStrictEqual(wasm.roundtripEnum("b"), "b"); + assert.deepStrictEqual(wasm.exports.roundtripEnum("a"), "a"); + assert.deepStrictEqual(wasm.exports.roundtripEnum("b"), "b"); - assert.deepStrictEqual(wasm.invertBool(true), false); - assert.deepStrictEqual(wasm.invertBool(false), true); + assert.deepStrictEqual(wasm.exports.invertBool(true), false); + assert.deepStrictEqual(wasm.exports.invertBool(false), true); { - const [a1, a2, a3, a4, a5, a6] = wasm.variantCasts([ + const [a1, a2, a3, a4, a5, a6] = wasm.exports.variantCasts([ { tag: 'a', val: 1 }, { tag: 'a', val: 2 }, { tag: 'a', val: 3 }, @@ -79,7 +79,7 @@ async function run() { assert.deepStrictEqual(a6, { tag: 'a', val: 6 }); } { - const [b1, b2, b3, b4, b5, b6] = wasm.variantCasts([ + const [b1, b2, b3, b4, b5, b6] = wasm.exports.variantCasts([ { tag: 'b', val: 1n }, { tag: 'b', val: 2 }, { tag: 'b', val: 3 }, @@ -96,7 +96,7 @@ async function run() { } { - const [a1, a2, a3, a4] = wasm.variantZeros([ + const [a1, a2, a3, a4] = wasm.exports.variantZeros([ { tag: 'a', val: 1 }, { tag: 'a', val: 2n }, { tag: 'a', val: 3 }, @@ -108,7 +108,7 @@ async function run() { assert.deepStrictEqual(a4, { tag: 'a', val: 4 }); } - wasm.variantTypedefs(null, false, { tag: 'err', val: undefined }); + wasm.exports.variantTypedefs(null, false, { tag: 'err', val: undefined }); } await run() diff --git a/update-tests.sh b/update-tests.sh new file mode 100755 index 000000000..67feb235d --- /dev/null +++ b/update-tests.sh @@ -0,0 +1,38 @@ +git submodule init +git submodule update +cd wit-bindgen +cargo test --workspace -p gen-host-js --test runtime --features runtime-tests + +for t in target/codegen-tests/js-*/* +do + name="$(basename $(dirname $t))" + echo "cp $t/component.wasm ../test/fixtures/${name:3}.component.wasm" + cp $t/component.wasm ../test/fixtures/${name:3}.component.wasm +done + +for t in target/debug/build/runtime-macro-*/out/*.component.wasm +do + name="$(basename $t)" + echo "cp $t ../test/fixtures/${name}" + cp $t ../test/fixtures/${name} +done + +# copy the c builds over the rust builds so they take precedence +for t in target/debug/build/runtime-macro-*/out/c-*/*.component.wasm +do + name="$(basename $(dirname $t))" + echo "cp $t ../test/fixtures/${name:2}.component.wasm" + cp $t ../test/fixtures/${name:2}.component.wasm +done + +# c utf16 tests +for t in target/debug/build/runtime-macro-*/out/c_utf16-*/*.component.wasm +do + name="$(basename $(dirname $t))" + echo "cp $t ../test/fixtures/${name:8}_utf16.component.wasm" + cp $t ../test/fixtures/${name:8}_utf16.component.wasm +done + +cd test/fixtures +rm wasi_snapshot_preview1.wasm +wget https://github.com/bytecodealliance/preview2-prototyping/releases/download/latest/wasi_snapshot_preview1.wasm diff --git a/wit-bindgen b/wit-bindgen new file mode 160000 index 000000000..98c2b1e4c --- /dev/null +++ b/wit-bindgen @@ -0,0 +1 @@ +Subproject commit 98c2b1e4cb10d3757114b7524d112511a4c7459e