1
0
mirror of https://github.com/fluencelabs/wasm-bindgen synced 2025-03-31 17:31:06 +00:00

Add an option to "uglify" imports

This commit adds an option to "uglify" the wasm module's imports/exports so
those which are controlled by bindgen are renamed to a shorter (probably one
letter) names. This'll hopefully help cut down on both the wasm size slightly
and also the generated JS as the glue we're talking to wasm over won't require
such large names all the time.
This commit is contained in:
Alex Crichton 2017-12-24 15:32:40 -08:00
parent 240d3cd1a1
commit bef908a9b1
6 changed files with 275 additions and 59 deletions
crates
test-support/src
wasm-bindgen-cli-support/src
wasm-bindgen-cli/src
tests

@ -15,6 +15,7 @@ thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst));
pub struct Project { pub struct Project {
files: Vec<(String, String)>, files: Vec<(String, String)>,
debug: bool, debug: bool,
uglify: bool,
} }
pub fn project() -> Project { pub fn project() -> Project {
@ -27,6 +28,7 @@ pub fn project() -> Project {
.read_to_string(&mut lockfile).unwrap(); .read_to_string(&mut lockfile).unwrap();
Project { Project {
debug: true, debug: true,
uglify: false,
files: vec![ files: vec![
("Cargo.toml".to_string(), format!(r#" ("Cargo.toml".to_string(), format!(r#"
[package] [package]
@ -125,6 +127,11 @@ impl Project {
self self
} }
pub fn uglify(&mut self, uglify: bool) -> &mut Project {
self.uglify = uglify;
self
}
pub fn test(&mut self) { pub fn test(&mut self) {
let root = root(); let root = root();
drop(fs::remove_dir_all(&root)); drop(fs::remove_dir_all(&root));
@ -159,6 +166,7 @@ impl Project {
.input_path(&out) .input_path(&out)
.nodejs(true) .nodejs(true)
.debug(self.debug) .debug(self.debug)
.uglify_wasm_names(self.uglify)
.generate() .generate()
.expect("failed to run bindgen"); .expect("failed to run bindgen");
obj.write_ts_to(root.join("out.ts")).expect("failed to write ts"); obj.write_ts_to(root.join("out.ts")).expect("failed to write ts");

@ -4,23 +4,28 @@ extern crate parity_wasm;
extern crate wasm_bindgen_shared as shared; extern crate wasm_bindgen_shared as shared;
extern crate serde_json; extern crate serde_json;
use std::path::{Path, PathBuf}; use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf};
use failure::{Error, ResultExt}; use failure::{Error, ResultExt};
use parity_wasm::elements::*; use parity_wasm::elements::*;
mod ts; mod ts;
mod mapped;
use mapped::Mapped;
pub struct Bindgen { pub struct Bindgen {
path: Option<PathBuf>, path: Option<PathBuf>,
nodejs: bool, nodejs: bool,
debug: bool, debug: bool,
uglify: bool,
} }
pub struct Object { pub struct Object {
module: Module, module: Mapped,
program: shared::Program, program: shared::Program,
nodejs: bool, nodejs: bool,
debug: bool, debug: bool,
@ -32,6 +37,7 @@ impl Bindgen {
path: None, path: None,
nodejs: false, nodejs: false,
debug: false, debug: false,
uglify: false,
} }
} }
@ -50,6 +56,11 @@ impl Bindgen {
self self
} }
pub fn uglify_wasm_names(&mut self, uglify: bool) -> &mut Bindgen {
self.uglify = uglify;
self
}
pub fn generate(&mut self) -> Result<Object, Error> { pub fn generate(&mut self) -> Result<Object, Error> {
let input = match self.path { let input = match self.path {
Some(ref path) => path, Some(ref path) => path,
@ -59,8 +70,16 @@ impl Bindgen {
format_err!("{:?}", e) format_err!("{:?}", e)
})?; })?;
let program = extract_program(&mut module); let program = extract_program(&mut module);
Ok(Object { let mut mapped = Mapped {
module, module,
imports: HashMap::new(),
exports: HashMap::new(),
};
if self.uglify {
mapped.uglify(&program);
}
Ok(Object {
module: mapped,
program, program,
nodejs: self.nodejs, nodejs: self.nodejs,
debug: self.debug, debug: self.debug,
@ -89,7 +108,7 @@ impl Object {
} }
fn _write_wasm_to(self, path: &Path) -> Result<(), Error> { fn _write_wasm_to(self, path: &Path) -> Result<(), Error> {
parity_wasm::serialize_to_file(path, self.module).map_err(|e| { parity_wasm::serialize_to_file(path, self.module.module).map_err(|e| {
format_err!("{:?}", e) format_err!("{:?}", e)
})?; })?;
Ok(()) Ok(())

@ -0,0 +1,118 @@
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;
}

@ -3,6 +3,8 @@ use std::collections::{HashSet, HashMap};
use shared; use shared;
use parity_wasm::elements::*; use parity_wasm::elements::*;
use super::Mapped;
#[derive(Default)] #[derive(Default)]
pub struct Js { pub struct Js {
globals: String, globals: String,
@ -18,27 +20,32 @@ impl Js {
pub fn generate_program(&mut self, pub fn generate_program(&mut self,
program: &shared::Program, program: &shared::Program,
_wasm: &Module) { m: &Mapped) {
for f in program.free_functions.iter() { for f in program.free_functions.iter() {
self.generate_free_function(f); self.generate_free_function(f, m);
} }
for s in program.structs.iter() { for s in program.structs.iter() {
self.generate_struct(s); self.generate_struct(s, m);
} }
} }
pub fn generate_free_function(&mut self, func: &shared::Function) { pub fn generate_free_function(&mut self,
func: &shared::Function,
m: &Mapped) {
let (js, ts) = self.generate_function("function", let (js, ts) = self.generate_function("function",
&func.name, &func.name,
&func.name, &func.name,
false, false,
&func.arguments, &func.arguments,
func.ret.as_ref()); func.ret.as_ref(),
m);
self.exports.push((func.name.clone(), js, ts)); self.exports.push((func.name.clone(), js, ts));
} }
pub fn generate_struct(&mut self, s: &shared::Struct) { pub fn generate_struct(&mut self,
s: &shared::Struct,
m: &Mapped) {
let mut dst = String::new(); let mut dst = String::new();
self.expose_wasm_exports(); self.expose_wasm_exports();
dst.push_str(&format!(" dst.push_str(&format!("
@ -63,7 +70,7 @@ impl Js {
this.ptr = 0; this.ptr = 0;
wasm_exports.{}(ptr); wasm_exports.{}(ptr);
}} }}
", s.free_function())); ", m.export_name(&s.free_function())));
self.wasm_exports_bound.insert(s.name.clone()); self.wasm_exports_bound.insert(s.name.clone());
@ -75,6 +82,7 @@ impl Js {
false, false,
&function.arguments, &function.arguments,
function.ret.as_ref(), function.ret.as_ref(),
m,
); );
dst.push_str(&js); dst.push_str(&js);
dst.push_str("\n"); dst.push_str("\n");
@ -87,6 +95,7 @@ impl Js {
true, true,
&method.function.arguments, &method.function.arguments,
method.function.ret.as_ref(), method.function.ret.as_ref(),
m,
); );
dst.push_str(&js); dst.push_str(&js);
dst.push_str("\n"); dst.push_str("\n");
@ -104,7 +113,8 @@ impl Js {
wasm_name: &str, wasm_name: &str,
is_method: bool, is_method: bool,
arguments: &[shared::Type], arguments: &[shared::Type],
ret: Option<&shared::Type>) -> (String, String) { ret: Option<&shared::Type>,
m: &Mapped) -> (String, String) {
let mut dst = format!("{}(", name); let mut dst = format!("{}(", name);
let mut passed_args = String::new(); let mut passed_args = String::new();
let mut arg_conversions = String::new(); let mut arg_conversions = String::new();
@ -151,7 +161,7 @@ impl Js {
shared::Type::BorrowedStr | shared::Type::BorrowedStr |
shared::Type::String => { shared::Type::String => {
dst.push_str("string"); dst.push_str("string");
self.expose_pass_string_to_wasm(); self.expose_pass_string_to_wasm(m);
arg_conversions.push_str(&format!("\ arg_conversions.push_str(&format!("\
const [ptr{i}, len{i}] = passStringToWasm({arg}); const [ptr{i}, len{i}] = passStringToWasm({arg});
", i = i, arg = name)); ", i = i, arg = name));
@ -160,8 +170,8 @@ impl Js {
if let shared::Type::BorrowedStr = *arg { if let shared::Type::BorrowedStr = *arg {
self.expose_wasm_exports(); self.expose_wasm_exports();
destructors.push_str(&format!("\n\ destructors.push_str(&format!("\n\
wasm_exports.__wbindgen_free(ptr{i}, len{i});\n\ wasm_exports.{free}(ptr{i}, len{i});\n\
", i = i)); ", i = i, free = m.export_name("__wbindgen_free")));
} }
} }
shared::Type::ByRef(ref s) | shared::Type::ByRef(ref s) |
@ -203,7 +213,7 @@ impl Js {
arg_conversions.push_str(&format!("\ arg_conversions.push_str(&format!("\
const idx{i} = addBorrowedObject({arg}); const idx{i} = addBorrowedObject({arg});
", i = i, arg = name)); ", i = i, arg = name));
destructors.push_str("popBorrowedObject();\n"); destructors.push_str("stack.pop();\n");
pass(&format!("idx{}", i)); pass(&format!("idx{}", i));
} }
} }
@ -248,12 +258,16 @@ impl Js {
self.expose_get_string_from_wasm(); self.expose_get_string_from_wasm();
self.expose_wasm_exports(); self.expose_wasm_exports();
format!(" format!("
const ptr = wasm_exports.__wbindgen_boxed_str_ptr(ret); const ptr = wasm_exports.{}(ret);
const len = wasm_exports.__wbindgen_boxed_str_len(ret); const len = wasm_exports.{}(ret);
const realRet = getStringFromWasm(ptr, len); const realRet = getStringFromWasm(ptr, len);
wasm_exports.__wbindgen_boxed_str_free(ret); wasm_exports.{}(ret);
return realRet; return realRet;
") ",
m.export_name("__wbindgen_boxed_str_ptr"),
m.export_name("__wbindgen_boxed_str_len"),
m.export_name("__wbindgen_boxed_str_free"),
)
} }
}; };
let mut dst_ts = dst.clone(); let mut dst_ts = dst.clone();
@ -265,7 +279,11 @@ impl Js {
dst.push_str(&format!("\ dst.push_str(&format!("\
const ret = wasm_exports.{f}({passed}); const ret = wasm_exports.{f}({passed});
{convert_ret} {convert_ret}
", f = wasm_name, passed = passed_args, convert_ret = convert_ret)); ",
f = m.export_name(wasm_name),
passed = passed_args,
convert_ret = convert_ret,
));
} else { } else {
dst.push_str(&format!("\ dst.push_str(&format!("\
try {{ try {{
@ -274,8 +292,12 @@ impl Js {
}} finally {{ }} finally {{
{destructors} {destructors}
}} }}
", f = wasm_name, passed = passed_args, destructors = destructors, ",
convert_ret = convert_ret)); f = m.export_name(wasm_name),
passed = passed_args,
destructors = destructors,
convert_ret = convert_ret,
));
} }
dst.push_str("}"); dst.push_str("}");
self.wasm_exports_bound.insert(wasm_name.to_string()); self.wasm_exports_bound.insert(wasm_name.to_string());
@ -374,7 +396,7 @@ impl Js {
(dst, ts_dst) (dst, ts_dst)
} }
pub fn to_string(&mut self, m: &Module, program: &shared::Program) -> String { pub fn to_string(&mut self, m: &Mapped, program: &shared::Program) -> String {
if self.debug { if self.debug {
self.expose_global_slab(); self.expose_global_slab();
self.expose_global_stack(); self.expose_global_stack();
@ -398,7 +420,7 @@ impl Js {
self.globals.push_str(class); self.globals.push_str(class);
self.globals.push_str("\n"); self.globals.push_str("\n");
} }
let wasm_exports = self.typescript_wasm_exports(m); let wasm_exports = self.typescript_wasm_exports(&m.module);
let mut exports_interface = String::new(); let mut exports_interface = String::new();
let mut extra_exports_interface = String::new(); let mut extra_exports_interface = String::new();
let mut exports = format!("\ let mut exports = format!("\
@ -419,14 +441,16 @@ impl Js {
// well. // well.
for (export, typescript) in wasm_exports.iter() { for (export, typescript) in wasm_exports.iter() {
// ignore any internal functions we have for ourselves // ignore any internal functions we have for ourselves
if export.starts_with("__wbindgen") { let orig_export = m.orig_export_name(export);
if orig_export.starts_with("__wbindgen") {
continue continue
} }
// Ignore anything we just bound above, // Ignore anything we just bound above,
if self.wasm_exports_bound.contains(export) { if self.wasm_exports_bound.contains(orig_export) {
continue continue
} }
assert_eq!(orig_export, export);
if extra_exports_interface.len() == 0 { if extra_exports_interface.len() == 0 {
extra_exports_interface.push_str("export interface ExtraExports {\n"); extra_exports_interface.push_str("export interface ExtraExports {\n");
exports_interface.push_str("extra: ExtraExports;\n"); exports_interface.push_str("extra: ExtraExports;\n");
@ -445,7 +469,7 @@ impl Js {
exports.push_str("},\n"); exports.push_str("},\n");
} }
exports.push_str("}"); exports.push_str("}");
let wasm_imports = self.typescript_wasm_imports(m); let wasm_imports = self.typescript_wasm_imports(&m.module);
let mut imports_object = String::new(); let mut imports_object = String::new();
let mut extra_imports_interface = String::new(); let mut extra_imports_interface = String::new();
@ -456,12 +480,13 @@ impl Js {
// the wasm module, an optimization pass at some point may have // the wasm module, an optimization pass at some point may have
// ended up removing the code that needed the import, removing the // ended up removing the code that needed the import, removing the
// import. // import.
if !wasm_imports.contains_key(&import.name) { let name = m.import_name(&import.name);
if !wasm_imports.contains_key(name) {
continue continue
} }
imports_bound.insert(import.name.clone()); imports_bound.insert(name.to_string());
let (val, ts) = self.generate_import(import); let (val, ts) = self.generate_import(import);
imports_object.push_str(&import.name); imports_object.push_str(&name);
imports_object.push_str(":"); imports_object.push_str(":");
imports_object.push_str(&val); imports_object.push_str(&val);
imports_object.push_str(",\n"); imports_object.push_str(",\n");
@ -474,7 +499,8 @@ impl Js {
// well. // well.
for (import, typescript) in wasm_imports.iter() { for (import, typescript) in wasm_imports.iter() {
// ignore any internal functions we have for ourselves // ignore any internal functions we have for ourselves
if import.starts_with("__wbindgen") { let orig_import = m.orig_import_name(import);
if orig_import.starts_with("__wbindgen") {
continue continue
} }
// Ignore anything we just bound above, // Ignore anything we just bound above,
@ -482,6 +508,7 @@ impl Js {
continue continue
} }
assert_eq!(orig_import, import);
if extra_imports_interface.len() == 0 { if extra_imports_interface.len() == 0 {
extra_imports_interface.push_str("export interface ExtraImports {\n"); extra_imports_interface.push_str("export interface ExtraImports {\n");
imports_interface.push_str("env: ExtraImports;\n"); imports_interface.push_str("env: ExtraImports;\n");
@ -511,7 +538,7 @@ impl Js {
String::from("(val as {cnt:number}).cnt += 1;") String::from("(val as {cnt:number}).cnt += 1;")
}; };
imports_object.push_str(&format!(" imports_object.push_str(&format!("
__wbindgen_object_clone_ref: function(idx: number): number {{ {}: function(idx: number): number {{
// If this object is on the stack promote it to the heap. // If this object is on the stack promote it to the heap.
if ((idx & 1) === 1) if ((idx & 1) === 1)
return addHeapObject(getObject(idx)); return addHeapObject(getObject(idx));
@ -522,19 +549,24 @@ impl Js {
{} {}
return idx; return idx;
}}, }},
", bump_cnt)); ", m.import_name("__wbindgen_object_clone_ref"), bump_cnt));
} }
if self.wasm_import_needed("__wbindgen_object_drop_ref", m) { if self.wasm_import_needed("__wbindgen_object_drop_ref", m) {
self.expose_drop_ref(); self.expose_drop_ref();
imports_object.push_str("__wbindgen_object_drop_ref: dropRef,\n"); let name = m.import_name("__wbindgen_object_drop_ref");
imports_object.push_str(&format!("{}: dropRef,\n", name));
} }
if self.wasm_import_needed("__wbindgen_throw", m) { if self.wasm_import_needed("__wbindgen_throw", m) {
self.expose_get_string_from_wasm(); self.expose_get_string_from_wasm();
imports_object.push_str("__wbindgen_throw: function(ptr: number, len: number) { imports_object.push_str(&format!("\
throw new Error(getStringFromWasm(ptr, len)); {}: function(ptr: number, len: number) {{
},\n"); throw new Error(getStringFromWasm(ptr, len));
}},
",
m.import_name("__wbindgen_throw"),
));
} }
let mut writes = String::new(); let mut writes = String::new();
@ -608,14 +640,14 @@ impl Js {
) )
} }
fn wasm_import_needed(&self, name: &str, m: &Module) -> bool { fn wasm_import_needed(&self, name: &str, m: &Mapped) -> bool {
let imports = match m.import_section() { let imports = match m.module.import_section() {
Some(s) => s, Some(s) => s,
None => return false, None => return false,
}; };
imports.entries().iter().any(|i| { imports.entries().iter().any(|i| {
i.module() == "env" && i.field() == name i.module() == "env" && i.field() == m.import_name(name)
}) })
} }
@ -883,37 +915,37 @@ impl Js {
"); ");
} }
fn expose_pass_string_to_wasm(&mut self) { fn expose_pass_string_to_wasm(&mut self, m: &Mapped) {
if !self.exposed_globals.insert("pass_string_to_wasm") { if !self.exposed_globals.insert("pass_string_to_wasm") {
return return
} }
self.expose_wasm_exports(); self.expose_wasm_exports();
self.expose_global_memory(); self.expose_global_memory();
if self.nodejs { if self.nodejs {
self.globals.push_str(" self.globals.push_str(&format!("
function passStringToWasm(arg: string): [number, number] { function passStringToWasm(arg: string): [number, number] {{
if (typeof(arg) !== 'string') if (typeof(arg) !== 'string')
throw new Error('expected a string argument'); throw new Error('expected a string argument');
const buf = Buffer.from(arg); const buf = Buffer.from(arg);
const len = buf.length; const len = buf.length;
const ptr = wasm_exports.__wbindgen_malloc(len); const ptr = wasm_exports.{}(len);
buf.copy(Buffer.from(memory.buffer), ptr); buf.copy(Buffer.from(memory.buffer), ptr);
return [ptr, len]; return [ptr, len];
} }}
"); ", m.export_name("__wbindgen_malloc")));
} else { } else {
self.globals.push_str(" self.globals.push_str(&format!("
function passStringToWasm(arg: string): [number, number] { function passStringToWasm(arg: string): [number, number] {{
if (typeof(arg) !== 'string') if (typeof(arg) !== 'string')
throw new Error('expected a string argument'); throw new Error('expected a string argument');
const buf = new TextEncoder('utf-8').encode(arg); const buf = new TextEncoder('utf-8').encode(arg);
const len = buf.length; const len = buf.length;
const ptr = wasm_exports.__wbindgen_malloc(len); const ptr = wasm_exports.{}(len);
let array = new Uint8Array(memory.buffer); let array = new Uint8Array(memory.buffer);
array.set(buf, ptr); array.set(buf, ptr);
return [ptr, len]; return [ptr, len];
} }}
"); ", m.export_name("__wbindgen_malloc")));
} }
} }
@ -966,10 +998,6 @@ impl Js {
stack.push(obj); stack.push(obj);
return ((stack.length - 1) << 1) | 1; return ((stack.length - 1) << 1) | 1;
} }
function popBorrowedObject(): void {
stack.pop();
}
"); ");
} }

@ -37,9 +37,10 @@ fn main() {
.unwrap_or_else(|e| e.exit()); .unwrap_or_else(|e| e.exit());
let mut b = Bindgen::new(); let mut b = Bindgen::new();
b.input_path(&args.arg_input); b.input_path(&args.arg_input)
b.nodejs(args.flag_nodejs); .nodejs(args.flag_nodejs)
b.debug(args.flag_debug); .debug(args.flag_debug)
.uglify_wasm_names(!args.flag_debug);
let ret = b.generate().expect("failed to generate bindings"); let ret = b.generate().expect("failed to generate bindings");
if let Some(ref ts) = args.flag_output_ts { if let Some(ref ts) = args.flag_output_ts {
ret.write_ts_to(ts).expect("failed to write TypeScript output file"); ret.write_ts_to(ts).expect("failed to write TypeScript output file");

42
tests/uglify.rs Normal file

@ -0,0 +1,42 @@
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();
}