Skip to content

Commit 9058965

Browse files
authored
feat: optimized host bindings (#382)
1 parent a23cc46 commit 9058965

File tree

252 files changed

+1087
-246
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

252 files changed

+1087
-246
lines changed

crates/js-component-bindgen-component/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ impl From<InstantiationMode> for js_component_bindgen::InstantiationMode {
4646
}
4747
}
4848

49+
impl From<BindingsMode> for js_component_bindgen::BindingsMode {
50+
fn from(value: BindingsMode) -> Self {
51+
match value {
52+
BindingsMode::Js => js_component_bindgen::BindingsMode::Js,
53+
BindingsMode::DirectOptimized => js_component_bindgen::BindingsMode::DirectOptimized,
54+
BindingsMode::Optimized => js_component_bindgen::BindingsMode::Optimized,
55+
BindingsMode::Hybrid => js_component_bindgen::BindingsMode::Hybrid,
56+
}
57+
}
58+
}
59+
4960
struct JsComponentBindgenComponent;
5061

5162
export!(JsComponentBindgenComponent);
@@ -67,6 +78,7 @@ impl Guest for JsComponentBindgenComponent {
6778
tracing: options.tracing.unwrap_or(false),
6879
no_namespaced_exports: options.no_namespaced_exports.unwrap_or(false),
6980
multi_memory: options.multi_memory.unwrap_or(false),
81+
import_bindings: options.import_bindings.map(Into::into),
7082
};
7183

7284
let js_component_bindgen::Transpiled {
@@ -134,6 +146,7 @@ impl Guest for JsComponentBindgenComponent {
134146
tracing: false,
135147
no_namespaced_exports: false,
136148
multi_memory: false,
149+
import_bindings: None,
137150
};
138151

139152
let files = generate_types(name, resolve, world, opts).map_err(|e| e.to_string())?;

crates/js-component-bindgen-component/wit/js-component-bindgen.wit

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ world js-component-bindgen {
99
sync,
1010
}
1111

12+
variant bindings-mode {
13+
js,
14+
hybrid,
15+
optimized,
16+
direct-optimized,
17+
}
18+
1219
record generate-options {
1320
/// Name to use for the generated component
1421
name: string,
@@ -21,6 +28,9 @@ world js-component-bindgen {
2128
/// of the direct importable native ESM output.
2229
instantiation: option<instantiation-mode>,
2330

31+
/// Import bindings generation mode
32+
import-bindings: option<bindings-mode>,
33+
2434
/// Mappings of component import specifiers to JS import specifiers.
2535
map: option<maps>,
2636

crates/js-component-bindgen/src/function_bindgen.rs

+70-62
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ pub enum ResourceData {
4545
///
4646
/// For a given resource id {x}, the local variables are assumed:
4747
/// - handleTable{x}
48-
/// - captureTable{x} (only for imported tables)
49-
/// - captureCnt{x} (only for imported tables)
48+
/// - captureTable{x} (rep to instance map for captured imported tables, only for JS import bindgen,
49+
/// not hybrid)
50+
/// - captureCnt{x} for assigning capture rep
5051
///
5152
/// For component-defined resources:
5253
/// - finalizationRegistry{x}
@@ -1045,17 +1046,6 @@ impl Bindgen for FunctionBindgen<'_> {
10451046
self.bind_results(sig_results_length, results);
10461047
uwriteln!(self.src, "{}({});", self.callee, operands.join(", "));
10471048

1048-
if !self.cur_resource_borrows.is_empty() {
1049-
uwriteln!(
1050-
self.src,
1051-
"if ({}) {{
1052-
throw new Error('Resource error: borrows were not dropped');
1053-
}}",
1054-
self.cur_resource_borrows.join(" || ")
1055-
);
1056-
self.cur_resource_borrows = Vec::new();
1057-
}
1058-
10591049
if let Some(prefix) = self.tracing_prefix {
10601050
let to_result_string = self.intrinsic(Intrinsic::ToResultString);
10611051
uwriteln!(
@@ -1196,16 +1186,16 @@ impl Bindgen for FunctionBindgen<'_> {
11961186
let id = id.as_u32();
11971187
let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
11981188
let rsc_table_remove = self.intrinsic(Intrinsic::ResourceTableRemove);
1189+
let rep = format!("rep{}", self.tmp());
11991190
if !imported {
1200-
let rep = format!("rep{}", self.tmp());
12011191
let symbol_resource_handle =
12021192
self.intrinsic(Intrinsic::SymbolResourceHandle);
1203-
let rsc_table_get = self.intrinsic(Intrinsic::ResourceTableGet);
1193+
let rsc_flag = self.intrinsic(Intrinsic::ResourceTableFlag);
12041194
uwrite!(
12051195
self.src,
12061196
"var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);
1207-
var {rep} = {rsc_table_get}(handleTable{id}, {handle}).rep;
1208-
Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {rep} }});
1197+
var {rep} = handleTable{id}[({handle} << 1) + 1] & ~{rsc_flag};
1198+
Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {rep} }});
12091199
",
12101200
);
12111201
if is_own {
@@ -1239,15 +1229,39 @@ impl Bindgen for FunctionBindgen<'_> {
12391229
));
12401230
}
12411231
} else {
1242-
// imported handles lift as instance capture from a previous lowering
1243-
let rsc_table_get = self.intrinsic(Intrinsic::ResourceTableGet);
1244-
uwriteln!(self.src, "var {rsc} = captureTable{id}.get({rsc_table_get}(handleTable{id}, {handle}).rep);");
1245-
// an own lifting is a transfer to JS, so handle is implicitly dropped
1232+
// imported handles either lift as instance capture from a previous lowering,
1233+
// or we create a new JS class to represent it
1234+
let rsc_flag = self.intrinsic(Intrinsic::ResourceTableFlag);
1235+
let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
1236+
let symbol_resource_handle =
1237+
self.intrinsic(Intrinsic::SymbolResourceHandle);
1238+
uwriteln!(
1239+
self.src,
1240+
"var {rep} = handleTable{id}[({handle} << 1) + 1] & ~{rsc_flag};"
1241+
);
1242+
uwriteln!(self.src,
1243+
"var {rsc} = captureTable{id}.get({rep});
1244+
if (!{rsc}) {{
1245+
{rsc} = Object.create({local_name}.prototype);
1246+
Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1247+
Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
1248+
}}"
1249+
);
12461250
if is_own {
1251+
// an own lifting is a transfer to JS, so existing own handle is implicitly dropped
12471252
uwriteln!(
12481253
self.src,
1249-
"captureTable{id}.delete({rsc_table_remove}(handleTable{id}, {handle}).rep);"
1254+
"else {{
1255+
captureTable{id}.delete({rep});
1256+
}}
1257+
{rsc_table_remove}(handleTable{id}, {handle});"
12501258
);
1259+
} else {
1260+
// if lifting a borrow, that was not previously captured, create the class
1261+
self.cur_resource_borrows.push(format!(
1262+
"{}[{symbol_resource_handle}] = null;",
1263+
rsc.to_string()
1264+
));
12511265
}
12521266
}
12531267
}
@@ -1318,78 +1332,72 @@ impl Bindgen for FunctionBindgen<'_> {
13181332
ResourceData::Host { id, local_name, .. } => {
13191333
let id = id.as_u32();
13201334
if !imported {
1321-
let rep = format!("rep{}", self.tmp());
1322-
13231335
uwriteln!(
13241336
self.src,
1325-
"var {rep} = {op}[{symbol_resource_handle}];
1326-
if ({rep} === null) {{
1327-
throw new Error('Resource error: \"{class_name}\" lifetime expired.');
1328-
}}
1329-
if ({rep} === undefined) {{
1337+
"var {handle} = {op}[{symbol_resource_handle}];
1338+
if (!{handle}) {{
13301339
throw new Error('Resource error: Not a valid \"{class_name}\" resource.');
13311340
}}
13321341
",
13331342
);
13341343

1335-
// Own resources of own components lowered into the component
1336-
// still need handle table tracking of their rep value, by creating
1337-
// a new handle for this.
1338-
// The class representation of that own resource is still disposed
1339-
// though, and their finalizers deregistered as well.
13401344
if is_own {
13411345
let empty_func = self.intrinsic(Intrinsic::EmptyFunc);
1342-
let rsc_table_create_own =
1343-
self.intrinsic(Intrinsic::ResourceTableCreateOwn);
13441346
uwriteln!(
13451347
self.src,
1346-
"var {handle} = {rsc_table_create_own}(handleTable{id}, {rep});
1347-
finalizationRegistry{id}.unregister({op});
1348+
"finalizationRegistry{id}.unregister({op});
13481349
{op}[{symbol_dispose}] = {empty_func};
13491350
{op}[{symbol_resource_handle}] = null;"
13501351
);
1351-
} else {
1352-
// it is only in the local borrow case where we can simplify the handle
1353-
// to just be the original rep value and don't need to track an
1354-
// explicit handle lifetime.
1355-
uwriteln!(self.src, "var {handle} = {rep};");
13561352
}
13571353
} else {
1358-
// imported resources are always given a unique handle
1354+
// imported resources may already have a handle if they were constructed
1355+
// by a component and then passed out
13591356
uwriteln!(
13601357
self.src,
13611358
"if (!({op} instanceof {local_name})) {{
13621359
throw new Error('Resource error: Not a valid \"{class_name}\" resource.');
13631360
}}
1364-
captureTable{id}.set(++captureCnt{id}, {op});"
1361+
var {handle} = {op}[{symbol_resource_handle}];
1362+
"
13651363
);
1364+
// otherwise, in hybrid bindgen we check for a Symbol.for('cabiRep')
1365+
// to get the resource rep
1366+
// fall back to assign a new rep in the capture table, when the imported
1367+
// resource was constructed externally
13661368
if is_own {
1367-
let rsc_table_create_own =
1369+
let symbol_resource_rep =
1370+
self.intrinsic(Intrinsic::SymbolResourceRep);
1371+
let rsc_table_create =
13681372
self.intrinsic(Intrinsic::ResourceTableCreateOwn);
13691373
uwriteln!(
13701374
self.src,
1371-
"var {handle} = {rsc_table_create_own}(handleTable{id}, captureCnt{id});",
1375+
"if (!{handle}) {{
1376+
let rep = {op}[{symbol_resource_rep}];
1377+
if (!rep) {{
1378+
captureTable{id}.set(++captureCnt{id}, {op});
1379+
rep = captureCnt{id};
1380+
}} else {{
1381+
{op}[{symbol_resource_rep}] = null;
1382+
}}
1383+
{handle} = {rsc_table_create}(handleTable{id}, rep);
1384+
}}"
13721385
);
13731386
} else {
1374-
let rsc_table_create_borrow =
1387+
let symbol_resource_rep =
1388+
self.intrinsic(Intrinsic::SymbolResourceRep);
1389+
let rsc_table_create =
13751390
self.intrinsic(Intrinsic::ResourceTableCreateBorrow);
13761391
uwriteln!(
13771392
self.src,
1378-
"var {handle} = {rsc_table_create_borrow}(handleTable{id}, captureCnt{id});",
1393+
"if (!{handle}) {{
1394+
if (!{op}[{symbol_resource_rep}]) {{
1395+
captureTable{id}.set(++captureCnt{id}, {op});
1396+
}}
1397+
{handle} = {rsc_table_create}(handleTable{id}, {op}[{symbol_resource_rep}] || captureCnt{id});
1398+
}}"
13791399
);
1380-
}
1381-
1382-
// track lowered borrows to ensure they are dropped
1383-
// cur_resource_borrows can be reused because:
1384-
// - it is not possible to have a Wasm call that lifts a a borrow handle argument
1385-
// and wasm calls cannot return borrow handles for lifting
1386-
// - conversely, it is not possible to have a JS call that lowers a borrow handle argument
1387-
// and JS calls cannot return borrows for lowering
1388-
if !is_own && !self.valid_lifting_optimization {
1389-
let rsc_table_get = self.intrinsic(Intrinsic::ResourceTableGet);
1390-
self.cur_resource_borrows
1391-
.push(format!("{rsc_table_get}(handleTable{id}, {handle})"));
1392-
}
1400+
};
13931401
}
13941402
}
13951403

0 commit comments

Comments
 (0)