mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-22 12:42:14 +00:00
Merge pull request #179 from rustwasm/wasm2asm
Add an example of `wasm2asm` and wasm-bindgen
This commit is contained in:
commit
85740a2591
@ -43,6 +43,7 @@ members = [
|
|||||||
"examples/closures",
|
"examples/closures",
|
||||||
"examples/no_modules",
|
"examples/no_modules",
|
||||||
"examples/add",
|
"examples/add",
|
||||||
|
"examples/asm.js",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
@ -17,6 +17,7 @@ parity-wasm = "0.28"
|
|||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
tempfile = "3.0"
|
||||||
wasm-bindgen-shared = { path = "../shared", version = '=0.2.7' }
|
wasm-bindgen-shared = { path = "../shared", version = '=0.2.7' }
|
||||||
wasm-gc-api = "0.1"
|
wasm-gc-api = "0.1"
|
||||||
wasmi = "0.2"
|
wasmi = "0.2"
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
extern crate base64;
|
extern crate base64;
|
||||||
|
extern crate tempfile;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashSet, HashMap};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Write, Read};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
use parity_wasm::elements::*;
|
use parity_wasm::elements::*;
|
||||||
use failure::Error;
|
use failure::{Error, ResultExt};
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
base64: bool,
|
base64: bool,
|
||||||
|
wasm2asm: bool,
|
||||||
fetch_path: Option<String>,
|
fetch_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
module: Module,
|
module: Module,
|
||||||
base64: bool,
|
base64: bool,
|
||||||
|
wasm2asm: bool,
|
||||||
fetch_path: Option<String>,
|
fetch_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +26,7 @@ impl Config {
|
|||||||
pub fn new() -> Config {
|
pub fn new() -> Config {
|
||||||
Config {
|
Config {
|
||||||
base64: false,
|
base64: false,
|
||||||
|
wasm2asm: false,
|
||||||
fetch_path: None,
|
fetch_path: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,19 +36,25 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn wasm2asm(&mut self, wasm2asm: bool) -> &mut Self {
|
||||||
|
self.wasm2asm = wasm2asm;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fetch(&mut self, path: Option<String>) -> &mut Self {
|
pub fn fetch(&mut self, path: Option<String>) -> &mut Self {
|
||||||
self.fetch_path = path;
|
self.fetch_path = path;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate(&mut self, wasm: &[u8]) -> Result<Output, Error> {
|
pub fn generate(&mut self, wasm: &[u8]) -> Result<Output, Error> {
|
||||||
if !self.base64 && !self.fetch_path.is_some() {
|
if !self.base64 && !self.fetch_path.is_some() && !self.wasm2asm {
|
||||||
bail!("the option --base64 or --fetch is required");
|
bail!("one of --base64, --fetch, or --wasm2asm is required");
|
||||||
}
|
}
|
||||||
let module = deserialize_buffer(wasm)?;
|
let module = deserialize_buffer(wasm)?;
|
||||||
Ok(Output {
|
Ok(Output {
|
||||||
module,
|
module,
|
||||||
base64: self.base64,
|
base64: self.base64,
|
||||||
|
wasm2asm: self.wasm2asm,
|
||||||
fetch_path: self.fetch_path.clone(),
|
fetch_path: self.fetch_path.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -108,6 +121,9 @@ impl Output {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn js(self) -> Result<String, Error> {
|
pub fn js(self) -> Result<String, Error> {
|
||||||
|
if self.wasm2asm {
|
||||||
|
return self.js_wasm2asm();
|
||||||
|
}
|
||||||
let mut js_imports = String::new();
|
let mut js_imports = String::new();
|
||||||
let mut exports = String::new();
|
let mut exports = String::new();
|
||||||
let mut imports = String::new();
|
let mut imports = String::new();
|
||||||
@ -235,4 +251,190 @@ impl Output {
|
|||||||
mem_export = if export_mem { "export let memory;" } else { "" },
|
mem_export = if export_mem { "export let memory;" } else { "" },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn js_wasm2asm(self) -> Result<String, Error> {
|
||||||
|
let mut js_imports = String::new();
|
||||||
|
let mut imported_modules = Vec::new();
|
||||||
|
if let Some(i) = self.module.import_section() {
|
||||||
|
let mut module_set = HashSet::new();
|
||||||
|
let mut name_map = HashMap::new();
|
||||||
|
for entry in i.entries() {
|
||||||
|
match *entry.external() {
|
||||||
|
External::Function(_) => {}
|
||||||
|
External::Table(_) => {
|
||||||
|
bail!("wasm imports a table which isn't supported yet");
|
||||||
|
}
|
||||||
|
External::Memory(_) => {
|
||||||
|
bail!("wasm imports memory which isn't supported yet");
|
||||||
|
}
|
||||||
|
External::Global(_) => {
|
||||||
|
bail!("wasm imports globals which aren't supported yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = name_map.entry(entry.field()).or_insert(entry.module());
|
||||||
|
if *m != entry.module() {
|
||||||
|
bail!("the name `{}` is imported from two differnet \
|
||||||
|
modules which currently isn't supported in `wasm2asm` \
|
||||||
|
mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !module_set.insert(entry.module()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = (b'a' + (module_set.len() as u8)) as char;
|
||||||
|
js_imports.push_str(&format!("import * as import_{} from '{}';",
|
||||||
|
name,
|
||||||
|
entry.module()));
|
||||||
|
imported_modules.push(format!("import_{}", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut js_exports = String::new();
|
||||||
|
if let Some(i) = self.module.export_section() {
|
||||||
|
let mut export_mem = false;
|
||||||
|
for entry in i.entries() {
|
||||||
|
match *entry.internal() {
|
||||||
|
Internal::Function(_) => {}
|
||||||
|
Internal::Memory(_) => export_mem = true,
|
||||||
|
Internal::Table(_) => continue,
|
||||||
|
Internal::Global(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
js_exports.push_str(&format!("export const {0} = ret.{0};\n",
|
||||||
|
entry.field()));
|
||||||
|
}
|
||||||
|
if !export_mem {
|
||||||
|
bail!("the `wasm2asm` mode is currently only compatible with \
|
||||||
|
modules that export memory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let memory_size = self.module.memory_section()
|
||||||
|
.unwrap()
|
||||||
|
.entries()[0]
|
||||||
|
.limits()
|
||||||
|
.initial();
|
||||||
|
|
||||||
|
let mut js_init_mem = String::new();
|
||||||
|
if let Some(s) = self.module.data_section() {
|
||||||
|
for entry in s.entries() {
|
||||||
|
let offset = entry.offset().code();
|
||||||
|
if offset.len() != 2 {
|
||||||
|
bail!("don't recognize data offset {:?}", offset)
|
||||||
|
}
|
||||||
|
if offset[1] != Opcode::End {
|
||||||
|
bail!("don't recognize data offset {:?}", offset)
|
||||||
|
}
|
||||||
|
let offset = match offset[0] {
|
||||||
|
Opcode::I32Const(x) => x,
|
||||||
|
_ => bail!("don't recognize data offset {:?}", offset),
|
||||||
|
};
|
||||||
|
|
||||||
|
let base64 = base64::encode(entry.value());
|
||||||
|
js_init_mem.push_str(&format!("_assign({}, \"{}\");\n",
|
||||||
|
offset,
|
||||||
|
base64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let td = tempfile::tempdir()?;
|
||||||
|
let wasm = serialize(self.module)?;
|
||||||
|
let wasm_file = td.as_ref().join("foo.wasm");
|
||||||
|
File::create(&wasm_file)
|
||||||
|
.and_then(|mut f| f.write_all(&wasm))
|
||||||
|
.with_context(|_| {
|
||||||
|
format!("failed to write wasm to `{}`", wasm_file.display())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let wast_file = td.as_ref().join("foo.wast");
|
||||||
|
run(
|
||||||
|
Command::new("wasm-dis")
|
||||||
|
.arg(&wasm_file)
|
||||||
|
.arg("-o")
|
||||||
|
.arg(&wast_file),
|
||||||
|
"wasm-dis",
|
||||||
|
)?;
|
||||||
|
let js_file = td.as_ref().join("foo.js");
|
||||||
|
run(
|
||||||
|
Command::new("wasm2asm")
|
||||||
|
.arg(&wast_file)
|
||||||
|
.arg("-o")
|
||||||
|
.arg(&js_file),
|
||||||
|
"wasm2asm",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut asm_func = String::new();
|
||||||
|
File::open(&js_file)
|
||||||
|
.and_then(|mut f| f.read_to_string(&mut asm_func))
|
||||||
|
.with_context(|_| {
|
||||||
|
format!("failed to read `{}`", js_file.display())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
|
||||||
|
let mut imports = String::from("{}");
|
||||||
|
for m in imported_modules {
|
||||||
|
imports = format!("Object.assign({}, {})", imports, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(format!("\
|
||||||
|
{js_imports}
|
||||||
|
|
||||||
|
{asm_func}
|
||||||
|
|
||||||
|
const mem = new ArrayBuffer({mem_size});
|
||||||
|
const _mem = new Uint8Array(mem);
|
||||||
|
function _assign(offset, s) {{
|
||||||
|
if (typeof Buffer === 'undefined') {{
|
||||||
|
const bytes = atob(s);
|
||||||
|
for (let i = 0; i < bytes.length; i++)
|
||||||
|
_mem[offset + i] = bytes.charCodeAt(i);
|
||||||
|
}} else {{
|
||||||
|
const bytes = Buffer.from(s, 'base64');
|
||||||
|
for (let i = 0; i < bytes.length; i++)
|
||||||
|
_mem[offset + i] = bytes[i];
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
{js_init_mem}
|
||||||
|
const ret = asmFunc(self, {imports}, mem);
|
||||||
|
{js_exports}
|
||||||
|
",
|
||||||
|
js_imports = js_imports,
|
||||||
|
js_init_mem = js_init_mem,
|
||||||
|
asm_func = asm_func,
|
||||||
|
js_exports = js_exports,
|
||||||
|
imports = imports,
|
||||||
|
mem_size = memory_size * (1 << 16),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(cmd: &mut Command, program: &str) -> Result<(), Error> {
|
||||||
|
let output = cmd.output().with_context(|e| {
|
||||||
|
if e.kind() == io::ErrorKind::NotFound {
|
||||||
|
format!("failed to execute `{}`, is the tool installed \
|
||||||
|
from the binaryen project?\ncommand line: {:?}",
|
||||||
|
program,
|
||||||
|
cmd)
|
||||||
|
} else {
|
||||||
|
format!("failed to execute: {:?}", cmd)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
if output.status.success() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = format!("failed to execute: {:?}\nstatus: {}\n",
|
||||||
|
cmd,
|
||||||
|
output.status);
|
||||||
|
if !output.stdout.is_empty() {
|
||||||
|
s.push_str(&format!("----- stdout ------\n{}\n",
|
||||||
|
String::from_utf8_lossy(&output.stdout)));
|
||||||
|
}
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
s.push_str(&format!("----- stderr ------\n{}\n",
|
||||||
|
String::from_utf8_lossy(&output.stderr)));
|
||||||
|
}
|
||||||
|
bail!("{}", s)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ Options:
|
|||||||
--typescript Output a `*.d.ts` file next to the JS output
|
--typescript Output a `*.d.ts` file next to the JS output
|
||||||
--base64 Inline the wasm module using base64 encoding
|
--base64 Inline the wasm module using base64 encoding
|
||||||
--fetch PATH Load module by passing the PATH argument to `fetch()`
|
--fetch PATH Load module by passing the PATH argument to `fetch()`
|
||||||
|
--wasm2asm Convert wasm to asm.js and don't use `WebAssembly`
|
||||||
|
|
||||||
Note that this is not intended to produce a production-ready output module
|
Note that this is not intended to produce a production-ready output module
|
||||||
but rather is intended purely as a temporary \"hack\" until it's standard in
|
but rather is intended purely as a temporary \"hack\" until it's standard in
|
||||||
@ -38,6 +39,7 @@ struct Args {
|
|||||||
flag_output: Option<PathBuf>,
|
flag_output: Option<PathBuf>,
|
||||||
flag_typescript: bool,
|
flag_typescript: bool,
|
||||||
flag_base64: bool,
|
flag_base64: bool,
|
||||||
|
flag_wasm2asm: bool,
|
||||||
flag_fetch: Option<String>,
|
flag_fetch: Option<String>,
|
||||||
arg_input: PathBuf,
|
arg_input: PathBuf,
|
||||||
}
|
}
|
||||||
@ -58,10 +60,6 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rmain(args: &Args) -> Result<(), Error> {
|
fn rmain(args: &Args) -> Result<(), Error> {
|
||||||
if !args.flag_base64 && !args.flag_fetch.is_some() {
|
|
||||||
bail!("unfortunately only works right now with base64 or fetch");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut wasm = Vec::new();
|
let mut wasm = Vec::new();
|
||||||
File::open(&args.arg_input)
|
File::open(&args.arg_input)
|
||||||
.and_then(|mut f| f.read_to_end(&mut wasm))
|
.and_then(|mut f| f.read_to_end(&mut wasm))
|
||||||
@ -69,6 +67,7 @@ fn rmain(args: &Args) -> Result<(), Error> {
|
|||||||
|
|
||||||
let object = wasm_bindgen_cli_support::wasm2es6js::Config::new()
|
let object = wasm_bindgen_cli_support::wasm2es6js::Config::new()
|
||||||
.base64(args.flag_base64)
|
.base64(args.flag_base64)
|
||||||
|
.wasm2asm(args.flag_wasm2asm)
|
||||||
.fetch(args.flag_fetch.clone())
|
.fetch(args.flag_fetch.clone())
|
||||||
.generate(&wasm)?;
|
.generate(&wasm)?;
|
||||||
|
|
||||||
|
@ -31,3 +31,7 @@ The examples here are:
|
|||||||
the `wasm-bindgen` CLI tool
|
the `wasm-bindgen` CLI tool
|
||||||
* `add` - an example of generating a tiny wasm binary, one that only adds two
|
* `add` - an example of generating a tiny wasm binary, one that only adds two
|
||||||
numbers.
|
numbers.
|
||||||
|
* `asm.js` - an example of using the `wasm2asm` tool from [binaryen] to convert
|
||||||
|
the generated WebAssembly to normal JS
|
||||||
|
|
||||||
|
[binaryen]: https://github.com/WebAssembly/binaryen
|
||||||
|
2
examples/asm.js/.gitignore
vendored
Normal file
2
examples/asm.js/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
package-lock.json
|
||||||
|
asmjs*
|
14
examples/asm.js/Cargo.toml
Normal file
14
examples/asm.js/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "asmjs"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Here we're using a path dependency to use what's already in this repository,
|
||||||
|
# but you'd use the commented out version below if you're copying this into your
|
||||||
|
# project.
|
||||||
|
wasm-bindgen = { path = "../.." }
|
||||||
|
#wasm-bindgen = "0.2"
|
23
examples/asm.js/README.md
Normal file
23
examples/asm.js/README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# WebAssembly to asm.js
|
||||||
|
|
||||||
|
This directory is an example of using [binaryen]'s `wasm2asm` tool to convert
|
||||||
|
the wasm output of `wasm-bindgen` to a normal JS file that can be executed like
|
||||||
|
asm.js.
|
||||||
|
|
||||||
|
You can build the example locally with:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
When opened in a web browser this should print "Hello, World!" to the console.
|
||||||
|
|
||||||
|
This example uses the `wasm2es6js` tool to convert the wasm file to an ES module
|
||||||
|
that's implemented with asm.js instead of WebAssembly. The conversion to asm.js
|
||||||
|
is done by [binaryen]'s `wasm2asm` tool internally.
|
||||||
|
|
||||||
|
Note that the `wasm2asm` tool is still pretty early days so there's likely to be
|
||||||
|
a number of bugs to run into or work around. If any are encountered though
|
||||||
|
please feel free to report them upstream!
|
||||||
|
|
||||||
|
[binaryen]: https://github.com/WebAssembly/binaryen
|
25
examples/asm.js/build.sh
Executable file
25
examples/asm.js/build.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# Compile our wasm module
|
||||||
|
cargo +nightly build --target wasm32-unknown-unknown --release
|
||||||
|
|
||||||
|
# Run wasm-bindgen, and note that the `--no-demangle` argument is here is
|
||||||
|
# important for compatibility with wasm2asm!
|
||||||
|
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \
|
||||||
|
--bin wasm-bindgen -- \
|
||||||
|
--no-demangle \
|
||||||
|
../../target/wasm32-unknown-unknown/release/asmjs.wasm --out-dir .
|
||||||
|
|
||||||
|
# Run the `wasm2es6js` primarily with the `--wasm2asm` flag, which will
|
||||||
|
# internally execute `wasm2asm` as necessary
|
||||||
|
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \
|
||||||
|
--bin wasm2es6js -- \
|
||||||
|
asmjs_bg.wasm --wasm2asm -o asmjs_bg.js
|
||||||
|
|
||||||
|
# Move our original wasm out of the way to avoid cofusing Webpack.
|
||||||
|
mv asmjs_bg.wasm asmjs_bg.bak.wasm
|
||||||
|
|
||||||
|
npm install
|
||||||
|
npm run serve
|
9
examples/asm.js/index.html
Normal file
9
examples/asm.js/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Open up the developer console to see "Hello, World!"</p>
|
||||||
|
<script src='./index.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
examples/asm.js/index.js
Normal file
3
examples/asm.js/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { run } from './asmjs';
|
||||||
|
|
||||||
|
run();
|
10
examples/asm.js/package.json
Normal file
10
examples/asm.js/package.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"serve": "webpack-dev-server"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"webpack": "^4.0.1",
|
||||||
|
"webpack-cli": "^2.0.10",
|
||||||
|
"webpack-dev-server": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
16
examples/asm.js/src/lib.rs
Normal file
16
examples/asm.js/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
#[wasm_bindgen(js_namespace = console)]
|
||||||
|
fn log(s: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn run() {
|
||||||
|
log("Hello, World!");
|
||||||
|
}
|
10
examples/asm.js/webpack.config.js
Normal file
10
examples/asm.js/webpack.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./index.js",
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
filename: "index.js",
|
||||||
|
},
|
||||||
|
mode: "development"
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user