Skip to content

Commit 156e1cb

Browse files
authored
Removing duplicate closure wrappers in the JS glue (#2002)
* Removing duplicate closure wrappers in the JS glue * Fixing build error * Adding in explanatory comment
1 parent 673e9b7 commit 156e1cb

File tree

4 files changed

+120
-67
lines changed

4 files changed

+120
-67
lines changed

crates/cli-support/src/descriptor.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ tys! {
3939
CLAMPED
4040
}
4141

42-
#[derive(Debug, Clone)]
42+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4343
pub enum Descriptor {
4444
I8,
4545
U8,
@@ -69,14 +69,14 @@ pub enum Descriptor {
6969
Unit,
7070
}
7171

72-
#[derive(Debug, Clone)]
72+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7373
pub struct Function {
7474
pub arguments: Vec<Descriptor>,
7575
pub shim_idx: u32,
7676
pub ret: Descriptor,
7777
}
7878

79-
#[derive(Debug, Clone)]
79+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8080
pub struct Closure {
8181
pub shim_idx: u32,
8282
pub dtor_idx: u32,

crates/cli-support/src/descriptors.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use wasm_bindgen_wasm_interpreter::Interpreter;
2222
pub struct WasmBindgenDescriptorsSection {
2323
pub descriptors: HashMap<String, Descriptor>,
2424
pub closure_imports: HashMap<ImportId, Closure>,
25+
cached_closures: HashMap<Descriptor, FunctionId>,
2526
}
2627

2728
pub type WasmBindgenDescriptorsSectionId = TypedCustomSectionId<WasmBindgenDescriptorsSection>;
@@ -126,10 +127,20 @@ impl WasmBindgenDescriptorsSection {
126127
// ourselves, and then we're good to go.
127128
let ty = module.funcs.get(wbindgen_describe_closure).ty();
128129
for (func, descriptor) in func_to_descriptor {
129-
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
130-
let (id, import_id) =
131-
module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
132-
module.funcs.get_mut(id).name = Some(import_name);
130+
// This uses a cache so that if the same closure exists multiple times it will
131+
// deduplicate it so it only exists once.
132+
let id = match self.cached_closures.get(&descriptor) {
133+
Some(id) => *id,
134+
None => {
135+
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
136+
let (id, import_id) =
137+
module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
138+
module.funcs.get_mut(id).name = Some(import_name);
139+
self.closure_imports.insert(import_id, descriptor.clone().unwrap_closure());
140+
self.cached_closures.insert(descriptor, id);
141+
id
142+
},
143+
};
133144

134145
let local = match &mut module.funcs.get_mut(func).kind {
135146
walrus::FunctionKind::Local(l) => l,
@@ -144,8 +155,6 @@ impl WasmBindgenDescriptorsSection {
144155
local,
145156
entry,
146157
);
147-
self.closure_imports
148-
.insert(import_id, descriptor.unwrap_closure());
149158
}
150159
return Ok(());
151160

crates/cli-support/src/js/mod.rs

+101-58
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,86 @@ impl<'a> Context<'a> {
17241724
);
17251725
}
17261726

1727+
fn expose_make_mut_closure(&mut self) -> Result<(), Error> {
1728+
if !self.should_write_global("make_mut_closure") {
1729+
return Ok(());
1730+
}
1731+
1732+
let table = self.export_function_table()?;
1733+
1734+
// For mutable closures they can't be invoked recursively.
1735+
// To handle that we swap out the `this.a` pointer with zero
1736+
// while we invoke it. If we finish and the closure wasn't
1737+
// destroyed, then we put back the pointer so a future
1738+
// invocation can succeed.
1739+
self.global(&format!(
1740+
"
1741+
function makeMutClosure(arg0, arg1, dtor, f) {{
1742+
const state = {{ a: arg0, b: arg1, cnt: 1 }};
1743+
const real = (...args) => {{
1744+
// First up with a closure we increment the internal reference
1745+
// count. This ensures that the Rust closure environment won't
1746+
// be deallocated while we're invoking it.
1747+
state.cnt++;
1748+
const a = state.a;
1749+
state.a = 0;
1750+
try {{
1751+
return f(a, state.b, ...args);
1752+
}} finally {{
1753+
if (--state.cnt === 0) wasm.{}.get(dtor)(a, state.b);
1754+
else state.a = a;
1755+
}}
1756+
}};
1757+
real.original = state;
1758+
return real;
1759+
}}
1760+
",
1761+
table
1762+
));
1763+
1764+
Ok(())
1765+
}
1766+
1767+
fn expose_make_closure(&mut self) -> Result<(), Error> {
1768+
if !self.should_write_global("make_closure") {
1769+
return Ok(());
1770+
}
1771+
1772+
let table = self.export_function_table()?;
1773+
1774+
// For shared closures they can be invoked recursively so we
1775+
// just immediately pass through `this.a`. If we end up
1776+
// executing the destructor, however, we clear out the
1777+
// `this.a` pointer to prevent it being used again the
1778+
// future.
1779+
self.global(&format!(
1780+
"
1781+
function makeClosure(arg0, arg1, dtor, f) {{
1782+
const state = {{ a: arg0, b: arg1, cnt: 1 }};
1783+
const real = (...args) => {{
1784+
// First up with a closure we increment the internal reference
1785+
// count. This ensures that the Rust closure environment won't
1786+
// be deallocated while we're invoking it.
1787+
state.cnt++;
1788+
try {{
1789+
return f(state.a, state.b, ...args);
1790+
}} finally {{
1791+
if (--state.cnt === 0) {{
1792+
wasm.{}.get(dtor)(state.a, state.b);
1793+
state.a = 0;
1794+
}}
1795+
}}
1796+
}};
1797+
real.original = state;
1798+
return real;
1799+
}}
1800+
",
1801+
table
1802+
));
1803+
1804+
Ok(())
1805+
}
1806+
17271807
fn global(&mut self, s: &str) {
17281808
let s = s.trim();
17291809

@@ -2338,73 +2418,36 @@ impl<'a> Context<'a> {
23382418
dtor,
23392419
mutable,
23402420
adapter,
2341-
nargs,
2421+
nargs: _,
23422422
} => {
23432423
assert!(kind == AdapterJsImportKind::Normal);
23442424
assert!(!variadic);
23452425
assert_eq!(args.len(), 3);
2346-
let arg_names = (0..*nargs)
2347-
.map(|i| format!("arg{}", i))
2348-
.collect::<Vec<_>>()
2349-
.join(", ");
2350-
let mut js = format!("({}) => {{\n", arg_names);
2351-
// First up with a closure we increment the internal reference
2352-
// count. This ensures that the Rust closure environment won't
2353-
// be deallocated while we're invoking it.
2354-
js.push_str("state.cnt++;\n");
2355-
2356-
let table = self.export_function_table()?;
2357-
let dtor = format!("wasm.{}.get({})", table, dtor);
2426+
23582427
let call = self.adapter_name(*adapter);
23592428

23602429
if *mutable {
2361-
// For mutable closures they can't be invoked recursively.
2362-
// To handle that we swap out the `this.a` pointer with zero
2363-
// while we invoke it. If we finish and the closure wasn't
2364-
// destroyed, then we put back the pointer so a future
2365-
// invocation can succeed.
2366-
js.push_str("const a = state.a;\n");
2367-
js.push_str("state.a = 0;\n");
2368-
js.push_str("try {\n");
2369-
js.push_str(&format!("return {}(a, state.b, {});\n", call, arg_names));
2370-
js.push_str("} finally {\n");
2371-
js.push_str("if (--state.cnt === 0) ");
2372-
js.push_str(&dtor);
2373-
js.push_str("(a, state.b);\n");
2374-
js.push_str("else state.a = a;\n");
2375-
js.push_str("}\n");
2430+
self.expose_make_mut_closure()?;
2431+
2432+
Ok(format!(
2433+
"makeMutClosure({arg0}, {arg1}, {dtor}, {call})",
2434+
arg0 = &args[0],
2435+
arg1 = &args[1],
2436+
dtor = dtor,
2437+
call = call,
2438+
))
2439+
23762440
} else {
2377-
// For shared closures they can be invoked recursively so we
2378-
// just immediately pass through `this.a`. If we end up
2379-
// executing the destructor, however, we clear out the
2380-
// `this.a` pointer to prevent it being used again the
2381-
// future.
2382-
js.push_str("try {\n");
2383-
js.push_str(&format!(
2384-
"return {}(state.a, state.b, {});\n",
2385-
call, arg_names
2386-
));
2387-
js.push_str("} finally {\n");
2388-
js.push_str("if (--state.cnt === 0) {\n");
2389-
js.push_str(&dtor);
2390-
js.push_str("(state.a, state.b);\n");
2391-
js.push_str("state.a = 0;\n");
2392-
js.push_str("}\n");
2393-
js.push_str("}\n");
2441+
self.expose_make_closure()?;
2442+
2443+
Ok(format!(
2444+
"makeClosure({arg0}, {arg1}, {dtor}, {call})",
2445+
arg0 = &args[0],
2446+
arg1 = &args[1],
2447+
dtor = dtor,
2448+
call = call,
2449+
))
23942450
}
2395-
js.push_str("}\n");
2396-
2397-
prelude.push_str(&format!(
2398-
"
2399-
const state = {{ a: {arg0}, b: {arg1}, cnt: 1 }};
2400-
const real = {body};
2401-
real.original = state;
2402-
",
2403-
body = js,
2404-
arg0 = &args[0],
2405-
arg1 = &args[1],
2406-
));
2407-
Ok("real".to_string())
24082451
}
24092452

24102453
AuxImport::StructuralMethod(name) => {

crates/cli-support/src/wit/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ impl<'a> Context<'a> {
159159
let WasmBindgenDescriptorsSection {
160160
descriptors,
161161
closure_imports,
162+
..
162163
} = *custom;
163164
// Store all the executed descriptors in our own field so we have
164165
// access to them while processing programs.

0 commit comments

Comments
 (0)