Skip to content

Commit 8c24b0b

Browse files
feat(bindgen): fill out future lower impl
1 parent 306a892 commit 8c24b0b

8 files changed

Lines changed: 357 additions & 59 deletions

File tree

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

Lines changed: 184 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use std::mem;
44

55
use heck::{ToLowerCamelCase, ToUpperCamelCase};
66
use wasmtime_environ::component::{
7-
CanonicalOptions, InterfaceType, ResourceIndex, TypeComponentLocalErrorContextTableIndex,
8-
TypeFutureTableIndex, TypeResourceTableIndex, TypeStreamTableIndex,
7+
CanonicalOptions, CanonicalOptionsDataModel, InterfaceType, LinearMemoryOptions, ResourceIndex,
8+
TypeComponentLocalErrorContextTableIndex, TypeFutureTableIndex, TypeResourceTableIndex,
9+
TypeStreamTableIndex,
910
};
1011
use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
1112
use wit_component::StringEncoding;
@@ -2685,19 +2686,172 @@ impl Bindgen for FunctionBindgen<'_> {
26852686
results.push(item.clone());
26862687
}
26872688

2688-
Instruction::FutureLower { .. } => {
2689-
// TODO: convert this return of the lifted Future:
2690-
//
2691-
// ```
2692-
// return BigInt(writeEndWaitableIdx) << 32n | BigInt(readEndWaitableIdx);
2693-
// ```
2694-
//
2695-
// Into a component-local Future instance
2696-
//
2689+
Instruction::FutureLower { ty, .. } => {
2690+
let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2691+
let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2692+
ComponentIntrinsic::GetOrCreateAsyncState,
2693+
));
2694+
let gen_future_host_inject_fn = self.intrinsic(Intrinsic::AsyncFuture(
2695+
AsyncFutureIntrinsic::GenFutureHostInjectFn,
2696+
));
2697+
let is_future_lowerable_object_fn = self.intrinsic(Intrinsic::AsyncFuture(
2698+
AsyncFutureIntrinsic::IsFutureLowerableObject,
2699+
));
2700+
2701+
let component_idx = self.canon_opts.instance.as_u32();
2702+
26972703
let future_arg = operands
26982704
.first()
26992705
.expect("unexpectedly missing ErrorContextLower arg");
2700-
results.push(future_arg.clone());
2706+
2707+
// Build the lowering function for the type produced by the future
2708+
let type_id = &crate::dealias(self.resolve, *ty);
2709+
let ResourceTable {
2710+
imported: true,
2711+
data:
2712+
ResourceData::Guest {
2713+
extra:
2714+
Some(ResourceExtraData::Future {
2715+
table_idx: future_table_idx_ty,
2716+
elem_ty,
2717+
}),
2718+
..
2719+
},
2720+
} = self
2721+
.resource_map
2722+
.get(type_id)
2723+
.expect("missing resource mapping for future lower")
2724+
else {
2725+
unreachable!("invalid resource table observed during future lower");
2726+
};
2727+
let future_table_idx = future_table_idx_ty.as_u32();
2728+
2729+
// Generate payload metadata ('elemMeta')
2730+
let (
2731+
payload_type_name_js,
2732+
lift_fn_js,
2733+
lower_fn_js,
2734+
payload_is_none,
2735+
payload_is_numeric,
2736+
payload_is_borrow,
2737+
payload_is_async_value,
2738+
payload_size32_js,
2739+
payload_align32_js,
2740+
payload_flat_count_js,
2741+
) = match elem_ty {
2742+
Some(PayloadTypeMetadata {
2743+
ty: _,
2744+
iface_ty,
2745+
lift_js_expr,
2746+
lower_js_expr,
2747+
size32,
2748+
align32,
2749+
flat_count,
2750+
}) => (
2751+
format!("'{iface_ty:?}'"),
2752+
lift_js_expr.as_str(),
2753+
lower_js_expr.as_str(),
2754+
"false",
2755+
format!(
2756+
"{}",
2757+
matches!(
2758+
iface_ty,
2759+
InterfaceType::U8
2760+
| InterfaceType::U16
2761+
| InterfaceType::U32
2762+
| InterfaceType::U64
2763+
| InterfaceType::S8
2764+
| InterfaceType::S16
2765+
| InterfaceType::S32
2766+
| InterfaceType::S64
2767+
| InterfaceType::Float32
2768+
| InterfaceType::Float64
2769+
)
2770+
),
2771+
format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
2772+
format!(
2773+
"{}",
2774+
matches!(
2775+
iface_ty,
2776+
InterfaceType::Stream(_) | InterfaceType::Future(_)
2777+
)
2778+
),
2779+
size32.to_string(),
2780+
align32.to_string(),
2781+
flat_count.unwrap_or(0).to_string(),
2782+
),
2783+
None => (
2784+
"null".into(),
2785+
"() => {{ throw new Error('no lift fn'); }}",
2786+
"() => {{ throw new Error('no lower fn'); }}",
2787+
"true",
2788+
"false".into(),
2789+
"false".into(),
2790+
"false".into(),
2791+
"null".into(),
2792+
"null".into(),
2793+
"0".into(),
2794+
),
2795+
};
2796+
2797+
// Retrieve the realloc fn if present, in case lowering fns need to allocate
2798+
//
2799+
// The realloc fn is saved on the element metadata which is passed through to
2800+
// stream end and underlying buffer
2801+
let get_realloc_fn_js = match self.canon_opts.data_model {
2802+
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
2803+
realloc: Some(realloc_idx),
2804+
..
2805+
}) => format!("() => realloc{}", realloc_idx.as_u32()),
2806+
_ => "undefined".into(),
2807+
};
2808+
2809+
let tmp = self.tmp();
2810+
let lowered_future_waitable_idx = format!("futureWaitableIdx{tmp}");
2811+
uwriteln!(
2812+
self.src,
2813+
r#"
2814+
if (!{is_future_lowerable_object_fn}({future_arg})) {{
2815+
{debug_log_fn}('[Instruction::FutureLower] object is not a Promise/Thenable', {{ {future_arg} }});
2816+
throw new Error('unrecognized future object (not Promise/Thenable)');
2817+
}}
2818+
2819+
const cstate{tmp} = {get_or_create_async_state_fn}({component_idx});
2820+
if (!cstate{tmp}) {{ throw new Error(`missing component state for component [{component_idx}]`); }}
2821+
2822+
// TODO(feat): facilitate non utf8 string encoding for lowered futures
2823+
const stringEncoding = 'utf8';
2824+
2825+
const {{ writeEnd: hostWriteEnd{tmp}, readEnd: readEnd{tmp} }} = cstate{tmp}.createFuture({{
2826+
tableIdx: {future_table_idx},
2827+
elemMeta: {{
2828+
liftFn: {lift_fn_js},
2829+
lowerFn: {lower_fn_js},
2830+
payloadTypeName: {payload_type_name_js},
2831+
isNone: {payload_is_none},
2832+
isNumeric: {payload_is_numeric},
2833+
isBorrowed: {payload_is_borrow},
2834+
isAsyncValue: {payload_is_async_value},
2835+
flatCount: {payload_flat_count_js},
2836+
align32: {payload_align32_js},
2837+
size32: {payload_size32_js},
2838+
stringEncoding,
2839+
getReallocFn: {get_realloc_fn_js},
2840+
}},
2841+
}});
2842+
2843+
const hostInjectFn = {gen_future_host_inject_fn}({{
2844+
promise: {future_arg},
2845+
stringEncoding,
2846+
hostWriteEnd: hostWriteEnd{tmp},
2847+
}});
2848+
readEnd{tmp}.setHostInjectFn(hostInjectFn);
2849+
2850+
const {lowered_future_waitable_idx} = readEnd{tmp}.waitableIdx();
2851+
"#
2852+
);
2853+
2854+
results.push(lowered_future_waitable_idx);
27012855
}
27022856

27032857
Instruction::FutureLift { payload, ty } => {
@@ -2802,9 +2956,12 @@ impl Bindgen for FunctionBindgen<'_> {
28022956
ComponentIntrinsic::GetOrCreateAsyncState,
28032957
));
28042958
let gen_host_inject_fn = self.intrinsic(Intrinsic::AsyncStream(
2805-
AsyncStreamIntrinsic::GenHostInjectFn,
2959+
AsyncStreamIntrinsic::GenStreamHostInjectFn,
28062960
));
28072961

2962+
// TODO(???): A component could end up receiving a stream that it outputted,
2963+
// and the below would fail (imported: false)?
2964+
28082965
// Build the lowering function for the type produced by the stream
28092966
let type_id = &crate::dealias(self.resolve, *ty);
28102967
let ResourceTable {
@@ -2896,6 +3053,18 @@ impl Bindgen for FunctionBindgen<'_> {
28963053
),
28973054
};
28983055

3056+
// Retrieve the realloc fn if present, in case lowering fns need to allocate
3057+
//
3058+
// The realloc fn is saved on the element metadata which is passed through to
3059+
// stream end and underlying buffer
3060+
let get_realloc_fn_js = match self.canon_opts.data_model {
3061+
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
3062+
realloc: Some(realloc_idx),
3063+
..
3064+
}) => format!("() => realloc{}", realloc_idx.as_u32()),
3065+
_ => "undefined".into(),
3066+
};
3067+
28993068
let tmp = self.tmp();
29003069
let lowered_stream_waitable_idx = format!("streamWaitableIdx{tmp}");
29013070
uwriteln!(
@@ -2926,6 +3095,7 @@ impl Bindgen for FunctionBindgen<'_> {
29263095
size32: {payload_size32_js},
29273096
// TODO(feat): facilitate non utf8 string encoding for lowered streams
29283097
stringEncoding: 'utf8',
3098+
geReallocFn: {get_realloc_fn_js},
29293099
}},
29303100
}});
29313101
@@ -2952,6 +3122,7 @@ impl Bindgen for FunctionBindgen<'_> {
29523122
const {lowered_stream_waitable_idx} = readEnd{tmp}.waitableIdx();
29533123
"#
29543124
);
3125+
29553126
results.push(lowered_stream_waitable_idx);
29563127
}
29573128

crates/js-component-bindgen/src/intrinsics/lift.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ impl LiftIntrinsic {
393393
let val;
394394
395395
if (ctx.useDirectParams) {{
396-
if (params.length === 0) {{ throw new Error('expected at least a single i32 argument'); }}
396+
if (ctx.params.length === 0) {{ throw new Error('expected at least a single i32 argument'); }}
397397
val = ctx.params[0];
398398
ctx.params = ctx.params.slice(1);
399399
return [val, ctx];
@@ -734,11 +734,6 @@ impl LiftIntrinsic {
734734
return function {lift_flat_record_fn}Inner(ctx) {{
735735
{debug_log_fn}('[{lift_flat_record_fn}()] args', {{ ctx }});
736736
737-
if (ctx.useDirectParams) {{
738-
ctx.storagePtr = ctx.params[0];
739-
ctx.params = ctx.params.slice(1);
740-
}}
741-
742737
const res = {{}};
743738
for (const [key, liftFn, _size32, _align32] of keysAndLiftFns) {{
744739
let [val, newCtx] = liftFn(ctx);
@@ -1233,28 +1228,27 @@ impl LiftIntrinsic {
12331228
output.push_str(&format!(r#"
12341229
function {lift_flat_error_fn}(errCtxTableIdx, ctx) {{
12351230
{debug_log_fn}('[{lift_flat_error_fn}()] ctx', ctx);
1236-
const {{ useDirectParams, params, componentIdx }} = ctx;
12371231
12381232
let val;
12391233
let table;
1240-
if (useDirectParams) {{
1241-
if (params.length === 0) {{ throw new Error('expected at least one single i32 argument'); }}
1234+
if (ctx.useDirectParams) {{
1235+
if (ctx.params.length === 0) {{ throw new Error('expected at least one single i32 argument'); }}
12421236
val = ctx.params[0];
12431237
ctx.params = ctx.params.slice(1);
1244-
table = {get_err_ctx_local_table_fn}(componentIdx, errCtxTableIdx);
1238+
table = {get_err_ctx_local_table_fn}(ctx.componentIdx, errCtxTableIdx);
12451239
}} else {{
12461240
throw new Error('indirect flat lift for error-contexts not yet implemented!');
12471241
}}
12481242
12491243
let handle = table.get(val);
12501244
if (handle === undefined) {{
1251-
throw new Error(`missing error ctx (handle [${{val}}], component [${{componentIdx}}], error context table [${{errCtxTableIdx}}])`);
1245+
throw new Error(`missing error ctx (handle [${{val}}], component [${{ctx.componentIdx}}], error context table [${{errCtxTableIdx}}])`);
12521246
}}
12531247
1254-
const cstate = {get_or_create_async_state_fn}(componentIdx);
1248+
const cstate = {get_or_create_async_state_fn}(ctx.componentIdx);
12551249
const errCtx = cstate.handles.get(handle);
12561250
if (!errCtx || errCtx.globalRep === undefined || errCtx.refCount === undefined) {{
1257-
throw new Error(`malformed error context (handle [${{handle}}], component [${{componentIdx}}])`);
1251+
throw new Error(`malformed error context (handle [${{handle}}], component [${{ctx.componentIdx}}])`);
12581252
}}
12591253
12601254
errCtx.refCount -= 1;

crates/js-component-bindgen/src/intrinsics/lower.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ impl LowerIntrinsic {
498498
function {lower_flat_f64_fn}(ctx) {{
499499
{debug_log_fn}('[{lower_flat_f64_fn}()] args', {{ ctx }});
500500
501-
if (vals.length !== 1) {{ throw new Error('unexpected number of vals'); }}
501+
if (ctx.vals.length !== 1) {{ throw new Error('unexpected number of vals'); }}
502502
503503
const rem = ctx.storagePtr % 8;
504504
if (rem !== 0) {{ ctx.storagePtr += (8 - rem); }}
@@ -994,7 +994,7 @@ impl LowerIntrinsic {
994994
Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenReadFnFromLowerableStream)
995995
.name();
996996
let gen_host_inject_fn =
997-
Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenHostInjectFn).name();
997+
Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenStreamHostInjectFn).name();
998998
let lower_u32_fn = Self::LowerFlatU32.name();
999999

10001000
output.push_str(&format!(

crates/js-component-bindgen/src/intrinsics/mod.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,8 @@ impl Intrinsic {
644644
storagePtr: startPtr,
645645
componentIdx: this.#componentIdx,
646646
stringEncoding: this.#elemMeta.stringEncoding,
647-
realloc: this.#elemMeta.reallocFn,
647+
realloc: this.#elemMeta.getReallocFn?.(),
648+
getReallocFn: this.#elemMeta.getReallocFn,
648649
}}
649650
for (const v of values) {{
650651
lowerCtx.vals = [v];
@@ -1401,7 +1402,22 @@ pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source {
14011402
&Intrinsic::SymbolResourceRep,
14021403
&Intrinsic::Component(ComponentIntrinsic::GetOrCreateAsyncState),
14031404
&Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenReadFnFromLowerableStream),
1404-
&Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenHostInjectFn),
1405+
&Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenStreamHostInjectFn),
1406+
&Intrinsic::Lower(LowerIntrinsic::LowerFlatU32),
1407+
])
1408+
}
1409+
1410+
if args
1411+
.intrinsics
1412+
.contains(&Intrinsic::Lower(LowerIntrinsic::LowerFlatFuture))
1413+
{
1414+
args.intrinsics.extend([
1415+
&Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GlobalFutureMap),
1416+
&Intrinsic::AsyncFuture(AsyncFutureIntrinsic::InternalFutureClass),
1417+
&Intrinsic::AsyncFuture(AsyncFutureIntrinsic::IsFutureLowerableObject),
1418+
&Intrinsic::SymbolResourceRep,
1419+
&Intrinsic::Component(ComponentIntrinsic::GetOrCreateAsyncState),
1420+
&Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GenFutureHostInjectFn),
14051421
&Intrinsic::Lower(LowerIntrinsic::LowerFlatU32),
14061422
])
14071423
}
@@ -1535,6 +1551,15 @@ pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source {
15351551
]);
15361552
}
15371553

1554+
if args.intrinsics.contains(&Intrinsic::AsyncFuture(
1555+
AsyncFutureIntrinsic::FutureNewFromLift,
1556+
)) {
1557+
args.intrinsics.extend([
1558+
&Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GlobalFutureMap),
1559+
&Intrinsic::AsyncFuture(AsyncFutureIntrinsic::HostFutureClass),
1560+
]);
1561+
}
1562+
15381563
if args.intrinsics.contains(&Intrinsic::AsyncFuture(
15391564
AsyncFutureIntrinsic::FutureWritableEndClass,
15401565
)) || args.intrinsics.contains(&Intrinsic::AsyncFuture(

0 commit comments

Comments
 (0)