mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-25 06:02:13 +00:00
Add an example of wasm2asm
and wasm-bindgen
This commit adds an example of executing the `wasm2asm` tool to generate asm.js output instead of WebAssembly. This is often useful when supporting older browsers, such as IE 11, that doesn't have native support for WebAssembly.
This commit is contained in:
parent
6f95e5c531
commit
dadcff15ef
@ -43,6 +43,7 @@ members = [
|
||||
"examples/closures",
|
||||
"examples/no_modules",
|
||||
"examples/add",
|
||||
"examples/asm.js",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
@ -17,6 +17,7 @@ parity-wasm = "0.28"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
tempfile = "3.0"
|
||||
wasm-bindgen-shared = { path = "../shared", version = '=0.2.7' }
|
||||
wasm-gc-api = "0.1"
|
||||
wasmi = "0.2"
|
||||
|
@ -1,18 +1,24 @@
|
||||
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 failure::Error;
|
||||
use failure::{Error, ResultExt};
|
||||
|
||||
pub struct Config {
|
||||
base64: bool,
|
||||
wasm2asm: bool,
|
||||
fetch_path: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Output {
|
||||
module: Module,
|
||||
base64: bool,
|
||||
wasm2asm: bool,
|
||||
fetch_path: Option<String>,
|
||||
}
|
||||
|
||||
@ -20,6 +26,7 @@ impl Config {
|
||||
pub fn new() -> Config {
|
||||
Config {
|
||||
base64: false,
|
||||
wasm2asm: false,
|
||||
fetch_path: None,
|
||||
}
|
||||
}
|
||||
@ -29,19 +36,25 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn wasm2asm(&mut self, wasm2asm: bool) -> &mut Self {
|
||||
self.wasm2asm = wasm2asm;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fetch(&mut self, path: Option<String>) -> &mut Self {
|
||||
self.fetch_path = path;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn generate(&mut self, wasm: &[u8]) -> Result<Output, Error> {
|
||||
if !self.base64 && !self.fetch_path.is_some() {
|
||||
bail!("the option --base64 or --fetch is required");
|
||||
if !self.base64 && !self.fetch_path.is_some() && !self.wasm2asm {
|
||||
bail!("one of --base64, --fetch, or --wasm2asm is required");
|
||||
}
|
||||
let module = deserialize_buffer(wasm)?;
|
||||
Ok(Output {
|
||||
module,
|
||||
base64: self.base64,
|
||||
wasm2asm: self.wasm2asm,
|
||||
fetch_path: self.fetch_path.clone(),
|
||||
})
|
||||
}
|
||||
@ -108,6 +121,9 @@ impl Output {
|
||||
}
|
||||
|
||||
pub fn js(self) -> Result<String, Error> {
|
||||
if self.wasm2asm {
|
||||
return self.js_wasm2asm();
|
||||
}
|
||||
let mut js_imports = String::new();
|
||||
let mut exports = String::new();
|
||||
let mut imports = String::new();
|
||||
@ -235,4 +251,190 @@ impl Output {
|
||||
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
|
||||
--base64 Inline the wasm module using base64 encoding
|
||||
--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
|
||||
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_typescript: bool,
|
||||
flag_base64: bool,
|
||||
flag_wasm2asm: bool,
|
||||
flag_fetch: Option<String>,
|
||||
arg_input: PathBuf,
|
||||
}
|
||||
@ -58,10 +60,6 @@ fn main() {
|
||||
}
|
||||
|
||||
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();
|
||||
File::open(&args.arg_input)
|
||||
.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()
|
||||
.base64(args.flag_base64)
|
||||
.wasm2asm(args.flag_wasm2asm)
|
||||
.fetch(args.flag_fetch.clone())
|
||||
.generate(&wasm)?;
|
||||
|
||||
|
@ -31,3 +31,7 @@ The examples here are:
|
||||
the `wasm-bindgen` CLI tool
|
||||
* `add` - an example of generating a tiny wasm binary, one that only adds two
|
||||
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