diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 220f6e70..4e4b5ca6 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -490,6 +490,39 @@ impl<'a> Context<'a> { let mut dst = format!("class {} {{\n", name); let mut ts_dst = format!("export {}", dst); + let (mkweakref, freeref) = if self.config.weak_refs { + // When weak refs are enabled we use them to automatically free the + // contents of an exported rust class when it's gc'd. Note that a + // manual `free` function still exists for deterministic + // destruction. + // + // This is implemented by using a `WeakRefGroup` to run finalizers + // for all `WeakRef` objects that it creates. Upon construction of + // a new wasm object we use `makeRef` with "holdings" of a thunk to + // free the wasm instance. Once the `this` (the instance we're + // creating) is gc'd then the finalizer will run with the + // `WeakRef`, and we'll pull out the `holdings`, our pointer. + // + // Note, though, that if manual finalization happens we want to + // cancel the `WeakRef`-generated finalization, so we retain the + // `WeakRef` in a global map. This global map is then used to + // `drop()` the `WeakRef` (cancel finalization) whenever it is + // finalized. + self.expose_cleanup_groups(); + let mk = format!(" + const cleanup_ptr = this.ptr; + const ref = CLEANUPS.makeRef(this, () => free{}(cleanup_ptr)); + CLEANUPS_MAP.set(this.ptr, ref); + ", name); + let free = " + CLEANUPS_MAP.get(ptr).drop(); + CLEANUPS_MAP.delete(ptr); + "; + (mk, free) + } else { + (String::new(), "") + }; + if self.config.debug || class.constructor.is_some() { self.expose_constructor_token(); @@ -516,9 +549,11 @@ impl<'a> Context<'a> { // This invocation of new will call this constructor with a ConstructorToken let instance = {class}.{constructor}(...args); this.ptr = instance.ptr; + {mkweakref} ", class = name, - constructor = constructor + constructor = constructor, + mkweakref = mkweakref, )); } else { dst.push_str( @@ -537,9 +572,11 @@ impl<'a> Context<'a> { constructor(ptr) {{ this.ptr = ptr; + {} }} ", - name + name, + mkweakref, )); } @@ -599,16 +636,29 @@ impl<'a> Context<'a> { } } - dst.push_str(&format!( + self.global(&format!( " - free() {{ - const ptr = this.ptr; - this.ptr = 0; + function free{}(ptr) {{ + {} wasm.{}(ptr); }} ", + name, + freeref, shared::free_function(&name) )); + dst.push_str(&format!( + " + free() {{ + if (this.ptr === 0) + return; + const ptr = this.ptr; + this.ptr = 0; + free{}(this.ptr); + }} + ", + name, + )); ts_dst.push_str("free(): void;\n"); dst.push_str(&class.contents); ts_dst.push_str(&class.typescript); @@ -1594,6 +1644,18 @@ impl<'a> Context<'a> { "); } + fn expose_cleanup_groups(&mut self) { + if !self.exposed_globals.insert("cleanup_groups") { + return + } + self.global( + " + const CLEANUPS = new WeakRefGroup(x => x.holdings()); + const CLEANUPS_MAP = new Map(); + " + ); + } + fn gc(&mut self) -> Result<(), Error> { let module = mem::replace(self.module, Module::default()); let module = module.parse_names().unwrap_or_else(|p| p.1); diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 5933a691..59f4eb85 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -10,6 +10,7 @@ extern crate failure; use std::any::Any; use std::collections::BTreeSet; +use std::env; use std::fmt; use std::fs; use std::mem; @@ -33,6 +34,9 @@ pub struct Bindgen { typescript: bool, demangle: bool, keep_debug: bool, + // Experimental support for `WeakRefGroup`, an upcoming ECMAScript feature. + // Currently only enable-able through an env var. + weak_refs: bool, } enum Input { @@ -55,6 +59,7 @@ impl Bindgen { typescript: false, demangle: true, keep_debug: false, + weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(), } }