From 80ae00b9b2fe942af44e4f9500d52735b59e8383 Mon Sep 17 00:00:00 2001 From: Nicholas Roberts Date: Wed, 18 Sep 2024 19:44:22 +1000 Subject: [PATCH 1/4] Generate Symbol.dispose entries for exposed structs (and define Symbol.dispose if it doesn't exist) --- crates/cli-support/src/js/mod.rs | 17 +++++++++++++++++ crates/cli/tests/reference/builder.d.ts | 1 + crates/cli/tests/reference/builder.js | 6 ++++++ crates/cli/tests/reference/constructor.d.ts | 1 + crates/cli/tests/reference/constructor.js | 6 ++++++ crates/cli/tests/reference/raw.d.ts | 1 + crates/cli/tests/reference/raw.js | 6 ++++++ 7 files changed, 38 insertions(+) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 87042e608fb..579254d7030 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1080,10 +1080,15 @@ impl<'a> Context<'a> { const ptr = this.__destroy_into_raw(); wasm.{}(ptr, 0); }} + + [Symbol.dispose]() {{ + this.free(); + }} ", wasm_bindgen_shared::free_function(name), )); ts_dst.push_str(" free(): void;\n"); + ts_dst.push_str(" [Symbol.dispose](): void;\n"); dst.push_str(&class.contents); ts_dst.push_str(&class.typescript); @@ -1486,6 +1491,14 @@ impl<'a> Context<'a> { Ok(ret) } + fn expose_symbol_dispose(&mut self) -> Result<(), Error> { + if !self.should_write_global("symbol_dispose") { + return Ok(()); + } + self.global(&"if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); }"); + Ok(()) + } + fn expose_text_encoder(&mut self) -> Result<(), Error> { if !self.should_write_global("text_encoder") { return Ok(()); @@ -2476,6 +2489,10 @@ impl<'a> Context<'a> { pub fn generate(&mut self) -> Result<(), Error> { self.prestore_global_import_identifiers()?; + // conditionally override Symbol.dispose + if !self.aux.structs.is_empty() { + self.expose_symbol_dispose()?; + } for (id, adapter) in crate::sorted_iter(&self.wit.adapters) { let instrs = match &adapter.kind { AdapterKind::Import { .. } => continue, diff --git a/crates/cli/tests/reference/builder.d.ts b/crates/cli/tests/reference/builder.d.ts index ede348c9632..097d4bf91a8 100644 --- a/crates/cli/tests/reference/builder.d.ts +++ b/crates/cli/tests/reference/builder.d.ts @@ -4,6 +4,7 @@ */ export class ClassBuilder { free(): void; + [Symbol.dispose](): void; /** * @returns {ClassBuilder} */ diff --git a/crates/cli/tests/reference/builder.js b/crates/cli/tests/reference/builder.js index b491793222a..c2257811b9c 100644 --- a/crates/cli/tests/reference/builder.js +++ b/crates/cli/tests/reference/builder.js @@ -4,6 +4,8 @@ export function __wbg_set_wasm(val) { } +if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); } + const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); @@ -50,6 +52,10 @@ export class ClassBuilder { const ptr = this.__destroy_into_raw(); wasm.__wbg_classbuilder_free(ptr, 0); } + + [Symbol.dispose]() { + this.free(); + } /** * @returns {ClassBuilder} */ diff --git a/crates/cli/tests/reference/constructor.d.ts b/crates/cli/tests/reference/constructor.d.ts index f35e8c72fa1..68166aebf00 100644 --- a/crates/cli/tests/reference/constructor.d.ts +++ b/crates/cli/tests/reference/constructor.d.ts @@ -4,6 +4,7 @@ */ export class ClassConstructor { free(): void; + [Symbol.dispose](): void; /** */ constructor(); diff --git a/crates/cli/tests/reference/constructor.js b/crates/cli/tests/reference/constructor.js index ffac02b997a..53d2aeef8b5 100644 --- a/crates/cli/tests/reference/constructor.js +++ b/crates/cli/tests/reference/constructor.js @@ -4,6 +4,8 @@ export function __wbg_set_wasm(val) { } +if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); } + const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); @@ -42,6 +44,10 @@ export class ClassConstructor { const ptr = this.__destroy_into_raw(); wasm.__wbg_classconstructor_free(ptr, 0); } + + [Symbol.dispose]() { + this.free(); + } /** */ constructor() { diff --git a/crates/cli/tests/reference/raw.d.ts b/crates/cli/tests/reference/raw.d.ts index 944af97ab04..f856da7ad88 100644 --- a/crates/cli/tests/reference/raw.d.ts +++ b/crates/cli/tests/reference/raw.d.ts @@ -9,6 +9,7 @@ export function test1(test: number): number; */ export class Test { free(): void; + [Symbol.dispose](): void; /** * @param {number} test * @returns {Test} diff --git a/crates/cli/tests/reference/raw.js b/crates/cli/tests/reference/raw.js index 3432d150366..160c52ee79d 100644 --- a/crates/cli/tests/reference/raw.js +++ b/crates/cli/tests/reference/raw.js @@ -26,6 +26,8 @@ function getStringFromWasm0(ptr, len) { return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); } +if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); } + const heap = new Array(128).fill(undefined); heap.push(undefined, null, true, false); @@ -89,6 +91,10 @@ export class Test { const ptr = this.__destroy_into_raw(); wasm.__wbg_test_free(ptr, 0); } + + [Symbol.dispose]() { + this.free(); + } /** * @param {number} test * @returns {Test} From ab3e9278661dcf1bea8527f49770e1fe0fd637cf Mon Sep 17 00:00:00 2001 From: Nicholas Roberts Date: Fri, 20 Sep 2024 18:04:30 +1000 Subject: [PATCH 2/4] gate Symbol.dispose generation behind WASM_BINDGEN_SYMBOL_DISPOSE env var, update tests to reflect lack of said var --- crates/cli-support/src/js/mod.rs | 19 ++++++++++++------- crates/cli-support/src/lib.rs | 3 +++ crates/cli/tests/reference/builder.d.ts | 1 - crates/cli/tests/reference/builder.js | 6 ------ crates/cli/tests/reference/constructor.d.ts | 1 - crates/cli/tests/reference/constructor.js | 6 ------ crates/cli/tests/reference/raw.d.ts | 1 - crates/cli/tests/reference/raw.js | 6 ------ 8 files changed, 15 insertions(+), 28 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 579254d7030..b053bbd526c 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1080,15 +1080,20 @@ impl<'a> Context<'a> { const ptr = this.__destroy_into_raw(); wasm.{}(ptr, 0); }} - - [Symbol.dispose]() {{ - this.free(); - }} ", wasm_bindgen_shared::free_function(name), )); ts_dst.push_str(" free(): void;\n"); - ts_dst.push_str(" [Symbol.dispose](): void;\n"); + if self.config.symbol_dispose { + dst.push_str( + " + [Symbol.dispose]() {{ + this.free(); + }} + ", + ); + ts_dst.push_str(" [Symbol.dispose](): void;\n"); + } dst.push_str(&class.contents); ts_dst.push_str(&class.typescript); @@ -1495,7 +1500,7 @@ impl<'a> Context<'a> { if !self.should_write_global("symbol_dispose") { return Ok(()); } - self.global(&"if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); }"); + self.global("if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); }"); Ok(()) } @@ -2490,7 +2495,7 @@ impl<'a> Context<'a> { pub fn generate(&mut self) -> Result<(), Error> { self.prestore_global_import_identifiers()?; // conditionally override Symbol.dispose - if !self.aux.structs.is_empty() { + if self.config.symbol_dispose && !self.aux.structs.is_empty() { self.expose_symbol_dispose()?; } for (id, adapter) in crate::sorted_iter(&self.wit.adapters) { diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index cf37a130616..d7c92a6af96 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -42,6 +42,7 @@ pub struct Bindgen { multi_value: bool, encode_into: EncodeInto, split_linked_modules: bool, + symbol_dispose: bool, } pub struct Output { @@ -88,6 +89,7 @@ impl Bindgen { let externref = env::var("WASM_BINDGEN_ANYREF").is_ok() || env::var("WASM_BINDGEN_EXTERNREF").is_ok(); let multi_value = env::var("WASM_BINDGEN_MULTI_VALUE").is_ok(); + let symbol_dispose = env::var("WASM_BINDGEN_SYMBOL_DISPOSE").is_ok(); Bindgen { input: Input::None, out_name: None, @@ -109,6 +111,7 @@ impl Bindgen { encode_into: EncodeInto::Test, omit_default_module_path: true, split_linked_modules: false, + symbol_dispose, } } diff --git a/crates/cli/tests/reference/builder.d.ts b/crates/cli/tests/reference/builder.d.ts index 097d4bf91a8..ede348c9632 100644 --- a/crates/cli/tests/reference/builder.d.ts +++ b/crates/cli/tests/reference/builder.d.ts @@ -4,7 +4,6 @@ */ export class ClassBuilder { free(): void; - [Symbol.dispose](): void; /** * @returns {ClassBuilder} */ diff --git a/crates/cli/tests/reference/builder.js b/crates/cli/tests/reference/builder.js index c2257811b9c..b491793222a 100644 --- a/crates/cli/tests/reference/builder.js +++ b/crates/cli/tests/reference/builder.js @@ -4,8 +4,6 @@ export function __wbg_set_wasm(val) { } -if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); } - const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); @@ -52,10 +50,6 @@ export class ClassBuilder { const ptr = this.__destroy_into_raw(); wasm.__wbg_classbuilder_free(ptr, 0); } - - [Symbol.dispose]() { - this.free(); - } /** * @returns {ClassBuilder} */ diff --git a/crates/cli/tests/reference/constructor.d.ts b/crates/cli/tests/reference/constructor.d.ts index 68166aebf00..f35e8c72fa1 100644 --- a/crates/cli/tests/reference/constructor.d.ts +++ b/crates/cli/tests/reference/constructor.d.ts @@ -4,7 +4,6 @@ */ export class ClassConstructor { free(): void; - [Symbol.dispose](): void; /** */ constructor(); diff --git a/crates/cli/tests/reference/constructor.js b/crates/cli/tests/reference/constructor.js index 53d2aeef8b5..ffac02b997a 100644 --- a/crates/cli/tests/reference/constructor.js +++ b/crates/cli/tests/reference/constructor.js @@ -4,8 +4,6 @@ export function __wbg_set_wasm(val) { } -if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); } - const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); @@ -44,10 +42,6 @@ export class ClassConstructor { const ptr = this.__destroy_into_raw(); wasm.__wbg_classconstructor_free(ptr, 0); } - - [Symbol.dispose]() { - this.free(); - } /** */ constructor() { diff --git a/crates/cli/tests/reference/raw.d.ts b/crates/cli/tests/reference/raw.d.ts index f856da7ad88..944af97ab04 100644 --- a/crates/cli/tests/reference/raw.d.ts +++ b/crates/cli/tests/reference/raw.d.ts @@ -9,7 +9,6 @@ export function test1(test: number): number; */ export class Test { free(): void; - [Symbol.dispose](): void; /** * @param {number} test * @returns {Test} diff --git a/crates/cli/tests/reference/raw.js b/crates/cli/tests/reference/raw.js index 160c52ee79d..3432d150366 100644 --- a/crates/cli/tests/reference/raw.js +++ b/crates/cli/tests/reference/raw.js @@ -26,8 +26,6 @@ function getStringFromWasm0(ptr, len) { return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); } -if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); } - const heap = new Array(128).fill(undefined); heap.push(undefined, null, true, false); @@ -91,10 +89,6 @@ export class Test { const ptr = this.__destroy_into_raw(); wasm.__wbg_test_free(ptr, 0); } - - [Symbol.dispose]() { - this.free(); - } /** * @param {number} test * @returns {Test} From 37c87d39af83765b037e1d05a2cf7efa836f8b59 Mon Sep 17 00:00:00 2001 From: Nicholas Roberts Date: Fri, 20 Sep 2024 19:03:43 +1000 Subject: [PATCH 3/4] Add Symbol.dispose/Explicit Resource Management example (via Deno) --- Cargo.toml | 1 + .../explicit-resource-management/Cargo.toml | 12 ++++ .../explicit-resource-management/README.md | 16 +++++ .../explicit-resource-management/build.sh | 7 +++ .../crate/.gitignore | 1 + .../explicit-resource-management/src/lib.rs | 63 +++++++++++++++++++ examples/explicit-resource-management/test.ts | 17 +++++ 7 files changed, 117 insertions(+) create mode 100644 examples/explicit-resource-management/Cargo.toml create mode 100644 examples/explicit-resource-management/README.md create mode 100755 examples/explicit-resource-management/build.sh create mode 100644 examples/explicit-resource-management/crate/.gitignore create mode 100644 examples/explicit-resource-management/src/lib.rs create mode 100644 examples/explicit-resource-management/test.ts diff --git a/Cargo.toml b/Cargo.toml index 141f0f316d5..ac38d0504d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ members = [ "examples/deno", "examples/dom", "examples/duck-typed-interfaces", + "examples/explicit-resource-management", "examples/fetch", "examples/guide-supported-types-examples", "examples/hello_world", diff --git a/examples/explicit-resource-management/Cargo.toml b/examples/explicit-resource-management/Cargo.toml new file mode 100644 index 00000000000..1bba26018ff --- /dev/null +++ b/examples/explicit-resource-management/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["The wasm-bindgen Developers"] +edition = "2021" +name = "explicit-resource-management" +publish = false +version = "0.0.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = { path = "../../" } diff --git a/examples/explicit-resource-management/README.md b/examples/explicit-resource-management/README.md new file mode 100644 index 00000000000..d6b6f76b939 --- /dev/null +++ b/examples/explicit-resource-management/README.md @@ -0,0 +1,16 @@ +# Using Explicit Resource Management (via Deno) + +You can build the example with + +```sh +$ ./build.sh +``` + +and test it with + +```sh +$ deno run --allow-read test.ts +``` + +The `--allow-read` flag is needed because the Wasm file is read during runtime. +This will be fixed when https://github.com/denoland/deno/issues/2552 is resolved. diff --git a/examples/explicit-resource-management/build.sh b/examples/explicit-resource-management/build.sh new file mode 100755 index 00000000000..8e1a70eb827 --- /dev/null +++ b/examples/explicit-resource-management/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -eux + +cargo build --target wasm32-unknown-unknown --release +WASM_BINDGEN_SYMBOL_DISPOSE=1 cargo run --package wasm-bindgen-cli --bin wasm-bindgen -- \ + --out-dir pkg --target deno ${CARGO_TARGET_DIR:-../../target}/wasm32-unknown-unknown/release/explicit_resource_management.wasm diff --git a/examples/explicit-resource-management/crate/.gitignore b/examples/explicit-resource-management/crate/.gitignore new file mode 100644 index 00000000000..5e38902c91f --- /dev/null +++ b/examples/explicit-resource-management/crate/.gitignore @@ -0,0 +1 @@ +/explicit-resource-management.wasm diff --git a/examples/explicit-resource-management/src/lib.rs b/examples/explicit-resource-management/src/lib.rs new file mode 100644 index 00000000000..67441dd798d --- /dev/null +++ b/examples/explicit-resource-management/src/lib.rs @@ -0,0 +1,63 @@ +use std::alloc::{GlobalAlloc, Layout, System}; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +use wasm_bindgen::prelude::*; + +// simple counting allocator tracking +struct Counter; + +static ALLOCATED: AtomicUsize = AtomicUsize::new(0); + +unsafe impl GlobalAlloc for Counter { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let ret = System.alloc(layout); + if !ret.is_null() { + ALLOCATED.fetch_add(layout.size(), Relaxed); + } + ret + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + System.dealloc(ptr, layout); + ALLOCATED.fetch_sub(layout.size(), Relaxed); + } +} + +#[global_allocator] +static A: Counter = Counter; + +#[wasm_bindgen] +pub fn current_allocation() -> usize { + ALLOCATED.load(Relaxed) +} + +// lifted from the `console_log` example +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +#[wasm_bindgen] +pub struct MyStruct { + x: Vec, + y: Vec, + name: String, +} + +#[wasm_bindgen] +impl MyStruct { + #[wasm_bindgen(constructor)] + pub fn new(name: String) -> MyStruct { + Self { + name, + x: (0..50).collect(), + y: (0..50).collect(), + } + } +} + +impl Drop for MyStruct { + fn drop(&mut self) { + log(&format!("Goodbye from {}!", self.name)); // should output "Goodbye from Rust!" + } +} diff --git a/examples/explicit-resource-management/test.ts b/examples/explicit-resource-management/test.ts new file mode 100644 index 00000000000..c622ce60e65 --- /dev/null +++ b/examples/explicit-resource-management/test.ts @@ -0,0 +1,17 @@ +import { current_allocation, MyStruct } from "./pkg/explicit_resource_management.js"; + +const initialAllocation = current_allocation(); +let referrent = {}; +console.log('Before scope: ', initialAllocation); + +{ + using foo = new MyStruct("Rust"); + // force foo to be treated as live by implicit memory management (FinalizationRegistry/GC) + // by retaining a reference that outlives the scope block (for the purposes of proving + // Symbol.dispose is called on scope exit). + referrent['foo'] = foo; + console.log('After construction, but before scope exit: ', current_allocation()); +} +const afterDisposeAllocation = current_allocation(); +console.log('After scope exit: ', afterDisposeAllocation); +console.log(referrent); \ No newline at end of file From a85b9c07d7f915c8c9edce8e54fa908683659df6 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 28 Sep 2024 10:49:37 +0200 Subject: [PATCH 4/4] Explicitly state experimental support --- CHANGELOG.md | 3 +++ crates/cli-support/src/lib.rs | 2 +- examples/explicit-resource-management/build.sh | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae724f8cbc..2430b15802d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ * Add bindings to `RTCRtpTransceiverDirection.stopped`. [#4102](https://github.com/rustwasm/wasm-bindgen/pull/4102) +* Added experimental support for `Symbol.dispose` via `WASM_BINDGEN_EXPERIMENTAL_SYMBOL_DISPOSE`. + [#4118](https://github.com/rustwasm/wasm-bindgen/pull/4118) + ### Fixed * Fixed linked modules emitting snippet files when not using `--split-linked-modules`. diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index d7c92a6af96..a35b057e6a6 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -89,7 +89,7 @@ impl Bindgen { let externref = env::var("WASM_BINDGEN_ANYREF").is_ok() || env::var("WASM_BINDGEN_EXTERNREF").is_ok(); let multi_value = env::var("WASM_BINDGEN_MULTI_VALUE").is_ok(); - let symbol_dispose = env::var("WASM_BINDGEN_SYMBOL_DISPOSE").is_ok(); + let symbol_dispose = env::var("WASM_BINDGEN_EXPERIMENTAL_SYMBOL_DISPOSE").is_ok(); Bindgen { input: Input::None, out_name: None, diff --git a/examples/explicit-resource-management/build.sh b/examples/explicit-resource-management/build.sh index 8e1a70eb827..0aecc1b2092 100755 --- a/examples/explicit-resource-management/build.sh +++ b/examples/explicit-resource-management/build.sh @@ -3,5 +3,5 @@ set -eux cargo build --target wasm32-unknown-unknown --release -WASM_BINDGEN_SYMBOL_DISPOSE=1 cargo run --package wasm-bindgen-cli --bin wasm-bindgen -- \ +WASM_BINDGEN_EXPERIMENTAL_SYMBOL_DISPOSE=1 cargo run --package wasm-bindgen-cli --bin wasm-bindgen -- \ --out-dir pkg --target deno ${CARGO_TARGET_DIR:-../../target}/wasm32-unknown-unknown/release/explicit_resource_management.wasm