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(); -}