Skip to content

Commit d979322

Browse files
feat(bindgen): add support for p3 futures
1 parent c67a083 commit d979322

File tree

8 files changed

+1799
-565
lines changed

8 files changed

+1799
-565
lines changed

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

Lines changed: 83 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,7 +1456,7 @@ impl Bindgen for FunctionBindgen<'_> {
14561456
const started = await task.enter();
14571457
if (!started) {{
14581458
{debug_log_fn}('[Instruction::AsyncTaskReturn] failed to enter task', {{
1459-
taskID: preparedTask.id(),
1459+
taskID: task.id(),
14601460
subtaskID: currentSubtask?.id(),
14611461
}});
14621462
throw new Error("failed to enter task");
@@ -1646,7 +1646,7 @@ impl Bindgen for FunctionBindgen<'_> {
16461646
const started = await task.enter({{ isHost: hostProvided }});
16471647
if (!started) {{
16481648
{debug_log_fn}('[Instruction::CallInterface] failed to enter task', {{
1649-
taskID: preparedTask.id(),
1649+
taskID: task.id(),
16501650
subtaskID: currentSubtask?.id(),
16511651
}});
16521652
throw new Error("failed to enter task");
@@ -2612,84 +2612,92 @@ impl Bindgen for FunctionBindgen<'_> {
26122612
}
26132613

26142614
Instruction::FutureLift { payload, ty } => {
2615-
let future_ty = &crate::dealias(self.resolve, *ty);
2616-
2617-
// TODO: we must generate the lifting function *before* function bindgen happens
2618-
// (see commented async param lift code generation), because inside here
2619-
// we do not have access to the interface types required to generate
2620-
//
2621-
// Alternatively, we can implement gen_flat_{lift,lower}_fn_js_expr for
2622-
// TypeDefs with a resolve as well (and make sure the code works with either)
2623-
//
2624-
// TODO(breaking): consider adding more information to bindgen (pointer to component types?)
2625-
match payload {
2626-
Some(payload_ty) => {
2627-
match payload_ty {
2628-
// TODO: reuse existing lifts
2629-
Type::Bool
2630-
| Type::U8
2631-
| Type::U16
2632-
| Type::U32
2633-
| Type::U64
2634-
| Type::S8
2635-
| Type::S16
2636-
| Type::S32
2637-
| Type::S64
2638-
| Type::F32
2639-
| Type::F64
2640-
| Type::Char
2641-
| Type::String
2642-
| Type::ErrorContext => uwriteln!(
2643-
self.src,
2644-
"const payloadLiftFn = () => {{ throw new Error('lift for {payload_ty:?}'); }}",
2645-
),
2646-
Type::Id(payload_ty_id) => {
2647-
if self.resource_map.contains_key(payload_ty_id) {
2648-
let ResourceTable { data, .. } =
2649-
&self.resource_map[payload_ty_id];
2650-
uwriteln!(
2651-
self.src,
2652-
"const payloadLiftFn = () => {{ throw new Error('lift for {} (identifier {})'); }}",
2653-
payload_ty_id.index(),
2654-
match data {
2655-
ResourceData::Host { local_name, .. } => local_name,
2656-
ResourceData::Guest { resource_name, .. } =>
2657-
resource_name,
2658-
}
2659-
);
2660-
} else {
2661-
// TODO: generate lift fns (see TODO above)
2662-
// NOTE: the missing type here is normally a result with nested types...
2663-
// the resource_map may not be indexing these properly
2664-
//
2665-
// eprintln!("warning: missing resource map def {:#?}", self.resolve.types[*payload_ty_id]);
2666-
}
2667-
}
2668-
};
2615+
let future_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncFuture(
2616+
AsyncFutureIntrinsic::FutureNewFromLift,
2617+
));
26692618

2670-
// // TODO: save payload type size below and more information about the type w/ the future?
2671-
// let payload_ty_size = self.sizes.size(payload_ty).size_wasm32();
2619+
// We must look up the type idx to find the future
2620+
let type_id = &crate::dealias(self.resolve, *ty);
2621+
let ResourceTable {
2622+
imported: true,
2623+
data:
2624+
ResourceData::Guest {
2625+
extra:
2626+
Some(ResourceExtraData::Future {
2627+
table_idx: future_table_idx_ty,
2628+
elem_ty: future_element_ty,
2629+
}),
2630+
..
2631+
},
2632+
} = self
2633+
.resource_map
2634+
.get(type_id)
2635+
.expect("missing resource mapping for future lift")
2636+
else {
2637+
unreachable!("invalid resource table observed during future lift");
2638+
};
26722639

2673-
// NOTE: here, rather than create a new `Future` "resource" using the saved
2674-
// ResourceData, we use the future.new intrinsic directly.
2675-
//
2676-
// TODO: differentiate "locally" created futures and futures that are lifted in?
2677-
//
2678-
let tmp = self.tmp();
2679-
let result_var = format!("futureResult{tmp}");
2680-
let component_idx = self.canon_opts.instance.as_u32();
2681-
let future_new_fn =
2682-
self.intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureNew));
2683-
uwriteln!(
2684-
self.src,
2685-
"const {result_var} = {future_new_fn}({{ componentIdx: {component_idx}, futureTypeRep: {} }});",
2686-
future_ty.index(),
2687-
);
2688-
results.push(result_var.clone());
2640+
// if a future element is present, it should match the payload we're getting
2641+
let (lift_fn_js, lower_fn_js) = match future_element_ty {
2642+
Some(PayloadTypeMetadata {
2643+
ty,
2644+
lift_js_expr,
2645+
lower_js_expr,
2646+
..
2647+
}) => {
2648+
assert_eq!(Some(*ty), **payload, "future element type mismatch");
2649+
(lift_js_expr.to_string(), lower_js_expr.to_string())
26892650
}
2651+
None => (
2652+
"() => {{ throw new Error('no lift fn'); }}".into(),
2653+
"() => {{ throw new Error('no lower fn'); }}".into(),
2654+
),
2655+
};
2656+
if let Some(PayloadTypeMetadata { ty, .. }) = future_element_ty {
2657+
assert_eq!(Some(*ty), **payload, "future element type mismatch");
2658+
}
2659+
2660+
let tmp = self.tmp();
2661+
let result_var = format!("futureResult{tmp}");
26902662

2691-
None => unreachable!("future with no payload unsupported"),
2663+
// We only need to attempt to do an immediate lift in non-async cases,
2664+
// as the return of the function execution ('above' in the code)
2665+
// will be the future idx
2666+
if !self.is_async {
2667+
// If we're dealing with a sync function, we can use the return directly
2668+
let arg_future_end_idx = operands
2669+
.first()
2670+
.expect("unexpectedly missing future end return arg in FutureLift");
2671+
2672+
let (payload_ty_size32_js, payload_ty_align32_js) =
2673+
if let Some(payload_ty) = payload {
2674+
(
2675+
self.sizes.size(payload_ty).size_wasm32().to_string(),
2676+
self.sizes.align(payload_ty).align_wasm32().to_string(),
2677+
)
2678+
} else {
2679+
("null".into(), "null".into())
2680+
};
2681+
2682+
let future_table_idx = future_table_idx_ty.as_u32();
2683+
let component_idx = self.canon_opts.instance.as_u32();
2684+
2685+
uwriteln!(
2686+
self.src,
2687+
"
2688+
const {result_var} = {future_new_from_lift_fn}({{
2689+
componentIdx: {component_idx},
2690+
futureTableIdx: {future_table_idx},
2691+
futureEndWaitableIdx: {arg_future_end_idx},
2692+
payloadLiftFn: {lift_fn_js},
2693+
payloadLowerFn: {lower_fn_js},
2694+
payloadTypeSize32: {payload_ty_size32_js},
2695+
payloadTypeAlign32: {payload_ty_align32_js},
2696+
}});",
2697+
);
26922698
}
2699+
2700+
results.push(result_var.clone());
26932701
}
26942702

26952703
Instruction::StreamLower { ty, .. } => {
@@ -2928,7 +2936,6 @@ impl Bindgen for FunctionBindgen<'_> {
29282936
};
29292937

29302938
let stream_table_idx = stream_table_idx_ty.as_u32();
2931-
let is_unit_stream = payload.is_none();
29322939

29332940
uwriteln!(
29342941
self.src,
@@ -2941,7 +2948,6 @@ impl Bindgen for FunctionBindgen<'_> {
29412948
payloadLowerFn: {lower_fn_js},
29422949
payloadTypeSize32: {payload_ty_size32_js},
29432950
payloadTypeAlign32: {payload_ty_align32_js},
2944-
isUnitStream: {is_unit_stream},
29452951
}});",
29462952
);
29472953
}

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

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
//! Intrinsics that represent helpers that manage per-component state
22
3-
use crate::{
4-
intrinsics::{
5-
Intrinsic,
6-
p3::{async_stream::AsyncStreamIntrinsic, waitable::WaitableIntrinsic},
7-
},
8-
source::Source,
9-
};
3+
use std::fmt::Write as _;
4+
5+
use crate::intrinsics::Intrinsic;
6+
use crate::intrinsics::p3::async_future::AsyncFutureIntrinsic;
7+
use crate::intrinsics::p3::async_stream::AsyncStreamIntrinsic;
8+
use crate::intrinsics::p3::waitable::WaitableIntrinsic;
9+
use crate::source::Source;
10+
use crate::uwriteln;
1011

1112
/// This enum contains intrinsics that manage per-component state
1213
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
@@ -84,7 +85,7 @@ impl ComponentIntrinsic {
8485
match self {
8586
Self::GlobalAsyncStateMap => {
8687
let var_name = Self::GlobalAsyncStateMap.name();
87-
output.push_str(&format!("const {var_name} = new Map();\n"));
88+
uwriteln!(output, r#"const {var_name} = new Map();"#);
8889
}
8990

9091
Self::BackpressureInc => {
@@ -127,6 +128,9 @@ impl ComponentIntrinsic {
127128
let internal_stream_class = AsyncStreamIntrinsic::InternalStreamClass.name();
128129
let global_stream_map = AsyncStreamIntrinsic::GlobalStreamMap.name();
129130
let global_stream_table_map = AsyncStreamIntrinsic::GlobalStreamTableMap.name();
131+
let internal_future_class = AsyncFutureIntrinsic::InternalFutureClass.name();
132+
let global_future_map = AsyncFutureIntrinsic::GlobalFutureMap.name();
133+
let global_future_table_map = AsyncFutureIntrinsic::GlobalFutureTableMap.name();
130134
let waitable_class = Intrinsic::Waitable(WaitableIntrinsic::WaitableClass).name();
131135
let get_or_create_async_state_fn = Self::GetOrCreateAsyncState.name();
132136
let promise_with_resolvers_fn = Intrinsic::PromiseWithResolversPonyfill.name();
@@ -655,6 +659,128 @@ impl ComponentIntrinsic {
655659
656660
return streamEnd;
657661
}}
662+
663+
createFuture(args) {{
664+
{debug_log_fn}('[{component_async_state_class}#createFuture()] args', args);
665+
const {{ tableIdx, elemMeta, hostInjectFn }} = args;
666+
if (tableIdx === undefined) {{ throw new Error("missing table idx while adding future"); }}
667+
if (elemMeta === undefined) {{ throw new Error("missing element metadata while adding future"); }}
668+
669+
const {{ table: futureTable, componentIdx }} = {global_future_table_map}[tableIdx];
670+
if (!futureTable) {{
671+
throw new Error(`missing global future table lookup for table [${{tableIdx}}] while creating future`);
672+
}}
673+
if (componentIdx !== this.#componentIdx) {{
674+
throw new Error('component idx mismatch while creating future');
675+
}}
676+
677+
const readWaitable = this.createWaitable();
678+
const writeWaitable = this.createWaitable();
679+
680+
const future = new {internal_future_class}({{
681+
tableIdx,
682+
componentIdx: this.#componentIdx,
683+
elemMeta,
684+
readWaitable,
685+
writeWaitable,
686+
hostInjectFn,
687+
}});
688+
future.setGlobalFutureMapRep({global_future_map}.insert(future));
689+
690+
const writeEnd = future.writeEnd();
691+
writeEnd.setWaitableIdx(this.handles.insert(writeEnd));
692+
writeEnd.setHandle(futureTable.insert(writeEnd));
693+
if (writeEnd.futureTableIdx() !== tableIdx) {{ throw new Error("unexpectedly mismatched future table"); }}
694+
695+
const writeEndWaitableIdx = writeEnd.waitableIdx();
696+
const writeEndHandle = writeEnd.handle();
697+
writeWaitable.setTarget(`waitable for future write end (waitable [${{writeEndWaitableIdx}}])`);
698+
writeEnd.setTarget(`future write end (waitable [${{writeEndWaitableIdx}}])`);
699+
700+
const readEnd = future.readEnd();
701+
readEnd.setWaitableIdx(this.handles.insert(readEnd));
702+
readEnd.setHandle(futureTable.insert(readEnd));
703+
if (readEnd.futureTableIdx() !== tableIdx) {{ throw new Error("unexpectedly mismatched future table"); }}
704+
705+
const readEndWaitableIdx = readEnd.waitableIdx();
706+
const readEndHandle = readEnd.handle();
707+
readWaitable.setTarget(`waitable for read end (waitable [${{readEndWaitableIdx}}])`);
708+
readEnd.setTarget(`future read end (waitable [${{readEndWaitableIdx}}])`);
709+
710+
return {{
711+
writeEnd,
712+
writeEndWaitableIdx,
713+
writeEndHandle,
714+
readEndWaitableIdx,
715+
readEndHandle,
716+
readEnd,
717+
}};
718+
}}
719+
720+
getFutureEnd(args) {{
721+
{debug_log_fn}('[{component_async_state_class}#getFutureEnd()] args', args);
722+
const {{ tableIdx, futureEndHandle, futureEndWaitableIdx }} = args;
723+
if (tableIdx === undefined) {{
724+
throw new Error('missing table idx while getting future end');
725+
}}
726+
727+
const {{ table, componentIdx }} = {global_future_table_map}[tableIdx];
728+
const cstate = {get_or_create_async_state_fn}(componentIdx);
729+
730+
let futureEnd;
731+
if (futureEndWaitableIdx !== undefined) {{
732+
futureEnd = cstate.handles.get(futureEndWaitableIdx);
733+
}} else if (futureEndHandle !== undefined) {{
734+
if (!table) {{ throw new Error(`missing/invalid table [${{tableIdx}}] while getting future end`); }}
735+
futureEnd = table.get(futureEndHandle);
736+
}} else {{
737+
console.log("args?", args);
738+
throw new TypeError("must specify either waitable idx or handle to retrieve future");
739+
}}
740+
741+
if (!futureEnd) {{
742+
throw new Error(`missing future end (tableIdx [${{tableIdx}}], handle [${{futureEndHandle}}], waitableIdx [${{futureEndWaitableIdx}}])`);
743+
}}
744+
if (tableIdx && futureEnd.futureTableIdx() !== tableIdx) {{
745+
throw new Error(`future end table idx [${{futureEnd.futureTableIdx()}}] does not match [${{tableIdx}}]`);
746+
}}
747+
748+
return futureEnd;
749+
}}
750+
751+
removeFutureEndFromTable(args) {{
752+
{debug_log_fn}('[{component_async_state_class}#removeFutureEndFromTable()] args', args);
753+
754+
const {{ tableIdx, futureWaitableIdx }} = args;
755+
if (tableIdx === undefined) {{ throw new Error("missing table idx while removing future end"); }}
756+
if (futureWaitableIdx === undefined) {{
757+
throw new Error("missing future end waitable idx while removing future end");
758+
}}
759+
760+
const {{ table, componentIdx }} = {global_future_table_map}[tableIdx];
761+
if (!table) {{ throw new Error(`missing/invalid table [${{tableIdx}}] while removing future end`); }}
762+
763+
const cstate = {get_or_create_async_state_fn}(componentIdx);
764+
765+
const futureEnd = cstate.handles.get(futureWaitableIdx);
766+
if (!futureEnd) {{
767+
throw new Error(`missing future end (handle [${{futureWaitableIdx}}], table [${{tableIdx}}])`);
768+
}}
769+
const handle = futureEnd.handle();
770+
771+
let removed = cstate.handles.remove(futureWaitableIdx);
772+
if (!removed) {{
773+
throw new Error(`failed to remove futureEnd from handles (waitable idx [${{futureWaitableIdx}}]), component [${{componentIdx}}])`);
774+
}}
775+
776+
removed = table.remove(handle);
777+
if (!removed) {{
778+
throw new Error(`failed to remove futureEnd from table (handle [${{handle}}]), table [${{tableIdx}}], component [${{componentIdx}}])`);
779+
}}
780+
781+
return futureEnd;
782+
}}
783+
658784
}}
659785
"#,
660786
));

0 commit comments

Comments
 (0)