Rewrite wasm-bindgen with ES6 modules in mind

This commit is a mostly-rewrite of the `wasm-bindgen` tool. After some recent
discussions it's clear that the previous model wasn't quite going to cut it, and
this iteration is one which primarily embraces ES6 modules and the idea that
this is a polyfill for host bindings.

The overall interface and functionality hasn't changed much but the underlying
technology has now changed significantly. Previously `wasm-bindgen` would emit a
JS file that acted as an ES6 module but had a bit of a wonky interface. It
exposed an async function for instantiation of the wasm module, but that's the
bundler's job, not ours!

Instead this iteration views each input and output as a discrete ES6 module. The
input wasm file is interpreted as "this *should* be an ES6 module with rich
types" and the output is "well here's some ES6 modules that fulfill that
contract". Notably the tool now replaces the original wasm ES6 module with a JS
ES6 module that has the "rich interface". Additionally a second ES6 module is
emitted (the actual wasm file) which imports and exports to the original ES6
module.

This strategy is hoped to be much more amenable to bundlers and controlling how
the wasm itself is instantiated. The emitted files files purely assume ES6
modules and should be able to work as-is once ES6 module integration for wasm is
completed.

Note that there aren't a ton of tools to pretend a wasm module is an ES6 module
at the moment but those should be coming soon! In the meantime a local
`wasm2es6js` hack was added to help make *something* work today. The README has
also been updated with instructions for interacting with this model.
This commit is contained in:
Alex Crichton 2018-01-29 21:20:38 -08:00
parent f27e4a9e94
commit c51a342cb3
22 changed files with 1514 additions and 1946 deletions

198
README.md
View File

@ -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
<html>
@ -135,27 +162,22 @@ Ok let's see what this look like on the web!
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<script type='module'>
import { instantiate } from "./hello.js";
// Send an async request to fetch the wasm file and get all its contents
fetch("hello.wasm")
.then(resp => resp.arrayBuffer())
// Invoke the wasm-bindgen-generated function `instantiate` which will
// give us a compiled wasm module when it's resolved
.then(wasm => instantiate(wasm))
// Using the module, call our Rust-exported function `greet` and then
// use `alert` to display it
.then(mod => {
alert(mod.greet("world"));
});
</script>
<script src='./index.js'></script>
</body>
</html>
```
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
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<script type='module'>
import { instantiate } from "./hello.js";
function assertEq(a, b) {
if (a !== b)
throw new Error(`${a} != ${b}`);
console.log(`found ${a} === ${b}`);
}
import { Foo, Bar, concat } from "./js_hello_world";
import { booted } from "./js_hello_world_wasm";
fetch("hello.wasm")
.then(resp => resp.arrayBuffer())
.then(bytes => {
return instantiate(bytes, {
bar_on_reset(s, token) {
console.log(token);
console.log(`this instance of bar was reset to ${s}`);
},
});
})
.then(mod => {
assertEq(mod.concat('a', 'b'), 'ab');
export function bar_on_reset(s, token) {
console.log(token);
console.log(`this instance of bar was reset to ${s}`);
}
// Note the `new Foo()` syntax cannot be used, static function
// constructors must be used instead. Additionally objects allocated
// corresponding to Rust structs will need to be deallocated on the
// Rust side of things with an explicit call to `free`.
let foo = mod.Foo.new();
assertEq(foo.add(10), 10);
foo.free();
function assertEq(a, b) {
if (a !== b)
throw new Error(`${a} != ${b}`);
console.log(`found ${a} === ${b}`);
}
// Pass objects to one another
let foo1 = mod.Foo.new();
let bar = mod.Bar.from_str("22", { opaque: 'object' });
foo1.add_other(bar);
function main() {
assertEq(concat('a', 'b'), 'ab');
// 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);
// Note the `new Foo()` syntax cannot be used, static function
// constructors must be used instead. Additionally objects allocated
// corresponding to Rust structs will need to be deallocated on the
// Rust side of things with an explicit call to `free`.
let foo = Foo.new();
assertEq(foo.add(10), 10);
foo.free();
assertEq(foo1.add(2), 22 + 34 + 2);
foo1.free();
// Pass objects to one another
let foo1 = Foo.new();
let bar = Bar.from_str("22", { opaque: 'object' });
foo1.add_other(bar);
alert('all passed!')
});
</script>
</body>
</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

View File

@ -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");
}

View File

@ -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"

View File

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

View File

@ -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<PathBuf>,
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<Object, Error> {
pub fn generate<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(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 {

View File

@ -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<String, String>,
pub exports: HashMap<String, String>,
}
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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<Output, Error> {
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<boolean>;");
}
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 { "" },
)
}
}

View File

@ -5,10 +5,15 @@ authors = ["Alex Crichton <alex@alexcrichton.com>"]
[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"

View File

@ -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<PathBuf>,
flag_output_ts: Option<PathBuf>,
flag_output_wasm: Option<PathBuf>,
flag_nodejs: bool,
flag_typescript: bool,
flag_out_dir: Option<PathBuf>,
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");
}

View File

@ -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] <input>
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<PathBuf>,
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);
}
}
}

View File

@ -15,6 +15,7 @@ pub struct Function {
}
pub struct Import {
pub module: String,
pub function: Function,
pub decl: Box<syn::FnDecl>,
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(),
}
}
}

View File

@ -5,7 +5,7 @@ extern crate serde_derive;
pub struct Program {
pub structs: Vec<Struct>,
pub free_functions: Vec<Function>,
pub imports: Vec<Function>,
pub imports: Vec<(String, Function)>,
}
#[derive(Serialize, Deserialize)]

View File

@ -338,9 +338,7 @@ pub mod __rt {
}
pub fn into_inner(self) -> T {
unsafe {
self.value.into_inner()
}
self.value.into_inner()
}
}

View File

@ -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');

View File

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

View File

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

View File

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

View File

@ -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);
}
"#)

View File

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

View File

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

View File

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