Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 65 additions & 4 deletions crates/spidermonkey-embedding-splicer/src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub enum Resource {
Constructor(String),
Static(String),
Method(String),
Destructor(String),
}

impl Resource {
Expand All @@ -38,6 +39,7 @@ impl Resource {
Resource::Constructor(name) => format!("[constructor]{name}"),
Resource::Static(name) => format!("[static]{name}.{fn_name}"),
Resource::Method(name) => format!("[method]{name}.{fn_name}"),
Resource::Destructor(name) => format!("[dtor]{name}"),
}
}

Expand Down Expand Up @@ -65,6 +67,9 @@ impl Resource {
fn_name.to_lower_camel_case()
)
}
Resource::Destructor(name) => {
format!("{}$dtor", name.to_lower_camel_case())
}
}
}
}
Expand Down Expand Up @@ -190,7 +195,8 @@ pub fn componentize_bindgen(
Resource::None => None,
Resource::Method(name)
| Resource::Static(name)
| Resource::Constructor(name) => Some(name),
| Resource::Constructor(name)
| Resource::Destructor(name) => Some(name),
})
.or_default()
.push(item);
Expand Down Expand Up @@ -426,10 +432,12 @@ impl JsBindgen<'_> {
}
WorldItem::Interface { id, stability: _ } => {
let iface = &self.resolve.interfaces[*id];
for id in iface.types.values() {
if let TypeDefKind::Resource = &self.resolve.types[*id].kind {
let iface_name = interface_name(self.resolve, *id);
for ty_id in iface.types.values() {
if let TypeDefKind::Resource = &self.resolve.types[*ty_id].kind {
self.resource_directions
.insert(*id, AbiVariant::GuestExport);
.insert(*ty_id, AbiVariant::GuestExport);
self.resource_dtor_bindgen(name.clone(), iface_name.clone(), *ty_id);
}
}
for (func_name, func) in &iface.functions {
Expand Down Expand Up @@ -991,6 +999,59 @@ impl JsBindgen<'_> {
));
}

fn resource_dtor_bindgen(
&mut self,
export_name: String,
iface_name: Option<String>,
resource: TypeId,
) {
let resource_name = self.resolve.types[resource].name.as_ref().unwrap();
let resource_name_camel = resource_name.to_lower_camel_case();
let prefix = iface_name
.as_deref()
.map(|name| format!("{name}$"))
.unwrap_or_default();
let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
let binding_name = format!(
"export_{}",
binding_name(&format!("{resource_name_camel}$dtor"), &iface_name)
);

uwriteln!(
self.src,
"
async function {binding_name}(rep) {{
const entry = repTable.get(rep);
if (!entry) return;
repTable.delete(rep);
const resource = entry.rep;
delete resource[{symbol_resource_handle}];
finalizationRegistry_export${prefix}{resource_name_camel}.unregister(resource);
if (resource[{symbol_dispose}]) resource[{symbol_dispose}]();
}}
"
);

self.exports.push((
export_name,
BindingItem {
iface: true,
iface_name,
binding_name,
resource: Resource::Destructor(resource_name.clone()),
name: resource_name.clone(),
func: CoreFn {
params: vec![CoreTy::I32],
ret: None,
retptr: false,
retsize: 0,
paramptr: false,
},
},
));
}

fn core_fn(&self, func: &Function, sig: &WasmSignature) -> CoreFn {
CoreFn {
retsize: if sig.retptr {
Expand Down
28 changes: 22 additions & 6 deletions crates/spidermonkey-embedding-splicer/src/splice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ pub fn splice_bindings(
} else {
export_name.clone()
};
exports.push((expt, map_core_fn(func)));
exports.push((
expt,
map_core_fn(func),
matches!(resource, bindgen::Resource::Destructor(_)),
));
}

let mut imports = Vec::new();
Expand Down Expand Up @@ -340,7 +344,7 @@ pub fn splice_bindings(
pub fn splice(
engine: Vec<u8>,
imports: Vec<(String, String, CoreFn, Option<i32>)>,
exports: Vec<(String, CoreFn)>,
exports: Vec<(String, CoreFn, bool)>,
features: Vec<Feature>,
debug: bool,
) -> Result<Vec<u8>> {
Expand Down Expand Up @@ -383,13 +387,13 @@ pub fn splice(

fn remove_if_exported_by_js(
module: &mut Module,
content_exports: &[(String, CoreFn)],
content_exports: &[(String, CoreFn, bool)],
name_start: &str,
name_end: &str,
) {
let content_exports_run = content_exports
.iter()
.any(|(name, _)| name.starts_with(name_start) && name.ends_with(name_end));
.any(|(name, _, _)| name.starts_with(name_start) && name.ends_with(name_end));
if content_exports_run {
let exported_run_fn = module
.exports
Expand Down Expand Up @@ -846,7 +850,10 @@ fn synthesize_import_functions(
Ok(())
}

fn synthesize_export_functions(module: &mut Module, exports: &[(String, CoreFn)]) -> Result<()> {
fn synthesize_export_functions(
module: &mut Module,
exports: &[(String, CoreFn, bool)],
) -> Result<()> {
let cabi_realloc = get_export_fid(
module,
&module
Expand All @@ -867,7 +874,7 @@ fn synthesize_export_functions(module: &mut Module, exports: &[(String, CoreFn)]

let memory = 0;
// (2) Export call function synthesis
for (export_num, (expt_name, expt_sig)) in exports.iter().enumerate() {
for (export_num, (expt_name, expt_sig, inline_post_call)) in exports.iter().enumerate() {
// Export function synthesis
{
// add the function type
Expand Down Expand Up @@ -1031,10 +1038,19 @@ fn synthesize_export_functions(module: &mut Module, exports: &[(String, CoreFn)]
}
}

if *inline_post_call {
func.i32_const(export_num as i32);
func.call(post_call);
}

let fid = func.finish_module(module);
module.exports.add_export_func((*expt_name).clone(), *fid);
}

if *inline_post_call {
continue;
}

// Post export function synthesis
// We always define a post-export since we use a bulk deallocation strategy
// add the function type
Expand Down
23 changes: 23 additions & 0 deletions test/cases/resource-disposal/source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
let disposeCount = 0;
const disposeSymbol = Symbol.dispose || Symbol.for('dispose');

class Example {
constructor(id) {
this.id = id;
}

getId() {
return this.id;
}

[disposeSymbol]() {
disposeCount += 1;
}
}

export const resources = {
Example,
disposeCount() {
return disposeCount;
},
};
14 changes: 14 additions & 0 deletions test/cases/resource-disposal/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { strictEqual } from 'node:assert';

const disposeSymbol = Symbol.dispose || Symbol.for('dispose');

export function test(instance) {
const resource = new instance.resources.Example(42);

strictEqual(resource.getId(), 42);
resource[disposeSymbol]();
strictEqual(instance.resources.disposeCount(), 1);

resource[disposeSymbol]();
strictEqual(instance.resources.disposeCount(), 1);
}
14 changes: 14 additions & 0 deletions test/cases/resource-disposal/world.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test:test;

interface resources {
resource example {
constructor(id: u32);
get-id: func() -> u32;
}

dispose-count: func() -> u32;
}

world test {
export resources;
}
Loading