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

View File

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

View File

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

View File

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

View File

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

View File

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

42
tests/uglify.rs Normal file
View 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();
}