mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-13 13:01:22 +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:
@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user