From c51a342cb3ef27d176d5713084d05ac04963b6db Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Jan 2018 21:20:38 -0800 Subject: [PATCH] Rewrite wasm-bindgen with ES6 modules in mind This commit is a mostly-rewrite of the `wasm-bindgen` tool. After some recent discussions it's clear that the previous model wasn't quite going to cut it, and this iteration is one which primarily embraces ES6 modules and the idea that this is a polyfill for host bindings. The overall interface and functionality hasn't changed much but the underlying technology has now changed significantly. Previously `wasm-bindgen` would emit a JS file that acted as an ES6 module but had a bit of a wonky interface. It exposed an async function for instantiation of the wasm module, but that's the bundler's job, not ours! Instead this iteration views each input and output as a discrete ES6 module. The input wasm file is interpreted as "this *should* be an ES6 module with rich types" and the output is "well here's some ES6 modules that fulfill that contract". Notably the tool now replaces the original wasm ES6 module with a JS ES6 module that has the "rich interface". Additionally a second ES6 module is emitted (the actual wasm file) which imports and exports to the original ES6 module. This strategy is hoped to be much more amenable to bundlers and controlling how the wasm itself is instantiated. The emitted files files purely assume ES6 modules and should be able to work as-is once ES6 module integration for wasm is completed. Note that there aren't a ton of tools to pretend a wasm module is an ES6 module at the moment but those should be coming soon! In the meantime a local `wasm2es6js` hack was added to help make *something* work today. The README has also been updated with instructions for interacting with this model. --- README.md | 198 +-- crates/test-support/src/lib.rs | 120 +- crates/wasm-bindgen-cli-support/Cargo.toml | 1 + crates/wasm-bindgen-cli-support/src/js.rs | 846 +++++++++++ crates/wasm-bindgen-cli-support/src/lib.rs | 122 +- crates/wasm-bindgen-cli-support/src/mapped.rs | 118 -- crates/wasm-bindgen-cli-support/src/ts.rs | 1266 ----------------- .../src/wasm2es6js.rs | 214 +++ crates/wasm-bindgen-cli/Cargo.toml | 7 +- .../src/{main.rs => bin/wasm-bindgen.rs} | 35 +- crates/wasm-bindgen-cli/src/bin/wasm2es6js.rs | 63 + crates/wasm-bindgen-macro/src/ast.rs | 32 +- crates/wasm-bindgen-shared/src/lib.rs | 2 +- src/lib.rs | 4 +- tests/api.rs | 12 +- tests/classes.rs | 50 +- tests/imports.rs | 50 +- tests/js.rs | 79 - tests/jsobjects.rs | 76 +- tests/non-debug.rs | 8 +- tests/simple.rs | 115 +- tests/uglify.rs | 42 - 22 files changed, 1514 insertions(+), 1946 deletions(-) create mode 100644 crates/wasm-bindgen-cli-support/src/js.rs delete mode 100644 crates/wasm-bindgen-cli-support/src/mapped.rs delete mode 100644 crates/wasm-bindgen-cli-support/src/ts.rs create mode 100644 crates/wasm-bindgen-cli-support/src/wasm2es6js.rs rename crates/wasm-bindgen-cli/src/{main.rs => bin/wasm-bindgen.rs} (50%) create mode 100644 crates/wasm-bindgen-cli/src/bin/wasm2es6js.rs delete mode 100644 tests/js.rs delete mode 100644 tests/uglify.rs diff --git a/README.md b/README.md index 1f747e1b..7a01cb2d 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,23 @@ # wasm-bindgen -A CLI and Rust dependency for generating JS bindings of an interface defined in -Rust (and maybe eventually other languages!) +A CLI and Rust dependency for acting as a polyfill for [Wasm host +bindings][host], allowing you to interoperate with JS from wasm with types like +strings, JS objects, etc. + +[host]: https://github.com/WebAssembly/host-bindings [![Build Status](https://travis-ci.org/alexcrichton/wasm-bindgen.svg?branch=master)](https://travis-ci.org/alexcrichton/wasm-bindgen) [![Build status](https://ci.appveyor.com/api/projects/status/559c0lj5oh271u4c?svg=true)](https://ci.appveyor.com/project/alexcrichton/wasm-bindgen) -This project is intended to be a framework for interoperating between JS and -Rust. Currently it's very Rust-focused but it's hoped that one day the -`wasm-bindgen-cli` tool will not be so Rust-specific and would be amenable to -bindgen for C/C++ modules. +This project is a "temporary" polyfill for the [host bindings proposal][host] +which is intended to empower wasm modules to interact with host objects such as +strings, JS objects, etc. This project enables defining JS classes in wasm, +taking strings from JS in wasm, and calling into JS with JS objects previously +provided. + +Currently this tool is Rust-focused but the underlying foundation is +language-independent, and it's hoping that over time as this tool stabilizes +that it can be used for languages like C/C++! Notable features of this project includes: @@ -104,30 +112,49 @@ set of JS bindings as well. Let's invoke it! ``` $ wasm-bindgen target/wasm32-unknown-unknown/release/js_hello_world.wasm \ - --output-ts hello.ts \ - --output-wasm hello.wasm + --out-dir . ``` -This'll create a `hello.ts` (a TypeScript file) which binds the functions -described in `js_hello_world.wasm`, and the `hello.wasm` will be a little -smaller than the input `js_hello_world.wasm`, but it's otherwise equivalent. -Note that `hello.ts` isn't very pretty so to read it you'll probably want to run -it through a formatter. +This is the main point where the magic happens. The `js_hello_world.wasm` file +emitted by rustc contains *descriptors* of how to communicate via richer types +than wasm currently supports. The `wasm-bindgen` tool will interpret this +information, emitting a **replacement module** for the wasm file. -Typically you'll be feeding this typescript into a larger build system, and -often you'll be using this with your own typescript project as well. For now -though we'll just want the JS output, so let's convert it real quick: +The previous `js_hello_world.wasm` file is interpreted as if it were an ES6 +module. The `js_hello_world.js` file emitted by `wasm-bindgen` should have the +intended interface of the wasm file, notably with rich types like strings, +classes, etc. -``` -$ npm install typescript @types/webassembly-js-api @types/text-encoding -$ ./node_modules/typescript/bin/tsc hello.ts --lib es6 -m es2015 +The `wasm-bindgen` tool also emits a secondary file, `js_hello_world_wasm.wasm`. +This is the original wasm file but postprocessed a bit. It's intended that the +`js_hello_world_wasm.wasm` file, like before, acts like an ES6 module. The +`js_hello_world.wasm` file, for example, uses `import` to import functionality +from the wasm. + +Note that you can also pass a `--nodejs` argument to `wasm-bindgen` for emitting +Node-compatible JS as well as a `--typescript` argument to emit a `*.d.ts` file +describing the exported contents. + +At this point you'll typically plug these files into a larger build system. Both +files emitted by `wasm-bindgen` act like normal ES6 modules (one just happens to +be wasm). As of the time of this writing there's unfortunately not a lot of +tools that natively do this (but they're coming!). In the meantime we can use +the `wasm2es6js` utility (aka "hack") from the `wasm-bindgen` tool we previously +installed along with the `parcel-bundler` packager. Note that these steps will +differ depending on your build system. + +Alright first create an `index.js` file: + +```js +import { greet } from "./js_hello_world"; +import { booted } from "./js_hello_world_wasm"; + +booted.then(() => { + alert(greet("World!")) +}); ``` -Below we'll be using ES6 modules, but your browser may not support them natively -just yet. To see more information about this, you can browse -[online](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import). - -Ok let's see what this look like on the web! +Then a corresponding `index.html`: ```html @@ -135,27 +162,22 @@ Ok let's see what this look like on the web! - + ``` +And run a local server with these files: + +``` +# Convert `*.wasm` to `*.js` where the JS internally instantiates the wasm +$ wasm2es6js js_hello_world_wasm.wasm -o js_hello_world_wasm.js --base64 + +# Install parcel and run it against the index files we use below. +$ npm install -g parcel-bundler +$ parcel index.html -p 8000 +``` + If you open that in a browser you should see a `Hello, world!` dialog pop up! ## What just happened? @@ -192,14 +214,9 @@ file that we then imported. The JS file wraps instantiating the underlying wasm module (aka calling `WebAssembly.instantiate`) and then provides wrappers for classes/functions within. -Eventually `wasm-bindgen` will also take a list of imports where you can call -from Rust to JS without worrying about argument conversions and such. An example -to come here soon! - ## What else can we do? -Turns out much more! Here's a taste of various features you can use in this -project: +Much more! Here's a taste of various features you can use in this project: ```rust // src/lib.rs @@ -250,6 +267,7 @@ wasm_bindgen! { opaque: JsObject, // defined in `wasm_bindgen`, imported via prelude } + #[wasm_module = "./index"] // what ES6 module to import this functionality from extern "JS" { fn bar_on_reset(to: &str, opaque: &JsObject); } @@ -272,61 +290,53 @@ wasm_bindgen! { The generated JS bindings for this invocation of the macro [look like this][bindings]. You can view them in action like so: -[bindings]: https://gist.github.com/b7dfa241208ee858d5473c406225080f +[bindings]: https://gist.github.com/alexcrichton/12ccab3a18d7db0e0d7d777a0f4951b5 + +and our corresponding `index.js`: ```html - - - - - - - - + // We also don't have to `free` the `bar` variable as this function is + // transferring ownership to `foo1` + bar.reset('34'); + foo1.consume_other(bar); + + assertEq(foo1.add(2), 22 + 34 + 2); + foo1.free(); + + alert('all passed!') +} + +booted.then(main); ``` ## Feature reference diff --git a/crates/test-support/src/lib.rs b/crates/test-support/src/lib.rs index 153eb4fb..442a9143 100644 --- a/crates/test-support/src/lib.rs +++ b/crates/test-support/src/lib.rs @@ -1,7 +1,7 @@ extern crate wasm_bindgen_cli_support as cli; use std::env; -use std::fs; +use std::fs::{self, File}; use std::io::{Write, Read}; use std::path::{PathBuf, Path}; use std::process::Command; @@ -15,7 +15,6 @@ thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst)); pub struct Project { files: Vec<(String, String)>, debug: bool, - uglify: bool, js: bool, } @@ -29,7 +28,6 @@ pub fn project() -> Project { .read_to_string(&mut lockfile).unwrap(); Project { debug: true, - uglify: false, js: false, files: vec![ ("Cargo.toml".to_string(), format!(r#" @@ -54,23 +52,53 @@ pub fn project() -> Project { ("Cargo.lock".to_string(), lockfile), ("run.ts".to_string(), r#" - import * as fs from "fs"; import * as process from "process"; - import { instantiate } from "./out"; + import * as out from "./out_wasm"; import * as test from "./test"; - var wasm = fs.readFileSync("out.wasm"); - - instantiate(wasm, test.imports).then(m => { - test.test(m); - if ((m as any).assertHeapAndStackEmpty) - (m as any).assertHeapAndStackEmpty(); + out.booted.then(() => { + test.test(); + if ((out as any).assertHeapAndStackEmpty) + (out as any).assertHeapAndStackEmpty(); }).catch(error => { console.error(error); process.exit(1); }); "#.to_string()), + + ("rollup.config.js".to_string(), r#" + import typescript from 'rollup-plugin-typescript2'; + + export default { + input: './run.ts', + + plugins: [ + typescript() + ], + output: { + file: 'bundle.js', + format: 'cjs' + } + } + "#.to_string()), + + ("tsconfig.json".to_string(), r#" + { + "compilerOptions": { + "noEmitOnError": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noUnusedParameters": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "alwaysStrict": true, + "strict": true + } + } + "#.to_string()), ], } } @@ -87,7 +115,7 @@ pub fn root() -> PathBuf { return me } -fn typescript() -> PathBuf { +fn rollup() -> PathBuf { static INIT: Once = ONCE_INIT; let mut me = env::current_exe().unwrap(); @@ -95,7 +123,7 @@ fn typescript() -> PathBuf { me.pop(); // chop off `deps` me.pop(); // chop off `debug` / `release` let install_dir = me.clone(); - me.push("node_modules/typescript/bin/tsc"); + me.push("node_modules/.bin/rollup"); INIT.call_once(|| { if !me.exists() { @@ -108,9 +136,11 @@ fn typescript() -> PathBuf { }; run(npm .arg("install") + .arg("rollup") + .arg("rollup-plugin-typescript2") .arg("typescript") .arg("@types/node") - .arg("@types/webassembly-js-api") + //.arg("@types/webassembly-js-api") .current_dir(&install_dir), "npm"); assert!(me.exists()); } @@ -130,11 +160,6 @@ impl Project { self } - pub fn uglify(&mut self, uglify: bool) -> &mut Project { - self.uglify = uglify; - self - } - pub fn js(&mut self, js: bool) -> &mut Project { self.js = js; self @@ -170,48 +195,37 @@ impl Project { run(&mut cmd, "wasm-gc"); } - let obj = cli::Bindgen::new() - .input_path(&out) + let as_a_module = root.join("out.wasm"); + fs::copy(&out, &as_a_module).unwrap(); + + cli::Bindgen::new() + .input_path(&as_a_module) .nodejs(true) + .typescript(true) .debug(self.debug) - .uglify_wasm_names(self.uglify) - .generate() + .generate(&root) .expect("failed to run bindgen"); - if self.js { - obj.write_js_to(root.join("out.js")).expect("failed to write js"); - } else { - obj.write_ts_to(root.join("out.ts")).expect("failed to write ts"); - } - obj.write_wasm_to(root.join("out.wasm")).expect("failed to write wasm"); - let out_dir = if self.js { - root.join("out") - } else { - root.clone() - }; + + let mut wasm = Vec::new(); + File::open(root.join("out_wasm.wasm")).unwrap() + .read_to_end(&mut wasm).unwrap(); + let obj = cli::wasm2es6js::Config::new() + .base64(true) + .generate(&wasm) + .expect("failed to convert wasm to js"); + File::create(root.join("out_wasm.d.ts")).unwrap() + .write_all(obj.typescript().as_bytes()).unwrap(); + File::create(root.join("out_wasm.js")).unwrap() + .write_all(obj.js().as_bytes()).unwrap(); let mut cmd = Command::new("node"); - cmd.arg(typescript()) - .current_dir(&target_dir) - .arg(root.join("run.ts")) - .arg("--noUnusedLocals") - .arg("--noUnusedParameters") - .arg("--noImplicitReturns") - .arg("--lib") - .arg("es6") - .arg("--outDir").arg(&out_dir); - if self.js { - cmd.arg("--allowJs"); - } else { - cmd.arg("--noImplicitAny") - .arg("--strict") - .arg("--strictNullChecks") - .arg("--declaration") - .arg("--strictFunctionTypes"); - } + cmd.arg(rollup()) + .current_dir(&root) + .arg("-c"); run(&mut cmd, "node"); let mut cmd = Command::new("node"); - cmd.arg(out_dir.join("run.js")) + cmd.arg(root.join("bundle.js")) .current_dir(&root); run(&mut cmd, "node"); } diff --git a/crates/wasm-bindgen-cli-support/Cargo.toml b/crates/wasm-bindgen-cli-support/Cargo.toml index 77f6d045..e4a60230 100644 --- a/crates/wasm-bindgen-cli-support/Cargo.toml +++ b/crates/wasm-bindgen-cli-support/Cargo.toml @@ -8,3 +8,4 @@ parity-wasm = "0.23" failure = "0.1" wasm-bindgen-shared = { path = "../wasm-bindgen-shared" } serde_json = "1.0" +base64 = "0.9" diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs new file mode 100644 index 00000000..19673459 --- /dev/null +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -0,0 +1,846 @@ +use std::collections::HashSet; + +use shared; +use parity_wasm::elements::*; + +use super::Bindgen; + +pub struct Js<'a> { + pub globals: String, + pub imports: String, + pub typescript: String, + pub exposed_globals: HashSet<&'static str>, + pub config: &'a Bindgen, + pub module: &'a mut Module, + pub program: &'a shared::Program, +} + +impl<'a> Js<'a> { + pub fn generate(&mut self, module_name: &str) -> (String, String) { + for f in self.program.free_functions.iter() { + self.generate_free_function(f); + } + for f in self.program.imports.iter() { + self.generate_import(&f.0, &f.1); + } + for s in self.program.structs.iter() { + self.generate_struct(s); + } + + { + let mut bind = |name: &str, f: &Fn(&mut Self) -> String| { + if !self.wasm_import_needed(name) { + return + } + let global = format!("export const {} = {};", name, f(self)); + self.globals.push_str(&global); + }; + + bind("__wbindgen_object_clone_ref", &|me| { + me.expose_add_heap_object(); + me.expose_get_object(); + let bump_cnt = if me.config.debug { + String::from(" + if (typeof(val) === 'number') + throw new Error('corrupt slab'); + val.cnt += 1; + ") + } else { + String::from("val.cnt += 1;") + }; + format!(" + function(idx) {{ + // If this object is on the stack promote it to the heap. + if ((idx & 1) === 1) + return addHeapObject(getObject(idx)); + + // Otherwise if the object is on the heap just bump the + // refcount and move on + const val = slab[idx >> 1]; + {} + return idx; + }} + ", bump_cnt) + }); + + bind("__wbindgen_object_drop_ref", &|me| { + me.expose_drop_ref(); + "dropRef".to_string() + }); + + bind("__wbindgen_string_new", &|me| { + me.expose_add_heap_object(); + me.expose_get_string_from_wasm(); + String::from("(p, l) => addHeapObject(getStringFromWasm(p, l))") + }); + + bind("__wbindgen_number_new", &|me| { + me.expose_add_heap_object(); + String::from("addHeapObject") + }); + + bind("__wbindgen_number_get", &|me| { + me.expose_get_object(); + format!(" + function(n, invalid) {{ + let obj = getObject(n); + if (typeof(obj) === 'number') + return obj; + (new Uint8Array(wasm.memory.buffer))[invalid] = 1; + return 0; + }} + ") + }); + + bind("__wbindgen_undefined_new", &|me| { + me.expose_add_heap_object(); + String::from("() => addHeapObject(undefined)") + }); + + bind("__wbindgen_null_new", &|me| { + me.expose_add_heap_object(); + String::from("() => addHeapObject(null)") + }); + + bind("__wbindgen_is_null", &|me| { + me.expose_get_object(); + String::from("(idx) => getObject(idx) === null ? 1 : 0") + }); + + bind("__wbindgen_is_undefined", &|me| { + me.expose_get_object(); + String::from("(idx) => getObject(idx) === undefined ? 1 : 0") + }); + + bind("__wbindgen_boolean_new", &|me| { + me.expose_add_heap_object(); + String::from("(v) => addHeapObject(v == 1)") + }); + + bind("__wbindgen_boolean_get", &|me| { + me.expose_get_object(); + String::from("(i) => { + let v = getObject(i); + if (typeof(v) == 'boolean') { + return v ? 1 : 0; + } else { + return 2; + } + }") + }); + + bind("__wbindgen_symbol_new", &|me| { + me.expose_get_string_from_wasm(); + me.expose_add_heap_object(); + format!("(ptr, len) => {{ + let a; + console.log(ptr, len); + if (ptr === 0) {{ + a = Symbol(); + }} else {{ + a = Symbol(getStringFromWasm(ptr, len)); + }} + return addHeapObject(a); + }}") + }); + + bind("__wbindgen_is_symbol", &|me| { + me.expose_get_object(); + String::from("(i) => typeof(getObject(i)) == 'symbol' ? 1 : 0") + }); + + bind("__wbindgen_throw", &|me| { + me.expose_get_string_from_wasm(); + format!(" + function(ptr, len) {{ + throw new Error(getStringFromWasm(ptr, len)); + }} + ") + }); + + bind("__wbindgen_string_get", &|me| { + me.expose_pass_string_to_wasm(); + me.expose_get_object(); + String::from("(i, len_ptr) => { + let obj = getObject(i); + if (typeof(obj) !== 'string') + return 0; + const [ptr, len] = passStringToWasm(obj); + (new Uint32Array(wasm.memory.buffer))[len_ptr / 4] = len; + return ptr; + }") + }); + } + + let js = format!(" + /* tslint:disable */ + import * as wasm from './{module_name}_wasm'; // imports from wasm file + {imports} + + {globals} + ", + module_name = module_name, + globals = self.globals, + imports = self.imports, + ); + + self.rewrite_imports(module_name); + + (js, self.typescript.clone()) + } + + pub fn generate_free_function(&mut self, func: &shared::Function) { + let (js, ts) = self.generate_function("function", + &func.name, + &func.name, + false, + &func.arguments, + func.ret.as_ref()); + self.globals.push_str("export "); + self.globals.push_str(&js); + self.globals.push_str("\n"); + self.typescript.push_str("export "); + self.typescript.push_str(&ts); + self.typescript.push_str("\n"); + } + + pub fn generate_struct(&mut self, s: &shared::Struct) { + let mut dst = String::new(); + dst.push_str(&format!("export class {} {{", s.name)); + let mut ts_dst = dst.clone(); + ts_dst.push_str(" + public ptr: number; + "); + if self.config.debug { + self.expose_check_token(); + dst.push_str(&format!(" + constructor(ptr, sym) {{ + _checkToken(sym); + this.ptr = ptr; + }} + ")); + ts_dst.push_str("constructor(ptr: number, sym: Symbol);\n"); + } else { + dst.push_str(&format!(" + constructor(ptr) {{ + this.ptr = ptr; + }} + ")); + ts_dst.push_str("constructor(ptr: number);\n"); + } + + dst.push_str(&format!(" + free() {{ + const ptr = this.ptr; + this.ptr = 0; + wasm.{}(ptr); + }} + ", s.free_function())); + ts_dst.push_str("free();\n"); + + for function in s.functions.iter() { + let (js, ts) = self.generate_function( + "static", + &function.name, + &function.struct_function_export_name(&s.name), + false, + &function.arguments, + function.ret.as_ref(), + ); + dst.push_str(&js); + dst.push_str("\n"); + ts_dst.push_str(&ts); + ts_dst.push_str("\n"); + } + for method in s.methods.iter() { + let (js, ts) = self.generate_function( + "", + &method.function.name, + &method.function.struct_function_export_name(&s.name), + true, + &method.function.arguments, + method.function.ret.as_ref(), + ); + dst.push_str(&js); + dst.push_str("\n"); + ts_dst.push_str(&ts); + ts_dst.push_str("\n"); + } + dst.push_str("}\n"); + ts_dst.push_str("}\n"); + + self.globals.push_str(&dst); + self.globals.push_str("\n"); + self.typescript.push_str(&ts_dst); + self.typescript.push_str("\n"); + } + + fn generate_function(&mut self, + prefix: &str, + name: &str, + wasm_name: &str, + is_method: bool, + arguments: &[shared::Type], + ret: Option<&shared::Type>) -> (String, String) { + let mut dst = format!("{}(", name); + let mut dst_ts = format!("{}(", name); + let mut passed_args = String::new(); + let mut arg_conversions = String::new(); + let mut destructors = String::new(); + + if is_method { + passed_args.push_str("this.ptr"); + } + + for (i, arg) in arguments.iter().enumerate() { + let name = format!("arg{}", i); + if i > 0 { + dst.push_str(", "); + dst_ts.push_str(", "); + } + dst.push_str(&name); + dst_ts.push_str(&name); + + let mut pass = |arg: &str| { + if passed_args.len() > 0 { + passed_args.push_str(", "); + } + passed_args.push_str(arg); + }; + match *arg { + shared::Type::Number => { + dst_ts.push_str(": number"); + if self.config.debug { + self.expose_assert_num(); + arg_conversions.push_str(&format!("_assertNum({});\n", name)); + } + pass(&name) + } + shared::Type::Boolean => { + dst_ts.push_str(": boolean"); + if self.config.debug { + self.expose_assert_bool(); + arg_conversions.push_str(&format!("\ + _assertBoolean({name}); + ", name = name)); + } else { + } + pass(&format!("arg{i} ? 1 : 0", i = i)) + } + shared::Type::BorrowedStr | + shared::Type::String => { + dst_ts.push_str(": string"); + self.expose_pass_string_to_wasm(); + arg_conversions.push_str(&format!("\ + const [ptr{i}, len{i}] = passStringToWasm({arg}); + ", i = i, arg = name)); + pass(&format!("ptr{}", i)); + pass(&format!("len{}", i)); + if let shared::Type::BorrowedStr = *arg { + destructors.push_str(&format!("\n\ + wasm.__wbindgen_free(ptr{i}, len{i});\n\ + ", i = i)); + } + } + shared::Type::ByRef(ref s) | + shared::Type::ByMutRef(ref s) => { + dst_ts.push_str(&format!(": {}", s)); + if self.config.debug { + self.expose_assert_class(); + arg_conversions.push_str(&format!("\ + _assertClass({arg}, {struct_}); + ", arg = name, struct_ = s)); + } + pass(&format!("{}.ptr", name)); + } + shared::Type::ByValue(ref s) => { + dst_ts.push_str(&format!(": {}", s)); + if self.config.debug { + self.expose_assert_class(); + arg_conversions.push_str(&format!("\ + _assertClass({arg}, {struct_}); + ", arg = name, struct_ = s)); + } + arg_conversions.push_str(&format!("\ + const ptr{i} = {arg}.ptr; + {arg}.ptr = 0; + ", i = i, arg = name)); + pass(&format!("ptr{}", i)); + } + shared::Type::JsObject => { + dst_ts.push_str(": any"); + self.expose_add_heap_object(); + arg_conversions.push_str(&format!("\ + const idx{i} = addHeapObject({arg}); + ", i = i, arg = name)); + pass(&format!("idx{}", i)); + } + shared::Type::JsObjectRef => { + dst_ts.push_str(": any"); + self.expose_borrowed_objects(); + arg_conversions.push_str(&format!("\ + const idx{i} = addBorrowedObject({arg}); + ", i = i, arg = name)); + destructors.push_str("stack.pop();\n"); + pass(&format!("idx{}", i)); + } + } + } + dst.push_str(")"); + dst_ts.push_str(")"); + let convert_ret = match ret { + None => { + dst_ts.push_str(": void"); + format!("return ret;") + } + Some(&shared::Type::Number) => { + dst_ts.push_str(": number"); + format!("return ret;") + } + Some(&shared::Type::Boolean) => { + dst_ts.push_str(": boolean"); + format!("return ret != 0;") + } + Some(&shared::Type::JsObject) => { + dst_ts.push_str(": any"); + self.expose_take_object(); + format!("return takeObject(ret);") + } + Some(&shared::Type::JsObjectRef) | + Some(&shared::Type::BorrowedStr) | + Some(&shared::Type::ByMutRef(_)) | + Some(&shared::Type::ByRef(_)) => panic!(), + Some(&shared::Type::ByValue(ref name)) => { + dst_ts.push_str(": "); + dst_ts.push_str(name); + if self.config.debug { + format!("\ + return new {name}(ret, token); + ", name = name) + } else { + format!("\ + return new {name}(ret); + ", name = name) + } + } + Some(&shared::Type::String) => { + dst_ts.push_str(": string"); + self.expose_get_string_from_wasm(); + format!(" + const ptr = wasm.__wbindgen_boxed_str_ptr(ret); + const len = wasm.__wbindgen_boxed_str_len(ret); + const realRet = getStringFromWasm(ptr, len); + wasm.__wbindgen_boxed_str_free(ret); + return realRet; + ") + } + }; + dst_ts.push_str(";"); + dst.push_str(" {\n "); + dst.push_str(&arg_conversions); + if destructors.len() == 0 { + dst.push_str(&format!("\ + const ret = wasm.{}({passed}); + {convert_ret} + ", + f = wasm_name, + passed = passed_args, + convert_ret = convert_ret, + )); + } else { + dst.push_str(&format!("\ + try {{ + const ret = wasm.{f}({passed}); + {convert_ret} + }} finally {{ + {destructors} + }} + ", + f = wasm_name, + passed = passed_args, + destructors = destructors, + convert_ret = convert_ret, + )); + } + dst.push_str("}"); + (format!("{} {}", prefix, dst), format!("{} {}", prefix, dst_ts)) + } + + pub fn generate_import(&mut self, module: &str, import: &shared::Function) { + let mut dst = String::new(); + + let imported_name = format!("import{}", self.imports.len()); + + self.imports.push_str(&format!(" + import {{ {} as {} }} from '{}'; + ", import.name, imported_name, module)); + + dst.push_str(&format!("function __wbg_import_{}(", import.name)); + + let mut invocation = String::new(); + for (i, arg) in import.arguments.iter().enumerate() { + if invocation.len() > 0 { + invocation.push_str(", "); + } + if i > 0 { + dst.push_str(", "); + } + match *arg { + shared::Type::Number => { + invocation.push_str(&format!("arg{}", i)); + dst.push_str(&format!("arg{}", i)); + } + shared::Type::Boolean => { + invocation.push_str(&format!("arg{} != 0", i)); + dst.push_str(&format!("arg{}", i)); + } + shared::Type::BorrowedStr => { + self.expose_get_string_from_wasm(); + invocation.push_str(&format!("getStringFromWasm(ptr{0}, len{0})", i)); + dst.push_str(&format!("ptr{0}, len{0}", i)); + } + shared::Type::JsObject => { + self.expose_take_object(); + invocation.push_str(&format!("takeObject(arg{})", i)); + dst.push_str(&format!("arg{}", i)); + } + shared::Type::JsObjectRef => { + self.expose_get_object(); + invocation.push_str(&format!("getObject(arg{})", i)); + dst.push_str(&format!("arg{}", i)); + } + shared::Type::String | + shared::Type::ByRef(_) | + shared::Type::ByMutRef(_) | + shared::Type::ByValue(_) => { + panic!("unsupported type in import"); + } + } + } + dst.push_str(")"); + let invoc = format!("{}({})", imported_name, invocation); + let invoc = match import.ret { + Some(shared::Type::Number) => invoc, + Some(shared::Type::Boolean) => format!("{} ? 1 : 0", invoc), + Some(shared::Type::JsObject) => { + self.expose_add_heap_object(); + format!("addHeapObject({})", invoc) + } + None => invoc, + _ => unimplemented!(), + }; + dst.push_str(" {\n"); + dst.push_str(&format!("return {};\n}}", invoc)); + + self.globals.push_str("export "); + self.globals.push_str(&dst); + self.globals.push_str("\n"); + } + + fn wasm_import_needed(&self, name: &str) -> bool { + let imports = match self.module.import_section() { + Some(s) => s, + None => return false, + }; + + imports.entries().iter().any(|i| { + i.module() == "env" && i.field() == name + }) + } + + fn rewrite_imports(&mut self, module_name: &str) { + for section in self.module.sections_mut() { + let imports = match *section { + Section::Import(ref mut s) => s, + _ => continue, + }; + for import in imports.entries_mut() { + if import.module() != "env" { + continue + } + if import.field().starts_with("__wbindgen") { + import.module_mut().truncate(0); + import.module_mut().push_str("./"); + import.module_mut().push_str(module_name); + continue + } + + // rustc doesn't have support for importing from anything other + // than the module `env` so let's use the metadata here to + // rewrite the imports if they import from `env` until it's + // fixed upstream. + let program_import = self.program.imports + .iter() + .find(|&&(_, ref f)| f.name == import.field()); + if program_import.is_some() { + import.module_mut().truncate(0); + import.module_mut().push_str("./"); + import.module_mut().push_str(module_name); + import.field_mut().insert_str(0, "__wbg_import_"); + } + } + } + } + + fn expose_drop_ref(&mut self) { + if !self.exposed_globals.insert("drop_ref") { + return + } + self.expose_global_slab(); + self.expose_global_slab_next(); + let validate_owned = if self.config.debug { + String::from(" + if ((idx & 1) === 1) + throw new Error('cannot drop ref of stack objects'); + ") + } else { + String::new() + }; + let dec_ref = if self.config.debug { + String::from(" + if (typeof(obj) === 'number') + throw new Error('corrupt slab'); + obj.cnt -= 1; + if (obj.cnt > 0) + return; + ") + } else { + String::from(" + obj.cnt -= 1; + if (obj.cnt > 0) + return; + ") + }; + self.globals.push_str(&format!(" + function dropRef(idx) {{ + {} + + let obj = slab[idx >> 1]; + {} + + // If we hit 0 then free up our space in the slab + slab[idx >> 1] = slab_next; + slab_next = idx >> 1; + }} + ", validate_owned, dec_ref)); + } + + fn expose_global_stack(&mut self) { + if !self.exposed_globals.insert("stack") { + return + } + self.globals.push_str(&format!(" + let stack = []; + ")); + } + + fn expose_global_slab(&mut self) { + if !self.exposed_globals.insert("slab") { + return + } + self.globals.push_str(&format!("let slab = [];")); + } + + fn expose_global_slab_next(&mut self) { + if !self.exposed_globals.insert("slab_next") { + return + } + self.globals.push_str(&format!(" + let slab_next = 0; + ")); + } + + fn expose_get_object(&mut self) { + if !self.exposed_globals.insert("get_object") { + return + } + self.expose_global_stack(); + self.expose_global_slab(); + + let get_obj = if self.config.debug { + String::from(" + if (typeof(val) === 'number') + throw new Error('corrupt slab'); + return val.obj; + ") + } else { + String::from(" + return val.obj; + ") + }; + self.globals.push_str(&format!(" + function getObject(idx) {{ + if ((idx & 1) === 1) {{ + return stack[idx >> 1]; + }} else {{ + const val = slab[idx >> 1]; + {} + }} + }} + ", get_obj)); + } + + fn expose_check_token(&mut self) { + if !self.exposed_globals.insert("check_token") { + return + } + self.globals.push_str(&format!(" + const token = Symbol('foo'); + function _checkToken(sym) {{ + if (token !== sym) + throw new Error('cannot invoke `new` directly'); + }} + ")); + } + + fn expose_assert_num(&mut self) { + if !self.exposed_globals.insert("assert_num") { + return + } + self.globals.push_str(&format!(" + function _assertNum(n) {{ + if (typeof(n) !== 'number') + throw new Error('expected a number argument'); + }} + ")); + } + + fn expose_assert_bool(&mut self) { + if !self.exposed_globals.insert("assert_bool") { + return + } + self.globals.push_str(&format!(" + function _assertBoolean(n) {{ + if (typeof(n) !== 'boolean') + throw new Error('expected a boolean argument'); + }} + ")); + } + + fn expose_pass_string_to_wasm(&mut self) { + if !self.exposed_globals.insert("pass_string_to_wasm") { + return + } + if self.config.nodejs { + self.globals.push_str(&format!(" + function passStringToWasm(arg) {{ + if (typeof(arg) !== 'string') + throw new Error('expected a string argument'); + const buf = Buffer.from(arg); + const len = buf.length; + const ptr = wasm.__wbindgen_malloc(len); + buf.copy(Buffer.from(wasm.memory.buffer), ptr); + return [ptr, len]; + }} + ")); + } else { + self.globals.push_str(&format!(" + function passStringToWasm(arg) {{ + if (typeof(arg) !== 'string') + throw new Error('expected a string argument'); + const buf = new TextEncoder('utf-8').encode(arg); + const len = buf.length; + const ptr = wasm.__wbindgen_malloc(len); + let array = new Uint8Array(wasm.memory.buffer); + array.set(buf, ptr); + return [ptr, len]; + }} + ")); + } + } + + fn expose_get_string_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_string_from_wasm") { + return + } + if self.config.nodejs { + self.globals.push_str(&format!(" + function getStringFromWasm(ptr, len) {{ + const buf = Buffer.from(wasm.memory.buffer).slice(ptr, ptr + len); + const ret = buf.toString(); + return ret; + }} + ")); + } else { + self.globals.push_str(&format!(" + function getStringFromWasm(ptr, len) {{ + const mem = new Uint8Array(wasm.memory.buffer); + const slice = mem.slice(ptr, ptr + len); + const ret = new TextDecoder('utf-8').decode(slice); + return ret; + }} + ")); + } + } + + fn expose_assert_class(&mut self) { + if !self.exposed_globals.insert("assert_class") { + return + } + self.globals.push_str(&format!(" + function _assertClass(instance, klass) {{ + if (!(instance instanceof klass)) + throw new Error(`expected instance of ${{klass.name}}`); + return instance.ptr; + }} + ")); + } + + fn expose_borrowed_objects(&mut self) { + if !self.exposed_globals.insert("borrowed_objects") { + return + } + self.expose_global_stack(); + self.globals.push_str(&format!(" + function addBorrowedObject(obj) {{ + stack.push(obj); + return ((stack.length - 1) << 1) | 1; + }} + ")); + } + + fn expose_take_object(&mut self) { + if !self.exposed_globals.insert("take_object") { + return + } + self.expose_get_object(); + self.expose_drop_ref(); + self.globals.push_str(&format!(" + function takeObject(idx) {{ + const ret = getObject(idx); + dropRef(idx); + return ret; + }} + ")); + } + + fn expose_add_heap_object(&mut self) { + if !self.exposed_globals.insert("add_heap_object") { + return + } + self.expose_global_slab(); + self.expose_global_slab_next(); + let set_slab_next = if self.config.debug { + String::from(" + if (typeof(next) !== 'number') + throw new Error('corrupt slab'); + slab_next = next; + ") + } else { + String::from(" + slab_next = next; + ") + }; + self.globals.push_str(&format!(" + function addHeapObject(obj) {{ + if (slab_next == slab.length) + slab.push(slab.length + 1); + const idx = slab_next; + const next = slab[idx]; + {} + slab[idx] = {{ obj, cnt: 1 }}; + return idx << 1; + }} + ", set_slab_next)); + } +} diff --git a/crates/wasm-bindgen-cli-support/src/lib.rs b/crates/wasm-bindgen-cli-support/src/lib.rs index 7a39889a..aa851788 100644 --- a/crates/wasm-bindgen-cli-support/src/lib.rs +++ b/crates/wasm-bindgen-cli-support/src/lib.rs @@ -4,31 +4,21 @@ extern crate parity_wasm; extern crate wasm_bindgen_shared as shared; extern crate serde_json; -use std::collections::HashMap; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; -use failure::{Error, ResultExt}; +use failure::Error; use parity_wasm::elements::*; -mod ts; -mod mapped; - -use mapped::Mapped; +mod js; +pub mod wasm2es6js; pub struct Bindgen { path: Option, nodejs: bool, debug: bool, - uglify: bool, -} - -pub struct Object { - module: Mapped, - program: shared::Program, - nodejs: bool, - debug: bool, + typescript: bool, } impl Bindgen { @@ -37,7 +27,7 @@ impl Bindgen { path: None, nodejs: false, debug: false, - uglify: false, + typescript: false, } } @@ -56,96 +46,52 @@ impl Bindgen { self } - pub fn uglify_wasm_names(&mut self, uglify: bool) -> &mut Bindgen { - self.uglify = uglify; + pub fn typescript(&mut self, typescript: bool) -> &mut Bindgen { + self.typescript = typescript; self } - pub fn generate(&mut self) -> Result { + pub fn generate>(&mut self, path: P) -> Result<(), Error> { + self._generate(path.as_ref()) + } + + fn _generate(&mut self, out_dir: &Path) -> Result<(), Error> { let input = match self.path { Some(ref path) => path, None => panic!("must have a path input for now"), }; + let stem = input.file_stem().unwrap().to_str().unwrap(); let mut module = parity_wasm::deserialize_file(input).map_err(|e| { format_err!("{:?}", e) })?; let program = extract_program(&mut module); - let mut mapped = Mapped { - module, - imports: HashMap::new(), - exports: HashMap::new(), - }; - if self.uglify { - mapped.uglify(&program); + + let (js, ts) = js::Js { + globals: String::new(), + imports: String::new(), + typescript: String::new(), + exposed_globals: Default::default(), + config: &self, + module: &mut module, + program: &program, + }.generate(stem); + + let js_path = out_dir.join(stem).with_extension("js"); + File::create(&js_path).unwrap() + .write_all(js.as_bytes()).unwrap(); + + if self.typescript { + let ts_path = out_dir.join(stem).with_extension("d.ts"); + File::create(&ts_path).unwrap() + .write_all(ts.as_bytes()).unwrap(); } - Ok(Object { - module: mapped, - program, - nodejs: self.nodejs, - debug: self.debug, - }) - } -} -impl Object { - pub fn write_ts_to>(&self, path: P) -> Result<(), Error> { - self._write_ts_to(path.as_ref()) - } - - fn _write_ts_to(&self, path: &Path) -> Result<(), Error> { - let ts = self.generate_ts(); - let mut f = File::create(path).with_context(|_| { - format!("failed to create file at {:?}", path) - })?; - f.write_all(ts.as_bytes()).with_context(|_| { - format!("failed to write file at {:?}", path) - })?; - Ok(()) - } - - pub fn write_js_to>(&self, path: P) -> Result<(), Error> { - self._write_js_to(path.as_ref()) - } - - fn _write_js_to(&self, path: &Path) -> Result<(), Error> { - let js = self.generate_js(); - let mut f = File::create(path).with_context(|_| { - format!("failed to create file at {:?}", path) - })?; - f.write_all(js.as_bytes()).with_context(|_| { - format!("failed to write file at {:?}", path) - })?; - Ok(()) - } - - pub fn write_wasm_to>(self, path: P) -> Result<(), Error> { - self._write_wasm_to(path.as_ref()) - } - - fn _write_wasm_to(self, path: &Path) -> Result<(), Error> { - parity_wasm::serialize_to_file(path, self.module.module).map_err(|e| { + let wasm_path = out_dir.join(format!("{}_wasm", stem)).with_extension("wasm"); + parity_wasm::serialize_to_file(wasm_path, module).map_err(|e| { format_err!("{:?}", e) })?; Ok(()) } - - pub fn generate_ts(&self) -> String { - let mut ts = ts::Js::default(); - ts.nodejs = self.nodejs; - ts.debug = self.debug; - ts.ts = true; - ts.generate_program(&self.program, &self.module); - ts.to_string(&self.module, &self.program) - } - - pub fn generate_js(&self) -> String { - let mut ts = ts::Js::default(); - ts.nodejs = self.nodejs; - ts.debug = self.debug; - ts.ts = false; - ts.generate_program(&self.program, &self.module); - ts.to_string(&self.module, &self.program) - } } fn extract_program(module: &mut Module) -> shared::Program { diff --git a/crates/wasm-bindgen-cli-support/src/mapped.rs b/crates/wasm-bindgen-cli-support/src/mapped.rs deleted file mode 100644 index c188f4a5..00000000 --- a/crates/wasm-bindgen-cli-support/src/mapped.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::collections::hash_map::{HashMap, Entry}; - -use parity_wasm::elements::*; -use shared; - -pub struct Mapped { - pub module: Module, - pub imports: HashMap, - pub exports: HashMap, -} - -impl Mapped { - pub fn export_name<'a>(&'a self, name: &'a str) -> &'a str { - self.exports.get(name).map(|s| &**s).unwrap_or(name) - } - - pub fn orig_export_name<'a>(&'a self, name: &'a str) -> &'a str { - self.exports.iter() - .find(|&(_k, v)| name == v) - .map(|p| &**p.0) - .unwrap_or(name) - } - - pub fn import_name<'a>(&'a self, name: &'a str) -> &'a str { - self.imports.get(name).map(|s| &**s).unwrap_or(name) - } - - pub fn orig_import_name<'a>(&'a self, name: &'a str) -> &'a str { - self.imports.iter() - .find(|&(_k, v)| name == v) - .map(|p| &**p.0) - .unwrap_or(name) - } - - pub fn uglify(&mut self, program: &shared::Program) { - let mut i = 0; - let mut generate = || { - let ret = generate(i); - i += 1; - return ret - }; - - for import in program.imports.iter() { - self.imports.insert(import.name.clone(), generate()); - } - - for f in program.free_functions.iter() { - self.exports.insert(f.free_function_export_name(), generate()); - } - - for s in program.structs.iter() { - for f in s.functions.iter() { - self.exports.insert(f.struct_function_export_name(&s.name), - generate()); - } - for f in s.methods.iter() { - self.exports.insert(f.function.struct_function_export_name(&s.name), - generate()); - } - } - - for section in self.module.sections_mut() { - match *section { - Section::Import(ref mut section) => { - for import in section.entries_mut() { - let new_name = match self.imports.entry(import.field().to_string()) { - Entry::Occupied(n) => n.into_mut(), - Entry::Vacant(v) => { - if !import.field().starts_with("__wbindgen") { - continue - } - v.insert(generate()) - } - }; - *import = ImportEntry::new( - import.module().to_string(), - new_name.to_string(), - import.external().clone(), - ); - } - } - Section::Export(ref mut e) => { - for e in e.entries_mut() { - let new_name = match self.exports.entry(e.field().to_string()) { - Entry::Occupied(n) => n.into_mut(), - Entry::Vacant(v) => { - if !e.field().starts_with("__wbindgen") { - continue - } - v.insert(generate()) - } - }; - *e = ExportEntry::new(new_name.to_string(), - e.internal().clone()); - } - } - _ => {} - } - } - } -} - -fn generate(mut i: usize) -> String { - const LETTERS: &str = "\ - abcdefghijklmnopqrstuvwxyz\ - ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - "; - let mut ret = String::new(); - loop { - let j = i % LETTERS.len(); - i /= LETTERS.len(); - ret.push(LETTERS.as_bytes()[j] as char); - if i == 0 { - break - } - } - return ret; -} diff --git a/crates/wasm-bindgen-cli-support/src/ts.rs b/crates/wasm-bindgen-cli-support/src/ts.rs deleted file mode 100644 index 49d6cafa..00000000 --- a/crates/wasm-bindgen-cli-support/src/ts.rs +++ /dev/null @@ -1,1266 +0,0 @@ -use std::collections::{HashSet, HashMap}; -use std::fmt; - -use shared; -use parity_wasm::elements::*; - -use super::Mapped; - -#[derive(Default)] -pub struct Js { - globals: String, - exposed_globals: HashSet<&'static str>, - exports: Vec<(String, String, String)>, - wasm_exports_bound: HashSet, - classes: Vec, - pub nodejs: bool, - pub debug: bool, - pub ts: bool, -} - -impl Js { - pub fn generate_program(&mut self, - program: &shared::Program, - m: &Mapped) { - for f in program.free_functions.iter() { - self.generate_free_function(f, m); - } - for s in program.structs.iter() { - self.generate_struct(s, m); - } - } - - pub fn generate_free_function(&mut self, - func: &shared::Function, - m: &Mapped) { - let (js, ts) = self.generate_function("function", - &func.name, - &func.name, - false, - &func.arguments, - func.ret.as_ref(), - m); - - self.exports.push((func.name.clone(), js, ts)); - } - - pub fn generate_struct(&mut self, - s: &shared::Struct, - m: &Mapped) { - let mut dst = String::new(); - self.expose_wasm_exports(); - dst.push_str(&format!(" - export class {} {{ - ", s.name)); - let number = self.typed("number"); - let symbol = self.typed("Symbol"); - let void = self.typed("void"); - if self.ts { - dst.push_str(" - public ptr: number; - "); - } - if self.debug { - self.expose_check_token(); - dst.push_str(&format!(" - constructor(ptr{}, sym{}) {{ - _checkToken(sym); - this.ptr = ptr; - }} - ", number, symbol)); - } else { - dst.push_str(&format!(" - constructor(ptr{}) {{ - this.ptr = ptr; - }} - ", number)); - } - - dst.push_str(&format!(" - free(){} {{ - const ptr = this.ptr; - this.ptr = 0; - wasm_exports.{}(ptr); - }} - ", void, m.export_name(&s.free_function()))); - - self.wasm_exports_bound.insert(s.name.clone()); - - for function in s.functions.iter() { - let (js, _ts) = self.generate_function( - "static", - &function.name, - &function.struct_function_export_name(&s.name), - false, - &function.arguments, - function.ret.as_ref(), - m, - ); - dst.push_str(&js); - dst.push_str("\n"); - } - for method in s.methods.iter() { - let (js, _ts) = self.generate_function( - "", - &method.function.name, - &method.function.struct_function_export_name(&s.name), - true, - &method.function.arguments, - method.function.ret.as_ref(), - m, - ); - dst.push_str(&js); - dst.push_str("\n"); - } - dst.push_str("}\n"); - self.classes.push(dst); - - let ts_export = format!("{0}: typeof {0};", s.name); - self.exports.push((s.name.clone(), s.name.clone(), ts_export)); - } - - fn generate_function(&mut self, - prefix: &str, - name: &str, - wasm_name: &str, - is_method: bool, - arguments: &[shared::Type], - ret: Option<&shared::Type>, - m: &Mapped) -> (String, String) { - let mut dst = format!("{}(", name); - let mut passed_args = String::new(); - let mut arg_conversions = String::new(); - let mut destructors = String::new(); - - if is_method { - passed_args.push_str("this.ptr"); - } - - for (i, arg) in arguments.iter().enumerate() { - let name = format!("arg{}", i); - if i > 0 { - dst.push_str(", "); - } - dst.push_str(&name); - - let mut pass = |arg: &str| { - if passed_args.len() > 0 { - passed_args.push_str(", "); - } - passed_args.push_str(arg); - }; - match *arg { - shared::Type::Number => { - dst.push_str(&format!("{}", self.typed("number"))); - if self.debug { - self.expose_assert_num(); - arg_conversions.push_str(&format!("_assertNum({});\n", name)); - } - pass(&name) - } - shared::Type::Boolean => { - dst.push_str(&format!("{}", self.typed("boolean"))); - if self.debug { - self.expose_assert_bool(); - arg_conversions.push_str(&format!("\ - _assertBoolean({name}); - ", name = name)); - } else { - } - pass(&format!("arg{i} ? 1 : 0", i = i)) - } - shared::Type::BorrowedStr | - shared::Type::String => { - dst.push_str(&format!("{}", self.typed("string"))); - self.expose_pass_string_to_wasm(m); - arg_conversions.push_str(&format!("\ - const [ptr{i}, len{i}] = passStringToWasm({arg}); - ", i = i, arg = name)); - pass(&format!("ptr{}", i)); - pass(&format!("len{}", i)); - if let shared::Type::BorrowedStr = *arg { - self.expose_wasm_exports(); - destructors.push_str(&format!("\n\ - wasm_exports.{free}(ptr{i}, len{i});\n\ - ", i = i, free = m.export_name("__wbindgen_free"))); - } - } - shared::Type::ByRef(ref s) | - shared::Type::ByMutRef(ref s) => { - dst.push_str(&format!("{}", self.typed(s))); - if self.debug { - self.expose_assert_class(); - arg_conversions.push_str(&format!("\ - _assertClass({arg}, {struct_}); - ", arg = name, struct_ = s)); - } - pass(&format!("{}.ptr", name)); - } - shared::Type::ByValue(ref s) => { - dst.push_str(&format!("{}", self.typed(s))); - if self.debug { - self.expose_assert_class(); - arg_conversions.push_str(&format!("\ - _assertClass({arg}, {struct_}); - ", arg = name, struct_ = s)); - } - arg_conversions.push_str(&format!("\ - const ptr{i} = {arg}.ptr; - {arg}.ptr = 0; - ", i = i, arg = name)); - pass(&format!("ptr{}", i)); - } - shared::Type::JsObject => { - dst.push_str(&format!("{}", self.typed("any"))); - self.expose_add_heap_object(); - arg_conversions.push_str(&format!("\ - const idx{i} = addHeapObject({arg}); - ", i = i, arg = name)); - pass(&format!("idx{}", i)); - } - shared::Type::JsObjectRef => { - dst.push_str(&format!("{}", self.typed("any"))); - self.expose_borrowed_objects(); - arg_conversions.push_str(&format!("\ - const idx{i} = addBorrowedObject({arg}); - ", i = i, arg = name)); - destructors.push_str("stack.pop();\n"); - pass(&format!("idx{}", i)); - } - } - } - dst.push_str(")"); - let convert_ret = match ret { - None => { - dst.push_str(&format!("{}", self.typed("void"))); - format!("return ret;") - } - Some(&shared::Type::Number) => { - dst.push_str(&format!("{}", self.typed("number"))); - format!("return ret;") - } - Some(&shared::Type::Boolean) => { - dst.push_str(&format!("{}", self.typed("boolean"))); - format!("return ret != 0;") - } - Some(&shared::Type::JsObject) => { - dst.push_str(&format!("{}", self.typed("any"))); - self.expose_take_object(); - format!("return takeObject(ret);") - } - Some(&shared::Type::JsObjectRef) | - Some(&shared::Type::BorrowedStr) | - Some(&shared::Type::ByMutRef(_)) | - Some(&shared::Type::ByRef(_)) => panic!(), - Some(&shared::Type::ByValue(ref name)) => { - dst.push_str(&format!("{}", self.typed(name))); - if self.debug { - format!("\ - return new {name}(ret, token); - ", name = name) - } else { - format!("\ - return new {name}(ret); - ", name = name) - } - } - Some(&shared::Type::String) => { - dst.push_str(&format!("{}", self.typed("string"))); - self.expose_get_string_from_wasm(); - self.expose_wasm_exports(); - format!(" - const ptr = wasm_exports.{}(ret); - const len = wasm_exports.{}(ret); - const realRet = getStringFromWasm(ptr, len); - wasm_exports.{}(ret); - return realRet; - ", - m.export_name("__wbindgen_boxed_str_ptr"), - m.export_name("__wbindgen_boxed_str_len"), - m.export_name("__wbindgen_boxed_str_free"), - ) - } - }; - let mut dst_ts = dst.clone(); - dst_ts.push_str(";"); - dst.push_str(" {\n "); - dst.push_str(&arg_conversions); - self.expose_wasm_exports(); - if destructors.len() == 0 { - dst.push_str(&format!("\ - const ret = wasm_exports.{f}({passed}); - {convert_ret} - ", - f = m.export_name(wasm_name), - passed = passed_args, - convert_ret = convert_ret, - )); - } else { - dst.push_str(&format!("\ - try {{ - const ret = wasm_exports.{f}({passed}); - {convert_ret} - }} finally {{ - {destructors} - }} - ", - f = m.export_name(wasm_name), - passed = passed_args, - destructors = destructors, - convert_ret = convert_ret, - )); - } - dst.push_str("}"); - self.wasm_exports_bound.insert(wasm_name.to_string()); - (format!("{} {}", prefix, dst), dst_ts) - } - - pub fn generate_import(&mut self, import: &shared::Function) - -> (String, String) - { - let mut dst = String::new(); - let mut ts_dst = String::new(); - let number = self.typed("number"); - - dst.push_str(&format!("function {0}_shim(", import.name)); - - ts_dst.push_str(&import.name); - ts_dst.push_str("("); - - let mut invocation = String::new(); - for (i, arg) in import.arguments.iter().enumerate() { - if invocation.len() > 0 { - invocation.push_str(", "); - } - if i > 0 { - dst.push_str(", "); - ts_dst.push_str(", "); - } - ts_dst.push_str(&format!("arg{}", i)); - match *arg { - shared::Type::Number => { - ts_dst.push_str(&self.typed("number").to_string()); - invocation.push_str(&format!("arg{}", i)); - dst.push_str(&format!("arg{}{}", i, number)); - } - shared::Type::Boolean => { - ts_dst.push_str(&self.typed("boolean").to_string()); - invocation.push_str(&format!("arg{} != 0", i)); - dst.push_str(&format!("arg{}{}", i, number)); - } - shared::Type::BorrowedStr => { - ts_dst.push_str(&self.typed("string").to_string()); - self.expose_get_string_from_wasm(); - invocation.push_str(&format!("getStringFromWasm(ptr{0}, len{0})", i)); - dst.push_str(&format!("ptr{0}{1}, len{0}{1}", i, number)); - } - shared::Type::JsObject => { - ts_dst.push_str(&self.typed("any").to_string()); - self.expose_take_object(); - invocation.push_str(&format!("takeObject(arg{})", i)); - dst.push_str(&format!("arg{}{}", i, number)); - } - shared::Type::JsObjectRef => { - ts_dst.push_str(&self.typed("any").to_string()); - self.expose_get_object(); - invocation.push_str(&format!("getObject(arg{})", i)); - dst.push_str(&format!("arg{}{}", i, number)); - } - shared::Type::String | - shared::Type::ByRef(_) | - shared::Type::ByMutRef(_) | - shared::Type::ByValue(_) => { - panic!("unsupported type in import"); - } - } - } - ts_dst.push_str(")"); - dst.push_str(")"); - let invoc = format!("_imports.{}({})", import.name, invocation); - let invoc = match import.ret { - Some(shared::Type::Number) => { - ts_dst.push_str(&self.typed("number").to_string()); - dst.push_str(&self.typed("number").to_string()); - invoc - } - Some(shared::Type::Boolean) => { - ts_dst.push_str(&self.typed("boolean").to_string()); - dst.push_str(&self.typed("number").to_string()); - format!("{} ? 1 : 0", invoc) - } - Some(shared::Type::JsObject) => { - ts_dst.push_str(&self.typed("any").to_string()); - dst.push_str(&self.typed("number").to_string()); - self.expose_add_heap_object(); - format!("addHeapObject({})", invoc) - } - None => { - ts_dst.push_str(&self.typed("void").to_string()); - dst.push_str(&self.typed("void").to_string()); - invoc - } - _ => unimplemented!(), - }; - ts_dst.push_str("\n"); - dst.push_str(" {\n"); - dst.push_str(&format!("return {};\n}}", invoc)); - - (dst, ts_dst) - } - - pub fn to_string(&mut self, m: &Mapped, program: &shared::Program) -> String { - if self.debug { - self.expose_global_slab(); - self.expose_global_stack(); - let void = self.typed("void"); - self.exports.push( - ( - "assertHeapAndStackEmpty".to_string(), - format!("function(){} {{ - if (stack.length > 0) - throw new Error('stack is not empty'); - for (let i = 0; i < slab.length; i++) {{ - if (typeof(slab[i]) !== 'number') - throw new Error('slab is not empty'); - }} - }}", void), - format!("assertHeapAndStackEmpty(){};\n", void), - ) - ); - } - - for class in self.classes.iter() { - self.globals.push_str(class); - self.globals.push_str("\n"); - } - let wasm_exports = self.typescript_wasm_exports(&m.module); - let mut exports_interface = String::new(); - let mut extra_exports_interface = String::new(); - let mut exports = format!("\ - {{ - module, - instance, - "); - for &(ref name, ref body, ref ts_export) in self.exports.iter() { - exports.push_str(name); - exports.push_str(": "); - exports.push_str(body); - exports.push_str(",\n"); - exports_interface.push_str(ts_export); - exports_interface.push_str("\n"); - } - // If the user otherwise specified functions to export which *weren't* - // part of wasm-bindgen we want to make sure they come through here as - // well. - for (export, typescript) in wasm_exports.iter() { - // ignore any internal functions we have for ourselves - let orig_export = m.orig_export_name(export); - if orig_export.starts_with("__wbindgen") { - continue - } - // Ignore anything we just bound above, - if self.wasm_exports_bound.contains(orig_export) { - continue - } - - assert_eq!(orig_export, export); - if extra_exports_interface.len() == 0 { - extra_exports_interface.push_str("export interface ExtraExports {\n"); - exports_interface.push_str("extra: ExtraExports;\n"); - exports.push_str("extra: {\n"); - } - exports.push_str(export); - exports.push_str(":"); - exports.push_str("exports."); - exports.push_str(export); - exports.push_str(",\n"); - extra_exports_interface.push_str(typescript); - extra_exports_interface.push_str("\n"); - } - if extra_exports_interface.len() > 0 { - extra_exports_interface.push_str("}\n"); - exports.push_str("},\n"); - } - exports.push_str("}"); - let wasm_imports = self.typescript_wasm_imports(&m.module); - - let mut imports_object = String::new(); - let mut extra_imports_interface = String::new(); - let mut imports_bound = HashSet::new(); - let mut imports_interface = String::new(); - for import in program.imports.iter() { - // Only actually generate this import if it ended up being used in - // the wasm module, an optimization pass at some point may have - // ended up removing the code that needed the import, removing the - // import. - let name = m.import_name(&import.name); - if !wasm_imports.contains_key(name) { - continue - } - imports_bound.insert(name.to_string()); - let (val, ts) = self.generate_import(import); - imports_object.push_str(&name); - imports_object.push_str(":"); - imports_object.push_str(&val); - imports_object.push_str(",\n"); - imports_interface.push_str(&ts); - imports_interface.push_str("\n"); - } - - // If the user otherwise specified functions to import which *weren't* - // part of wasm-bindgen we want to make sure they come through here as - // well. - for (import, typescript) in wasm_imports.iter() { - // ignore any internal functions we have for ourselves - let orig_import = m.orig_import_name(import); - if orig_import.starts_with("__wbindgen") { - continue - } - // Ignore anything we just bound above, - if imports_bound.contains(import) { - continue - } - - assert_eq!(orig_import, import); - if extra_imports_interface.len() == 0 { - extra_imports_interface.push_str("export interface ExtraImports {\n"); - imports_interface.push_str("env: ExtraImports;\n"); - } - imports_object.push_str(import); - imports_object.push_str(":"); - imports_object.push_str("_imports.env."); - imports_object.push_str(import); - imports_object.push_str(",\n"); - extra_imports_interface.push_str(typescript); - extra_imports_interface.push_str("\n"); - } - if extra_imports_interface.len() > 0 { - extra_imports_interface.push_str("}\n"); - } - - { - let mut bind = |name: &str, f: &Fn(&mut Self) -> String| { - if !self.wasm_import_needed(name, m) { - return - } - imports_object.push_str(&format!(" - {}: {}, - ", m.import_name(name), f(self))); - }; - - bind("__wbindgen_object_clone_ref", &|me| { - me.expose_add_heap_object(); - me.expose_get_object(); - let bump_cnt = if me.debug { - String::from(" - if (typeof(val) === 'number') - throw new Error('corrupt slab'); - val.cnt += 1; - ") - } else if me.ts { - String::from("(val as {cnt:number}).cnt += 1;") - } else { - String::from("val.cnt += 1;") - }; - let number = me.typed("number"); - format!(" - function(idx{}){} {{ - // If this object is on the stack promote it to the heap. - if ((idx & 1) === 1) - return addHeapObject(getObject(idx)); - - // Otherwise if the object is on the heap just bump the - // refcount and move on - const val = slab[idx >> 1]; - {} - return idx; - }} - ", number, number, bump_cnt) - }); - - bind("__wbindgen_object_drop_ref", &|me| { - me.expose_drop_ref(); - "dropRef".to_string() - }); - - bind("__wbindgen_string_new", &|me| { - me.expose_add_heap_object(); - me.expose_get_string_from_wasm(); - String::from("(p, l) => addHeapObject(getStringFromWasm(p, l))") - }); - - bind("__wbindgen_number_new", &|me| { - me.expose_add_heap_object(); - String::from("addHeapObject") - }); - - bind("__wbindgen_number_get", &|me| { - me.expose_global_memory(); - let number = me.typed("number"); - format!(" - function(n{0}, invalid{0}){0} {{ - let obj = getObject(n); - if (typeof(obj) === 'number') - return obj; - (new Uint8Array(memory.buffer))[invalid] = 1; - return 0; - }} - ", number) - }); - - bind("__wbindgen_undefined_new", &|me| { - me.expose_add_heap_object(); - String::from("() => addHeapObject(undefined)") - }); - - bind("__wbindgen_null_new", &|me| { - me.expose_add_heap_object(); - String::from("() => addHeapObject(null)") - }); - - bind("__wbindgen_is_null", &|me| { - me.expose_get_object(); - String::from("(idx) => getObject(idx) === null ? 1 : 0") - }); - - bind("__wbindgen_is_undefined", &|me| { - me.expose_get_object(); - String::from("(idx) => getObject(idx) === undefined ? 1 : 0") - }); - - bind("__wbindgen_boolean_new", &|me| { - me.expose_add_heap_object(); - String::from("(v) => addHeapObject(v == 1)") - }); - - bind("__wbindgen_boolean_get", &|me| { - me.expose_get_object(); - String::from("(i) => { - let v = getObject(i); - if (typeof(v) == 'boolean') { - return v ? 1 : 0; - } else { - return 2; - } - }") - }); - - bind("__wbindgen_symbol_new", &|me| { - me.expose_get_string_from_wasm(); - me.expose_add_heap_object(); - let symbol = me.typed("Symbol"); - format!("(ptr, len) => {{ - let a{}; - console.log(ptr, len); - if (ptr === 0) {{ - a = Symbol(); - }} else {{ - a = Symbol(getStringFromWasm(ptr, len)); - }} - return addHeapObject(a); - }}", symbol) - }); - - bind("__wbindgen_is_symbol", &|me| { - me.expose_get_object(); - String::from("(i) => typeof(getObject(i)) == 'symbol' ? 1 : 0") - }); - - bind("__wbindgen_throw", &|me| { - me.expose_get_string_from_wasm(); - let number = me.typed("number"); - format!(" - function(ptr{}, len{}) {{ - throw new Error(getStringFromWasm(ptr, len)); - }} - ", number, number) - }); - - bind("__wbindgen_string_get", &|me| { - me.expose_pass_string_to_wasm(m); - me.expose_get_object(); - me.expose_global_memory(); - String::from("(i, len_ptr) => { - let obj = getObject(i); - if (typeof(obj) !== 'string') - return 0; - const [ptr, len] = passStringToWasm(obj); - (new Uint32Array(memory.buffer))[len_ptr / 4] = len; - return ptr; - }") - }); - } - - let mut writes = String::new(); - if self.exposed_globals.contains(&"memory") { - writes.push_str("memory = exports.memory;\n"); - } - if self.exposed_globals.contains(&"wasm_exports") { - writes.push_str("wasm_exports = exports;\n"); - } - let mut interfaces = format!(" - /* tslint:disable */ - interface WasmImportsTop {{ - env: WasmImports, - }} - - interface WasmImports {{ - {wasm_imports} - }} - - interface WasmExports {{ - {wasm_exports} - }} - - export interface Imports {{ - {imports_interface} - }} - - {extra_imports_interface} - - export interface Exports {{ - module: WebAssembly.Module; - instance: WebAssembly.Module; - {exports_interface} - }} - - {extra_exports_interface} - ", - imports_interface = imports_interface, - extra_imports_interface = extra_imports_interface, - exports_interface = exports_interface, - extra_exports_interface = extra_exports_interface, - wasm_imports = wasm_imports.values() - .map(|s| &**s) - .collect::>() - .join("\n"), - wasm_exports = wasm_exports.values() - .map(|s| &**s) - .collect::>() - .join("\n"), - ); - if !self.ts { - interfaces.truncate(0); - } - - let any = self.typed("any"); - let imports_ty = self.typed("Imports"); - let exports_ty = self.typed("Exports"); - let result_ty = self.typed("WebAssembly.ResultObject"); - let promise_ty = self.typed("Promise"); - let wasm_exports_ty = self.typed("WasmExports"); - let wasm_imports_ty = self.typed("WasmImportsTop"); - - format!(" - /* tslint:disable */ - {globals} - - {interfaces} - - function xform(obj{result_ty}){exports_ty} {{ - let {{ module, instance }} = obj; - let exports{wasm_exports_ty} = instance.exports; - {writes} - return {exports}; - }} - export function instantiate(bytes{any}, _imports{imports_ty}){promise_ty} {{ - let wasm_imports{wasm_imports_ty} = {{ - env: {{ - {imports_object} - }}, - }}; - return WebAssembly.instantiate(bytes, wasm_imports).then(xform); - }} - ", - globals = self.globals, - interfaces = interfaces, - any = any, - result_ty = result_ty, - exports_ty = exports_ty, - promise_ty = promise_ty, - imports_ty = imports_ty, - wasm_imports_ty = wasm_imports_ty, - wasm_exports_ty = wasm_exports_ty, - exports = exports, - imports_object = imports_object, - writes = writes, - ) - } - - fn wasm_import_needed(&self, name: &str, m: &Mapped) -> bool { - let imports = match m.module.import_section() { - Some(s) => s, - None => return false, - }; - - imports.entries().iter().any(|i| { - i.module() == "env" && i.field() == m.import_name(name) - }) - } - - /// Returns a map of import name to the typescript definition for that name. - /// - /// This function generates the list of imports that a wasm module has, - /// using the source of truth (the was module itself) to generate this list. - fn typescript_wasm_imports(&self, m: &Module) -> HashMap { - let imports = match m.import_section() { - Some(s) => s, - None => return HashMap::new(), - }; - let types = match m.type_section() { - Some(s) => s, - None => return HashMap::new(), - }; - - let mut map = HashMap::new(); - for import in imports.entries() { - assert_eq!(import.module(), "env"); - - let ty = match *import.external() { - External::Function(i) => { - match types.types()[i as usize] { - Type::Function(ref t) => t, - } - } - _ => continue, - }; - - let mut ts = String::new(); - ts.push_str(import.field()); - ts.push_str("("); - // TODO: probably match `arg` to catch exhaustive errors in the - // future - for (i, _arg) in ty.params().iter().enumerate() { - if i > 0 { - ts.push_str(", "); - } - ts.push_str(&format!("arg{}{}", i, self.typed("number"))); - } - ts.push_str(")"); - if ty.return_type().is_none() { - ts.push_str(&self.typed("void").to_string()); - } else { - ts.push_str(&self.typed("number").to_string()); - } - ts.push_str(";"); - - map.insert(import.field().to_string(), ts); - } - return map; - } - - /// Returns a map from export name to its typescript signature. - /// - /// This uses the module itself as the source of truth to help flesh out - /// bugs in this program. - fn typescript_wasm_exports(&self, m: &Module) -> HashMap { - let imported_functions = match m.import_section() { - Some(s) => s.functions(), - None => 0, - }; - let functions = match m.function_section() { - Some(s) => s, - None => return HashMap::new(), - }; - let types = match m.type_section() { - Some(s) => s, - None => return HashMap::new(), - }; - let exports = match m.export_section() { - Some(s) => s, - None => return HashMap::new(), - }; - - let mut map = HashMap::new(); - for export in exports.entries() { - let fn_idx = match *export.internal() { - Internal::Function(i) => i as usize, - Internal::Memory(_) => { - map.insert(export.field().to_string(), - format!("{}: WebAssembly.Memory;", export.field())); - continue - } - _ => continue, - }; - assert!(fn_idx >= imported_functions); - let function = &functions.entries()[fn_idx - imported_functions]; - let ty = match types.types()[function.type_ref() as usize] { - Type::Function(ref t) => t, - }; - - let mut ts = String::new(); - ts.push_str(export.field()); - ts.push_str("("); - // TODO: probably match `arg` to catch exhaustive errors in the - // future - for (i, _arg) in ty.params().iter().enumerate() { - if i > 0 { - ts.push_str(", "); - } - ts.push_str(&format!("arg{}{}", i, self.typed("number"))); - } - ts.push_str(")"); - if ty.return_type().is_none() { - ts.push_str(&self.typed("void").to_string()); - } else { - ts.push_str(&self.typed("number").to_string()); - } - ts.push_str(";"); - map.insert(export.field().to_string(), ts); - } - return map; - } - - fn expose_drop_ref(&mut self) { - if !self.exposed_globals.insert("drop_ref") { - return - } - self.expose_global_slab(); - self.expose_global_slab_next(); - let validate_owned = if self.debug { - String::from(" - if ((idx & 1) === 1) - throw new Error('cannot drop ref of stack objects'); - ") - } else { - String::new() - }; - let dec_ref = if self.debug { - String::from(" - if (typeof(obj) === 'number') - throw new Error('corrupt slab'); - obj.cnt -= 1; - if (obj.cnt > 0) - return; - ") - } else if self.ts { - String::from(" - (obj as {cnt:number}).cnt -= 1; - if ((obj as {cnt:number}).cnt > 0) - return; - ") - } else { - String::from(" - obj.cnt -= 1; - if (obj.cnt > 0) - return; - ") - }; - let number = self.typed("number"); - let void = self.typed("void"); - self.globals.push_str(&format!(" - function dropRef(idx{}){} {{ - {} - - let obj = slab[idx >> 1]; - {} - - // If we hit 0 then free up our space in the slab - slab[idx >> 1] = slab_next; - slab_next = idx >> 1; - }} - ", number, void, validate_owned, dec_ref)); - } - - fn expose_global_stack(&mut self) { - if !self.exposed_globals.insert("stack") { - return - } - let ty = self.typed("any[]"); - self.globals.push_str(&format!(" - let stack{} = []; - ", ty)); - } - - fn expose_global_slab(&mut self) { - if !self.exposed_globals.insert("slab") { - return - } - let ty = self.typed("({ obj: any, cnt: number } | number)[]"); - self.globals.push_str(&format!("let slab{} = [];", ty)); - } - - fn expose_global_slab_next(&mut self) { - if !self.exposed_globals.insert("slab_next") { - return - } - let ty = self.typed("number"); - self.globals.push_str(&format!(" - let slab_next{} = 0; - ", ty)); - } - - fn expose_get_object(&mut self) { - if !self.exposed_globals.insert("get_object") { - return - } - self.expose_global_stack(); - self.expose_global_slab(); - - let get_obj = if self.debug { - String::from(" - if (typeof(val) === 'number') - throw new Error('corrupt slab'); - return val.obj; - ") - } else if self.ts { - String::from(" - return (val as {obj:any}).obj; - ") - } else { - String::from(" - return val.obj; - ") - }; - let number = self.typed("number"); - let any = self.typed("any"); - self.globals.push_str(&format!(" - function getObject(idx{}){} {{ - if ((idx & 1) === 1) {{ - return stack[idx >> 1]; - }} else {{ - const val = slab[idx >> 1]; - {} - }} - }} - ", number, any, get_obj)); - } - - fn expose_global_memory(&mut self) { - if !self.exposed_globals.insert("memory") { - return - } - let mem = self.typed("WebAssembly.Memory"); - self.globals.push_str(&format!("let memory{};\n", mem)); - } - - fn expose_wasm_exports(&mut self) { - if !self.exposed_globals.insert("wasm_exports") { - return - } - let ty = self.typed("WasmExports"); - self.globals.push_str(&format!("let wasm_exports{};\n", ty)); - } - - fn expose_check_token(&mut self) { - if !self.exposed_globals.insert("check_token") { - return - } - let symbol = self.typed("Symbol"); - let void = self.typed("void"); - self.globals.push_str(&format!(" - const token = Symbol('foo'); - function _checkToken(sym{}){} {{ - if (token !== sym) - throw new Error('cannot invoke `new` directly'); - }} - ", symbol, void)); - } - - fn expose_assert_num(&mut self) { - if !self.exposed_globals.insert("assert_num") { - return - } - let number = self.typed("number"); - let void = self.typed("void"); - self.globals.push_str(&format!(" - function _assertNum(n{}){} {{ - if (typeof(n) !== 'number') - throw new Error('expected a number argument'); - }} - ", number, void)); - } - - fn expose_assert_bool(&mut self) { - if !self.exposed_globals.insert("assert_bool") { - return - } - let boolean = self.typed("boolean"); - self.globals.push_str(&format!(" - function _assertBoolean(n{}) {{ - if (typeof(n) !== 'boolean') - throw new Error('expected a boolean argument'); - }} - ", boolean)); - } - - fn expose_pass_string_to_wasm(&mut self, m: &Mapped) { - if !self.exposed_globals.insert("pass_string_to_wasm") { - return - } - self.expose_wasm_exports(); - self.expose_global_memory(); - let string = self.typed("string"); - let ret = self.typed("[number, number]"); - if self.nodejs { - self.globals.push_str(&format!(" - function passStringToWasm(arg{}){} {{ - if (typeof(arg) !== 'string') - throw new Error('expected a string argument'); - const buf = Buffer.from(arg); - const len = buf.length; - const ptr = wasm_exports.{}(len); - buf.copy(Buffer.from(memory.buffer), ptr); - return [ptr, len]; - }} - ", string, ret, m.export_name("__wbindgen_malloc"))); - } else { - self.globals.push_str(&format!(" - function passStringToWasm(arg{}){} {{ - if (typeof(arg) !== 'string') - throw new Error('expected a string argument'); - const buf = new TextEncoder('utf-8').encode(arg); - const len = buf.length; - const ptr = wasm_exports.{}(len); - let array = new Uint8Array(memory.buffer); - array.set(buf, ptr); - return [ptr, len]; - }} - ", string, ret, m.export_name("__wbindgen_malloc"))); - } - } - - fn expose_get_string_from_wasm(&mut self) { - if !self.exposed_globals.insert("get_string_from_wasm") { - return - } - let number = self.typed("number"); - let string = self.typed("string"); - if self.nodejs { - self.expose_global_memory(); - self.globals.push_str(&format!(" - function getStringFromWasm(ptr{}, len{}){} {{ - const buf = Buffer.from(memory.buffer).slice(ptr, ptr + len); - const ret = buf.toString(); - return ret; - }} - ", number, number, string)); - } else { - self.expose_global_memory(); - self.globals.push_str(&format!(" - function getStringFromWasm(ptr{}, len{}){} {{ - const mem = new Uint8Array(memory.buffer); - const slice = mem.slice(ptr, ptr + len); - const ret = new TextDecoder('utf-8').decode(slice); - return ret; - }} - ", number, number, string)); - } - } - - fn expose_assert_class(&mut self) { - if !self.exposed_globals.insert("assert_class") { - return - } - let any = self.typed("any"); - self.globals.push_str(&format!(" - function _assertClass(instance{}, klass{}) {{ - if (!(instance instanceof klass)) - throw new Error(`expected instance of ${{klass.name}}`); - return instance.ptr; - }} - ", any, any)); - } - - fn expose_borrowed_objects(&mut self) { - if !self.exposed_globals.insert("borrowed_objects") { - return - } - self.expose_global_stack(); - let any = self.typed("any"); - let number = self.typed("number"); - self.globals.push_str(&format!(" - function addBorrowedObject(obj{}){} {{ - stack.push(obj); - return ((stack.length - 1) << 1) | 1; - }} - ", any, number)); - } - - fn expose_take_object(&mut self) { - if !self.exposed_globals.insert("take_object") { - return - } - self.expose_get_object(); - self.expose_drop_ref(); - let number = self.typed("number"); - let any = self.typed("any"); - self.globals.push_str(&format!(" - function takeObject(idx{}){} {{ - const ret = getObject(idx); - dropRef(idx); - return ret; - }} - ", number, any)); - } - - fn expose_add_heap_object(&mut self) { - if !self.exposed_globals.insert("add_heap_object") { - return - } - self.expose_global_slab(); - self.expose_global_slab_next(); - let set_slab_next = if self.debug { - String::from(" - if (typeof(next) !== 'number') - throw new Error('corrupt slab'); - slab_next = next; - ") - } else if self.ts { - String::from(" - slab_next = next as number; - ") - } else { - String::from(" - slab_next = next; - ") - }; - let any = self.typed("any"); - let number = self.typed("number"); - self.globals.push_str(&format!(" - function addHeapObject(obj{}){} {{ - if (slab_next == slab.length) - slab.push(slab.length + 1); - const idx = slab_next; - const next = slab[idx]; - {} - slab[idx] = {{ obj, cnt: 1 }}; - return idx << 1; - }} - ", any, number, set_slab_next)); - } - - fn typed(&self, t: T) -> MaybeEmit> { - self.maybe(Typed(t)) - } - - fn maybe(&self, t: T) -> MaybeEmit { - MaybeEmit { - enabled: self.ts, - inner: t, - } - } -} - -struct MaybeEmit { - enabled: bool, - inner: T, -} - -impl fmt::Display for MaybeEmit { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.enabled { - self.inner.fmt(f) - } else { - Ok(()) - } - } -} - -struct Typed(T); - -impl fmt::Display for Typed { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, ": {}", self.0) - } -} diff --git a/crates/wasm-bindgen-cli-support/src/wasm2es6js.rs b/crates/wasm-bindgen-cli-support/src/wasm2es6js.rs new file mode 100644 index 00000000..bd2b6d9e --- /dev/null +++ b/crates/wasm-bindgen-cli-support/src/wasm2es6js.rs @@ -0,0 +1,214 @@ +extern crate base64; + +use std::collections::HashSet; + +use parity_wasm::elements::*; + +use super::Error; + +pub struct Config { + base64: bool, +} + +pub struct Output { + module: Module, + base64: bool, +} + +impl Config { + pub fn new() -> Config { + Config { + base64: false, + } + } + + pub fn base64(&mut self, base64: bool) -> &mut Self { + self.base64 = base64; + self + } + + pub fn generate(&mut self, wasm: &[u8]) -> Result { + assert!(self.base64); + let module = deserialize_buffer(wasm).map_err(|e| { + format_err!("{:?}", e) + })?; + Ok(Output { + module, + base64: self.base64, + }) + } +} + +impl Output { + pub fn typescript(&self) -> String { + let mut exports = String::new(); + + if let Some(i) = self.module.export_section() { + let imported_functions = self.module.import_section() + .map(|m| m.functions() as u32) + .unwrap_or(0); + for entry in i.entries() { + let idx = match *entry.internal() { + Internal::Function(i) => i - imported_functions, + Internal::Memory(_) => { + exports.push_str(&format!(" + export const {}: WebAssembly.Memory; + ", entry.field())); + continue + } + Internal::Table(_) => { + panic!("wasm exports a table which isn't supported yet"); + } + Internal::Global(_) => { + panic!("wasm exports globals which aren't supported yet"); + } + }; + + let functions = self.module.function_section() + .expect("failed to find function section"); + let idx = functions.entries()[idx as usize].type_ref(); + + let types = self.module.type_section() + .expect("failed to find type section"); + let ty = match types.types()[idx as usize] { + Type::Function(ref f) => f, + }; + let mut args = String::new(); + for (i, _) in ty.params().iter().enumerate() { + if i > 0 { + args.push_str(", "); + } + args.push((b'a' + (i as u8)) as char); + args.push_str(": number"); + } + + exports.push_str(&format!(" + export function {name}({args}): {ret}; + ", + name = entry.field(), + args = args, + ret = if ty.return_type().is_some() { "number" } else { "void" }, + )); + } + } + + if self.base64 { + exports.push_str("export const booted: Promise;"); + } + + return exports + } + + pub fn js(self) -> String { + let mut js_imports = String::new(); + let mut exports = String::new(); + let mut imports = String::new(); + let mut export_mem = false; + + if let Some(i) = self.module.import_section() { + let mut set = HashSet::new(); + for entry in i.entries() { + match *entry.external() { + External::Function(_) => {} + External::Table(_) => { + panic!("wasm imports a table which isn't supported yet"); + } + External::Memory(_) => { + panic!("wasm imports memory which isn't supported yet"); + } + External::Global(_) => { + panic!("wasm imports globals which aren't supported yet"); + } + } + + if !set.insert(entry.module()) { + continue + } + + let name = (b'a' + (set.len() as u8)) as char; + js_imports.push_str(&format!("import * as import_{} from '{}';", + name, + entry.module())); + imports.push_str(&format!("'{}': import_{}, ", entry.module(), name)); + } + } + + if let Some(i) = self.module.export_section() { + let imported_functions = self.module.import_section() + .map(|m| m.functions() as u32) + .unwrap_or(0); + for entry in i.entries() { + let idx = match *entry.internal() { + Internal::Function(i) => i - imported_functions, + Internal::Memory(_) => { + export_mem = true; + continue + } + Internal::Table(_) => { + panic!("wasm exports a table which isn't supported yet"); + } + Internal::Global(_) => { + panic!("wasm exports globals which aren't supported yet"); + } + }; + + let functions = self.module.function_section() + .expect("failed to find function section"); + let idx = functions.entries()[idx as usize].type_ref(); + + let types = self.module.type_section() + .expect("failed to find type section"); + let ty = match types.types()[idx as usize] { + Type::Function(ref f) => f, + }; + let mut args = String::new(); + for (i, _) in ty.params().iter().enumerate() { + if i > 0 { + args.push_str(", "); + } + args.push((b'a' + (i as u8)) as char); + } + + exports.push_str(&format!(" + export function {name}({args}) {{ + {ret} wasm.exports.{name}({args}); + }} + ", + name = entry.field(), + args = args, + ret = if ty.return_type().is_some() { "return" } else { "" }, + )); + } + } + + let wasm = serialize(self.module) + .expect("failed to serialize"); + + format!(" + {js_imports} + let wasm; + let bytes; + const base64 = \"{base64}\"; + if (typeof Buffer === 'undefined') {{ + bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0)); + }} else {{ + bytes = Buffer.from(base64, 'base64'); + }} + {mem_export} + export const booted = WebAssembly.instantiate(bytes, {{ {imports} }}) + .then(obj => {{ + wasm = obj.instance; + {memory} + }}); + + {exports} + ", + base64 = base64::encode(&wasm), + js_imports = js_imports, + imports = imports, + exports = exports, + mem_export = if export_mem { "export let memory;" } else { "" }, + memory = if export_mem { "memory = wasm.exports.memory;" } else { "" }, + ) + } +} diff --git a/crates/wasm-bindgen-cli/Cargo.toml b/crates/wasm-bindgen-cli/Cargo.toml index 246449dc..e4d1cfe6 100644 --- a/crates/wasm-bindgen-cli/Cargo.toml +++ b/crates/wasm-bindgen-cli/Cargo.toml @@ -5,10 +5,15 @@ authors = ["Alex Crichton "] [dependencies] docopt = "0.8" +parity-wasm = "0.23" serde = "1.0" serde_derive = "1.0" wasm-bindgen-cli-support = { path = "../wasm-bindgen-cli-support" } [[bin]] name = "wasm-bindgen" -path = "src/main.rs" +path = "src/bin/wasm-bindgen.rs" + +[[bin]] +name = "wasm2es6js" +path = "src/bin/wasm2es6js.rs" diff --git a/crates/wasm-bindgen-cli/src/main.rs b/crates/wasm-bindgen-cli/src/bin/wasm-bindgen.rs similarity index 50% rename from crates/wasm-bindgen-cli/src/main.rs rename to crates/wasm-bindgen-cli/src/bin/wasm-bindgen.rs index 14c430ba..f067de93 100644 --- a/crates/wasm-bindgen-cli/src/main.rs +++ b/crates/wasm-bindgen-cli/src/bin/wasm-bindgen.rs @@ -17,19 +17,17 @@ Usage: Options: -h --help Show this screen. - --output-js FILE Output Javascript file - --output-ts FILE Output TypeScript file - --output-wasm FILE Output WASM file + --out-dir DIR Output directory --nodejs Generate output for node.js, not the browser + --typescript Output a TypeScript definition file --debug Include otherwise-extraneous debug checks in output "; #[derive(Debug, Deserialize)] struct Args { - flag_output_js: Option, - flag_output_ts: Option, - flag_output_wasm: Option, flag_nodejs: bool, + flag_typescript: bool, + flag_out_dir: Option, flag_debug: bool, arg_input: PathBuf, } @@ -43,21 +41,12 @@ fn main() { b.input_path(&args.arg_input) .nodejs(args.flag_nodejs) .debug(args.flag_debug) - .uglify_wasm_names(!args.flag_debug); - let ret = b.generate().expect("failed to generate bindings"); - let mut written = false; - if let Some(ref ts) = args.flag_output_ts { - ret.write_ts_to(ts).expect("failed to write TypeScript output file"); - written = true; - } - if let Some(ref js) = args.flag_output_js { - ret.write_js_to(js).expect("failed to write Javascript output file"); - written = true; - } - if !written { - println!("{}", ret.generate_ts()); - } - if let Some(ref wasm) = args.flag_output_wasm { - ret.write_wasm_to(wasm).expect("failed to write wasm output file"); - } + .typescript(args.flag_typescript); + + let out_dir = match args.flag_out_dir { + Some(ref p) => p, + None => panic!("the `--out-dir` argument is now required"), + }; + + b.generate(out_dir).expect("failed to generate bindings"); } diff --git a/crates/wasm-bindgen-cli/src/bin/wasm2es6js.rs b/crates/wasm-bindgen-cli/src/bin/wasm2es6js.rs new file mode 100644 index 00000000..7f67714e --- /dev/null +++ b/crates/wasm-bindgen-cli/src/bin/wasm2es6js.rs @@ -0,0 +1,63 @@ +#[macro_use] +extern crate serde_derive; +extern crate docopt; +extern crate parity_wasm; +extern crate wasm_bindgen_cli_support; + +use std::collections::HashSet; +use std::fs::File; +use std::io::{Write, Read}; +use std::path::PathBuf; + +use docopt::Docopt; +use parity_wasm::elements::*; + +const USAGE: &'static str = " +Converts a wasm file to an ES6 JS module + +Usage: + wasm2es6js [options] + wasm2es6js -h | --help + +Options: + -h --help Show this screen. + -o --output FILE File to place output in + --base64 Inline the wasm module using base64 encoding +"; + +#[derive(Debug, Deserialize)] +struct Args { + flag_output: Option, + flag_base64: bool, + arg_input: PathBuf, +} + +fn main() { + let args: Args = Docopt::new(USAGE) + .and_then(|d| d.deserialize()) + .unwrap_or_else(|e| e.exit()); + + if !args.flag_base64 { + panic!("unfortunately only works right now with base64"); + } + + let mut wasm = Vec::new(); + File::open(&args.arg_input).expect("failed to open input") + .read_to_end(&mut wasm).expect("failed to read input"); + + let object = wasm_bindgen_cli_support::wasm2es6js::Config::new() + .base64(args.flag_base64) + .generate(&wasm) + .expect("failed to parse wasm"); + let js = object.js(); + + match args.flag_output { + Some(ref p) => { + File::create(p).expect("failed to create output") + .write_all(js.as_bytes()).expect("failed to write output"); + } + None => { + println!("{}", js); + } + } +} diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index 125c68d2..8ad348e3 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -15,6 +15,7 @@ pub struct Function { } pub struct Import { + pub module: String, pub function: Function, pub decl: Box, pub ident: syn::Ident, @@ -79,18 +80,41 @@ impl Program { Some(ref l) if l.value() == "JS" => {} _ => panic!("only foreign mods with the `JS` ABI are allowed"), } + let module = f.attrs.iter() + .filter_map(|f| f.interpret_meta()) + .filter_map(|i| { + match i { + syn::Meta::NameValue(i) => { + if i.ident == "wasm_module" { + Some(i.lit) + } else { + None + } + } + _ => None, + } + }) + .next() + .and_then(|lit| { + match lit { + syn::Lit::Str(v) => Some(v.value()), + _ => None, + } + }) + .expect("must specify `#[wasm_module = ...]` for module to import from"); for item in f.items.iter() { - self.push_foreign_item(item); + self.push_foreign_item(&module, item); } } - pub fn push_foreign_item(&mut self, f: &syn::ForeignItem) { + pub fn push_foreign_item(&mut self, module: &str, f: &syn::ForeignItem) { let f = match *f { syn::ForeignItem::Fn(ref f) => f, _ => panic!("only foreign functions allowed for now, not statics"), }; self.imports.push(Import { + module: module.to_string(), attrs: f.attrs.clone(), vis: f.vis.clone(), decl: f.decl.clone(), @@ -103,7 +127,9 @@ impl Program { shared::Program { structs: self.structs.iter().map(|s| s.shared()).collect(), free_functions: self.free_functions.iter().map(|s| s.shared()).collect(), - imports: self.imports.iter().map(|i| i.function.shared()).collect(), + imports: self.imports.iter() + .map(|i| (i.module.clone(), i.function.shared())) + .collect(), } } } diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index 65c77f17..f8e13e53 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -5,7 +5,7 @@ extern crate serde_derive; pub struct Program { pub structs: Vec, pub free_functions: Vec, - pub imports: Vec, + pub imports: Vec<(String, Function)>, } #[derive(Serialize, Deserialize)] diff --git a/src/lib.rs b/src/lib.rs index a8efe69c..0eca8d5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -338,9 +338,7 @@ pub mod __rt { } pub fn into_inner(self) -> T { - unsafe { - self.value.into_inner() - } + self.value.into_inner() } } diff --git a/tests/api.rs b/tests/api.rs index 84d609fa..59ad685b 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -98,11 +98,9 @@ fn works() { "#) .file("test.ts", r#" import * as assert from "assert"; - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { + export function test() { assert.strictEqual(wasm.foo(), 'foo'); assert.strictEqual(wasm.bar('a'), 'a'); assert.strictEqual(wasm.baz(), 1); @@ -119,10 +117,10 @@ fn works() { assert.strictEqual(typeof(wasm.mk_symbol()), 'symbol'); assert.strictEqual(typeof(wasm.mk_symbol2('a')), 'symbol'); - assert.strictEqual(Symbol.keyFor(wasm.mk_symbol()), undefined); - assert.strictEqual(Symbol.keyFor(wasm.mk_symbol2('b')), undefined); + assert.strictEqual((Symbol as any).keyFor(wasm.mk_symbol()), undefined); + assert.strictEqual((Symbol as any).keyFor(wasm.mk_symbol2('b')), undefined); - wasm.assert_symbols(Symbol(), 'a'); + wasm.assert_symbols((Symbol as any)(), 'a'); wasm.acquire_string('foo', null) assert.strictEqual(wasm.acquire_string2(''), ''); assert.strictEqual(wasm.acquire_string2('a'), 'a'); diff --git a/tests/classes.rs b/tests/classes.rs index 0125c0cc..52ee5ba7 100644 --- a/tests/classes.rs +++ b/tests/classes.rs @@ -33,18 +33,16 @@ fn simple() { "#) .file("test.ts", r#" import * as assert from "assert"; - import { Exports, Imports } from "./out"; + import { Foo } from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { - const r = wasm.Foo.new(); + export function test() { + const r = Foo.new(); assert.strictEqual(r.add(0), 0); assert.strictEqual(r.add(1), 1); assert.strictEqual(r.add(1), 2); r.free(); - const r2 = wasm.Foo.with_contents(10); + const r2 = Foo.with_contents(10); assert.strictEqual(r2.add(1), 11); assert.strictEqual(r2.add(2), 13); assert.strictEqual(r2.add(3), 16); @@ -96,12 +94,10 @@ fn strings() { "#) .file("test.ts", r#" import * as assert from "assert"; - import { Exports, Imports } from "./out"; + import { Foo } from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { - const r = wasm.Foo.new(); + export function test() { + const r = Foo.new(); r.set(3); let bar = r.bar('baz'); r.free(); @@ -149,32 +145,28 @@ fn exceptions() { } "#) .file("test.js", r#" - var assert = require("assert"); + import * as assert from "assert"; + import { A, B } from "./out"; - exports.imports = {}; - exports.test = function(wasm) { - assert.throws(() => new wasm.A(), /cannot invoke `new` directly/); - let a = wasm.A.new(); + export function test() { + assert.throws(() => new A(), /cannot invoke `new` directly/); + let a = A.new(); a.free(); assert.throws(() => a.free(), /null pointer passed to rust/); - let b = wasm.A.new(); + let b = A.new(); b.foo(b); assert.throws(() => b.bar(b), /recursive use of an object/); - let c = wasm.A.new(); - let d = wasm.B.new(); + let c = A.new(); + let d = B.new(); assert.throws(() => c.foo(d), /expected instance of A/); d.free(); c.free(); }; "#) .file("test.d.ts", r#" - import { Exports, Imports } from "./out"; - - export const imports: Imports; - - export function test(wasm: Exports): void; + export function test(): void; "#) .test(); } @@ -214,13 +206,11 @@ fn pass_one_to_another() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import { A, B } from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { - let a = wasm.A.new(); - let b = wasm.B.new(); + export function test() { + let a = A.new(); + let b = B.new(); a.foo(b); a.bar(b); a.free(); diff --git a/tests/imports.rs b/tests/imports.rs index 74ca68ab..545af4af 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -11,6 +11,7 @@ fn simple() { use wasm_bindgen::prelude::*; wasm_bindgen! { + #[wasm_module = "./test"] extern "JS" { fn foo(s: &str); fn another(a: u32) -> i32; @@ -33,34 +34,32 @@ fn simple() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; import * as assert from "assert"; let ARG: string | null = null; let ANOTHER_ARG: number | null = null; - let SYM = Symbol('a'); + let SYM = (Symbol as any)('a'); - export const imports: Imports = { - foo(s) { - assert.strictEqual(ARG, null); - assert.strictEqual(s, "foo"); - ARG = s; - }, - another(s) { - assert.strictEqual(ANOTHER_ARG, null); - assert.strictEqual(s, 21); - ANOTHER_ARG = s; - return 35; - }, - take_and_return_bool(s: boolean): boolean { - return s; - }, - return_object(): any { - return SYM; - }, - }; + export function foo(s: string): void { + assert.strictEqual(ARG, null); + assert.strictEqual(s, "foo"); + ARG = s; + } + export function another(s: number): number { + assert.strictEqual(ANOTHER_ARG, null); + assert.strictEqual(s, 21); + ANOTHER_ARG = s; + return 35; + } + export function take_and_return_bool(s: boolean): boolean { + return s; + } + export function return_object(): any { + return SYM; + } - export function test(wasm: Exports) { + export function test() { assert.strictEqual(ARG, null); wasm.bar("foo"); assert.strictEqual(ARG, "foo"); @@ -89,6 +88,7 @@ fn unused() { use wasm_bindgen::prelude::*; wasm_bindgen! { + #[wasm_module = "./test"] extern "JS" { fn debug_print(s: &str); } @@ -97,11 +97,11 @@ fn unused() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; - export const imports: Imports = {}; + export function debug_print() {} - export function test(wasm: Exports) { + export function test() { wasm.bar(); } "#) diff --git a/tests/js.rs b/tests/js.rs deleted file mode 100644 index ab3f7e83..00000000 --- a/tests/js.rs +++ /dev/null @@ -1,79 +0,0 @@ -extern crate test_support; - -const SRC: &str = r#" - #![feature(proc_macro)] - - extern crate wasm_bindgen; - - use wasm_bindgen::prelude::*; - - wasm_bindgen! { - pub struct A {} - - impl A { - pub fn new() -> A { - A {} - } - } - pub fn clone(a: &JsObject) -> JsObject { - drop(a.clone()); - a.clone() - } - - extern "JS" { - fn bar(a: &JsObject, b: JsObject); - } - - pub fn foo( - _: &str, - _: bool, - _: i32, - _: &A, - _: A, - a: JsObject, - b: &JsObject, - ) -> String { - a.is_symbol(); - a.as_f64(); - a.as_string(); - a.as_bool(); - a.is_null(); - a.is_undefined(); - bar(b, a); - JsObject::from("a"); - JsObject::from(3); - String::new() - } - } -"#; - -#[test] -fn works() { - test_support::project() - .js(true) - .debug(true) - .file("src/lib.rs", SRC) - .file("test.ts", r#" - export const imports = {}; - - export function test(_) { - } - "#) - .test(); -} - -#[test] -fn works_non_debug() { - test_support::project() - .js(true) - .debug(false) - .file("src/lib.rs", SRC) - .file("test.ts", r#" - export const imports = {}; - - export function test(_) { - } - "#) - .test(); -} - diff --git a/tests/jsobjects.rs b/tests/jsobjects.rs index 6db5219f..806e8252 100644 --- a/tests/jsobjects.rs +++ b/tests/jsobjects.rs @@ -11,6 +11,7 @@ fn simple() { use wasm_bindgen::prelude::*; wasm_bindgen! { + #[wasm_module = "./test"] extern "JS" { fn foo(s: &JsObject); } @@ -20,21 +21,19 @@ fn simple() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; import * as assert from "assert"; let ARG: string | null = null; - export const imports: Imports = { - foo(s) { - assert.strictEqual(ARG, null); - ARG = s; - }, - }; - - export function test(wasm: Exports) { + export function foo(s: any): void { assert.strictEqual(ARG, null); - let sym = Symbol('test'); + ARG = s; + } + + export function test() { + assert.strictEqual(ARG, null); + let sym = (Symbol as any)('test'); wasm.bar(sym); assert.strictEqual(ARG, sym); } @@ -53,6 +52,7 @@ fn owned() { use wasm_bindgen::prelude::*; wasm_bindgen! { + #[wasm_module = "./test"] extern "JS" { fn foo(s: JsObject); } @@ -62,21 +62,19 @@ fn owned() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; import * as assert from "assert"; - let ARG: Symbol | null = null; + let ARG: any = null; - export const imports: Imports = { - foo(s) { - assert.strictEqual(ARG, null); - ARG = s; - }, - }; - - export function test(wasm: Exports) { + export function foo(s: any): void { assert.strictEqual(ARG, null); - let sym = Symbol('test'); + ARG = s; + } + + export function test() { + assert.strictEqual(ARG, null); + let sym = (Symbol as any)('test'); wasm.bar(sym); assert.strictEqual(ARG, sym); } @@ -95,6 +93,7 @@ fn clone() { use wasm_bindgen::prelude::*; wasm_bindgen! { + #[wasm_module = "./test"] extern "JS" { fn foo1(s: JsObject); fn foo2(s: &JsObject); @@ -113,20 +112,18 @@ fn clone() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; import * as assert from "assert"; - let ARG = Symbol('test'); + let ARG = (Symbol as any)('test'); - export const imports: Imports = { - foo1(s) { assert.strictEqual(s, ARG); }, - foo2(s) { assert.strictEqual(s, ARG); }, - foo3(s) { assert.strictEqual(s, ARG); }, - foo4(s) { assert.strictEqual(s, ARG); }, - foo5(s) { assert.strictEqual(s, ARG); }, - }; + export function foo1(s: any): void { assert.strictEqual(s, ARG); } + export function foo2(s: any): void { assert.strictEqual(s, ARG); } + export function foo3(s: any): void { assert.strictEqual(s, ARG); } + export function foo4(s: any): void { assert.strictEqual(s, ARG); } + export function foo5(s: any): void { assert.strictEqual(s, ARG); } - export function test(wasm: Exports) { + export function test() { wasm.bar(ARG); } "#) @@ -144,6 +141,7 @@ fn promote() { use wasm_bindgen::prelude::*; wasm_bindgen! { + #[wasm_module = "./test"] extern "JS" { fn foo1(s: &JsObject); fn foo2(s: JsObject); @@ -160,19 +158,17 @@ fn promote() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; import * as assert from "assert"; - let ARG = Symbol('test'); + let ARG = (Symbol as any)('test'); - export const imports: Imports = { - foo1(s) { assert.strictEqual(s, ARG); }, - foo2(s) { assert.strictEqual(s, ARG); }, - foo3(s) { assert.strictEqual(s, ARG); }, - foo4(s) { assert.strictEqual(s, ARG); }, - }; + export function foo1(s: any): void { assert.strictEqual(s, ARG); } + export function foo2(s: any): void { assert.strictEqual(s, ARG); } + export function foo3(s: any): void { assert.strictEqual(s, ARG); } + export function foo4(s: any): void { assert.strictEqual(s, ARG); } - export function test(wasm: Exports) { + export function test() { wasm.bar(ARG); } "#) diff --git a/tests/non-debug.rs b/tests/non-debug.rs index 0f467061..023859fe 100644 --- a/tests/non-debug.rs +++ b/tests/non-debug.rs @@ -27,12 +27,10 @@ fn works() { "#) .file("test.ts", r#" import * as assert from "assert"; - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { - let sym = Symbol('a'); + export function test() { + let sym = (Symbol as any)('a'); assert.strictEqual(wasm.clone(sym), sym); let a = wasm.A.new(); a.free(); diff --git a/tests/simple.rs b/tests/simple.rs index 41c1ca6f..6338c0d8 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -37,11 +37,9 @@ fn add() { "#) .file("test.ts", r#" import * as assert from "assert"; - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { + export function test() { assert.strictEqual(wasm.add(1, 2), 3); assert.strictEqual(wasm.add(2, 3), 5); assert.strictEqual(wasm.add3(2), 5); @@ -74,11 +72,9 @@ fn string_arguments() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { + export function test() { wasm.assert_foo("foo"); wasm.assert_foo_and_bar("foo2", "bar"); } @@ -110,11 +106,9 @@ fn return_a_string() { "#) .file("test.ts", r#" import * as assert from "assert"; - import { Exports, Imports } from "./out"; + import * as wasm from "./out"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { + export function test() { assert.strictEqual(wasm.clone("foo"), "foo"); assert.strictEqual(wasm.clone("another"), "another"); assert.strictEqual(wasm.concat("a", "b", 3), "a b 3"); @@ -140,66 +134,53 @@ fn exceptions() { } "#) .file("test.js", r#" - var assert = require("assert"); + import * as assert from "assert"; + import * as wasm from "./out"; - exports.imports = {}; - exports.test = function(wasm) { + export function test() { assert.throws(() => wasm.foo('a'), /expected a number argument/); assert.throws(() => wasm.bar(3), /expected a string argument/); - }; + } "#) .file("test.d.ts", r#" - import { Exports, Imports } from "./out"; - - export const imports: Imports; - - export function test(wasm: Exports): void; + export function test(): void; "#) .test(); } -#[test] -fn other_imports() { - test_support::project() - .file("src/lib.rs", r#" - #![feature(proc_macro)] - - extern crate wasm_bindgen; - - use wasm_bindgen::prelude::*; - - extern { - fn another_import(a: u32); - } - - wasm_bindgen! { - pub fn foo(a: u32) { - unsafe { another_import(a); } - } - } - "#) - .file("test.ts", r#" - import * as assert from "assert"; - import { Exports, Imports } from "./out"; - - let ARG: number | null = null; - - export const imports: Imports = { - env: { - another_import(a: number) { - assert.strictEqual(ARG, null); - ARG = a; - }, - }, - }; - - export function test(wasm: Exports) { - wasm.foo(2); - assert.strictEqual(ARG, 2); - } - "#) - .test(); -} +// #[test] +// fn other_imports() { +// test_support::project() +// .file("src/lib.rs", r#" +// #![feature(proc_macro)] +// +// extern crate wasm_bindgen; +// +// use wasm_bindgen::prelude::*; +// +// extern { +// fn another_import(a: u32); +// } +// +// wasm_bindgen! { +// pub fn foo(a: u32) { +// unsafe { another_import(a); } +// } +// } +// "#) +// .file("test.ts", r#" +// import * as assert from "assert"; +// import * as wasm from "./out"; +// +// let ARG: number | null = null; +// +// export function test() { +// wasm.foo(2); +// assert.strictEqual(ARG, 2); +// } +// "#) +// .test(); +// } #[test] fn other_exports() { @@ -210,12 +191,10 @@ fn other_exports() { } "#) .file("test.ts", r#" - import { Exports, Imports } from "./out"; + import * as wasm from "./out_wasm"; - export const imports: Imports = {}; - - export function test(wasm: Exports) { - wasm.extra.foo(2); + export function test() { + wasm.foo(2); } "#) .test(); diff --git a/tests/uglify.rs b/tests/uglify.rs deleted file mode 100644 index 06c66afc..00000000 --- a/tests/uglify.rs +++ /dev/null @@ -1,42 +0,0 @@ -extern crate test_support; - -#[test] -fn works() { - test_support::project() - .uglify(true) - .file("src/lib.rs", r#" - #![feature(proc_macro)] - - extern crate wasm_bindgen; - - use wasm_bindgen::prelude::*; - - wasm_bindgen! { - pub struct A {} - - impl A { - pub fn new() -> A { - A {} - } - } - pub fn clone(a: &JsObject) -> JsObject { - drop(a.clone()); - a.clone() - } - } - "#) - .file("test.ts", r#" - import * as assert from "assert"; - import { Exports, Imports } from "./out"; - - export const imports: Imports = {}; - - export function test(wasm: Exports) { - let sym = Symbol('a'); - assert.strictEqual(wasm.clone(sym), sym); - let a = wasm.A.new(); - a.free(); - } - "#) - .test(); -}