Skip to content
Draft
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
197 changes: 184 additions & 13 deletions crates/js-component-bindgen/src/function_bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::mem;

use heck::{ToLowerCamelCase, ToUpperCamelCase};
use wasmtime_environ::component::{
CanonicalOptions, InterfaceType, ResourceIndex, TypeComponentLocalErrorContextTableIndex,
TypeFutureTableIndex, TypeResourceTableIndex, TypeStreamTableIndex,
CanonicalOptions, CanonicalOptionsDataModel, InterfaceType, LinearMemoryOptions, ResourceIndex,
TypeComponentLocalErrorContextTableIndex, TypeFutureTableIndex, TypeResourceTableIndex,
TypeStreamTableIndex,
};
use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
use wit_component::StringEncoding;
Expand Down Expand Up @@ -2685,19 +2686,172 @@ impl Bindgen for FunctionBindgen<'_> {
results.push(item.clone());
}

Instruction::FutureLower { .. } => {
// TODO: convert this return of the lifted Future:
//
// ```
// return BigInt(writeEndWaitableIdx) << 32n | BigInt(readEndWaitableIdx);
// ```
//
// Into a component-local Future instance
//
Instruction::FutureLower { ty, .. } => {
let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
ComponentIntrinsic::GetOrCreateAsyncState,
));
let gen_future_host_inject_fn = self.intrinsic(Intrinsic::AsyncFuture(
AsyncFutureIntrinsic::GenFutureHostInjectFn,
));
let is_future_lowerable_object_fn = self.intrinsic(Intrinsic::AsyncFuture(
AsyncFutureIntrinsic::IsFutureLowerableObject,
));

let component_idx = self.canon_opts.instance.as_u32();

let future_arg = operands
.first()
.expect("unexpectedly missing ErrorContextLower arg");
results.push(future_arg.clone());

// Build the lowering function for the type produced by the future
let type_id = &crate::dealias(self.resolve, *ty);
let ResourceTable {
imported: true,
data:
ResourceData::Guest {
extra:
Some(ResourceExtraData::Future {
table_idx: future_table_idx_ty,
elem_ty,
}),
..
},
} = self
.resource_map
.get(type_id)
.expect("missing resource mapping for future lower")
else {
unreachable!("invalid resource table observed during future lower");
};
let future_table_idx = future_table_idx_ty.as_u32();

// Generate payload metadata ('elemMeta')
let (
payload_type_name_js,
lift_fn_js,
lower_fn_js,
payload_is_none,
payload_is_numeric,
payload_is_borrow,
payload_is_async_value,
payload_size32_js,
payload_align32_js,
payload_flat_count_js,
) = match elem_ty {
Some(PayloadTypeMetadata {
ty: _,
iface_ty,
lift_js_expr,
lower_js_expr,
size32,
align32,
flat_count,
}) => (
format!("'{iface_ty:?}'"),
lift_js_expr.as_str(),
lower_js_expr.as_str(),
"false",
format!(
"{}",
matches!(
iface_ty,
InterfaceType::U8
| InterfaceType::U16
| InterfaceType::U32
| InterfaceType::U64
| InterfaceType::S8
| InterfaceType::S16
| InterfaceType::S32
| InterfaceType::S64
| InterfaceType::Float32
| InterfaceType::Float64
)
),
format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
format!(
"{}",
matches!(
iface_ty,
InterfaceType::Stream(_) | InterfaceType::Future(_)
)
),
size32.to_string(),
align32.to_string(),
flat_count.unwrap_or(0).to_string(),
),
None => (
"null".into(),
"() => {{ throw new Error('no lift fn'); }}",
"() => {{ throw new Error('no lower fn'); }}",
"true",
"false".into(),
"false".into(),
"false".into(),
"null".into(),
"null".into(),
"0".into(),
),
};

// Retrieve the realloc fn if present, in case lowering fns need to allocate
//
// The realloc fn is saved on the element metadata which is passed through to
// stream end and underlying buffer
let get_realloc_fn_js = match self.canon_opts.data_model {
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
realloc: Some(realloc_idx),
..
}) => format!("() => realloc{}", realloc_idx.as_u32()),
_ => "undefined".into(),
};

let tmp = self.tmp();
let lowered_future_waitable_idx = format!("futureWaitableIdx{tmp}");
uwriteln!(
self.src,
r#"
if (!{is_future_lowerable_object_fn}({future_arg})) {{
{debug_log_fn}('[Instruction::FutureLower] object is not a Promise/Thenable', {{ {future_arg} }});
throw new Error('unrecognized future object (not Promise/Thenable)');
}}

const cstate{tmp} = {get_or_create_async_state_fn}({component_idx});
if (!cstate{tmp}) {{ throw new Error(`missing component state for component [{component_idx}]`); }}

// TODO(feat): facilitate non utf8 string encoding for lowered futures
const stringEncoding = 'utf8';

const {{ writeEnd: hostWriteEnd{tmp}, readEnd: readEnd{tmp} }} = cstate{tmp}.createFuture({{
tableIdx: {future_table_idx},
elemMeta: {{
liftFn: {lift_fn_js},
lowerFn: {lower_fn_js},
payloadTypeName: {payload_type_name_js},
isNone: {payload_is_none},
isNumeric: {payload_is_numeric},
isBorrowed: {payload_is_borrow},
isAsyncValue: {payload_is_async_value},
flatCount: {payload_flat_count_js},
align32: {payload_align32_js},
size32: {payload_size32_js},
stringEncoding,
getReallocFn: {get_realloc_fn_js},
}},
}});

const hostInjectFn = {gen_future_host_inject_fn}({{
promise: {future_arg},
stringEncoding,
hostWriteEnd: hostWriteEnd{tmp},
}});
readEnd{tmp}.setHostInjectFn(hostInjectFn);

const {lowered_future_waitable_idx} = readEnd{tmp}.waitableIdx();
"#
);

results.push(lowered_future_waitable_idx);
}

Instruction::FutureLift { payload, ty } => {
Expand Down Expand Up @@ -2802,9 +2956,12 @@ impl Bindgen for FunctionBindgen<'_> {
ComponentIntrinsic::GetOrCreateAsyncState,
));
let gen_host_inject_fn = self.intrinsic(Intrinsic::AsyncStream(
AsyncStreamIntrinsic::GenHostInjectFn,
AsyncStreamIntrinsic::GenStreamHostInjectFn,
));

// TODO(???): A component could end up receiving a stream that it outputted,
// and the below would fail (imported: false)?

// Build the lowering function for the type produced by the stream
let type_id = &crate::dealias(self.resolve, *ty);
let ResourceTable {
Expand Down Expand Up @@ -2896,6 +3053,18 @@ impl Bindgen for FunctionBindgen<'_> {
),
};

// Retrieve the realloc fn if present, in case lowering fns need to allocate
//
// The realloc fn is saved on the element metadata which is passed through to
// stream end and underlying buffer
let get_realloc_fn_js = match self.canon_opts.data_model {
CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
realloc: Some(realloc_idx),
..
}) => format!("() => realloc{}", realloc_idx.as_u32()),
_ => "undefined".into(),
};

let tmp = self.tmp();
let lowered_stream_waitable_idx = format!("streamWaitableIdx{tmp}");
uwriteln!(
Expand Down Expand Up @@ -2926,6 +3095,7 @@ impl Bindgen for FunctionBindgen<'_> {
size32: {payload_size32_js},
// TODO(feat): facilitate non utf8 string encoding for lowered streams
stringEncoding: 'utf8',
geReallocFn: {get_realloc_fn_js},
}},
}});

Expand All @@ -2952,6 +3122,7 @@ impl Bindgen for FunctionBindgen<'_> {
const {lowered_stream_waitable_idx} = readEnd{tmp}.waitableIdx();
"#
);

results.push(lowered_stream_waitable_idx);
}

Expand Down
74 changes: 42 additions & 32 deletions crates/js-component-bindgen/src/intrinsics/lift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ impl LiftIntrinsic {
let val;

if (ctx.useDirectParams) {{
if (params.length === 0) {{ throw new Error('expected at least a single i32 argument'); }}
if (ctx.params.length === 0) {{ throw new Error('expected at least a single i32 argument'); }}
val = ctx.params[0];
ctx.params = ctx.params.slice(1);
return [val, ctx];
Expand Down Expand Up @@ -734,11 +734,6 @@ impl LiftIntrinsic {
return function {lift_flat_record_fn}Inner(ctx) {{
{debug_log_fn}('[{lift_flat_record_fn}()] args', {{ ctx }});

if (ctx.useDirectParams) {{
ctx.storagePtr = ctx.params[0];
ctx.params = ctx.params.slice(1);
}}

const res = {{}};
for (const [key, liftFn, _size32, _align32] of keysAndLiftFns) {{
let [val, newCtx] = liftFn(ctx);
Expand Down Expand Up @@ -847,25 +842,41 @@ impl LiftIntrinsic {

let liftResults;
if (knownLen !== undefined) {{ // list with known length

if (ctx.useDirectParams) {{
// list with known length w/ direct params
const dataPtr = ctx.params[0];
ctx.params = ctx.params.slice(1);

// TODO(???): is it possible for all values to come in from params?

ctx.useDirectParams = false;
const originalPtr = ctx.storagePtr;
ctx.storageLen = knownLen * elemSize32;
if (ctx.memory === null) {{
// If this lift should be using direct params,
// and the memory is missing, we are in the case where
// a fixed length list (or other value) is being passed only
// via parameters to the function.
//
// Normally, we would expect to use the direct parameters as a
// memory location + size, but in this case, *all* values are being passed directly,
// via params.
//
{debug_log_fn}('memory unexpectedly missing while lifting unknown length list', {{ ctx }});
liftResults = [ctx.params.slice(0, knownLen), ctx];
ctx.params = ctx.params.slice(knownLen);
}} else {{
// in-memory list with unknown length w/ direct params
const dataPtr = ctx.params[0];
ctx.params = ctx.params.slice(1);

ctx.useDirectParams = false;
const originalPtr = ctx.storagePtr;
ctx.storageLen = knownLen * elemSize32;

liftResults = readValuesAndReset(ctx, originalPtr, dataPtr, knownLen);

ctx.useDirectParams = true;
ctx.storagePtr = undefined;
ctx.storageLen = undefined;
}}
}} else {{ // indirect params
if (ctx.memory === null) {{
{debug_log_fn}('memory unexpectedly missing while lifting known length list', {{ knownLen, ctx }});
throw new Error(`memory missing while lifting known length (${{knownLen}}) list`);
}}

liftResults = readValuesAndReset(ctx, originalPtr, dataPtr, knownLen);

ctx.useDirectParams = true;
ctx.storagePtr = null;
ctx.storageLen = null;

}} else {{
ctx.storageLen = knownLen * elemSize32;
liftResults = readValuesAndReset(ctx, null, ctx.storagePtr, knownLen);
}}
Expand All @@ -885,8 +896,8 @@ impl LiftIntrinsic {
liftResults = readValuesAndReset(ctx, originalPtr, dataPtr, len);

ctx.useDirectParams = true;
ctx.storagePtr = null;
ctx.storageLen = null;
ctx.storagePtr = undefined;
ctx.storageLen = undefined;

}} else {{
// unknown length list ptr w/ in-memory params
Expand Down Expand Up @@ -1233,28 +1244,27 @@ impl LiftIntrinsic {
output.push_str(&format!(r#"
function {lift_flat_error_fn}(errCtxTableIdx, ctx) {{
{debug_log_fn}('[{lift_flat_error_fn}()] ctx', ctx);
const {{ useDirectParams, params, componentIdx }} = ctx;

let val;
let table;
if (useDirectParams) {{
if (params.length === 0) {{ throw new Error('expected at least one single i32 argument'); }}
if (ctx.useDirectParams) {{
if (ctx.params.length === 0) {{ throw new Error('expected at least one single i32 argument'); }}
val = ctx.params[0];
ctx.params = ctx.params.slice(1);
table = {get_err_ctx_local_table_fn}(componentIdx, errCtxTableIdx);
table = {get_err_ctx_local_table_fn}(ctx.componentIdx, errCtxTableIdx);
}} else {{
throw new Error('indirect flat lift for error-contexts not yet implemented!');
}}

let handle = table.get(val);
if (handle === undefined) {{
throw new Error(`missing error ctx (handle [${{val}}], component [${{componentIdx}}], error context table [${{errCtxTableIdx}}])`);
throw new Error(`missing error ctx (handle [${{val}}], component [${{ctx.componentIdx}}], error context table [${{errCtxTableIdx}}])`);
}}

const cstate = {get_or_create_async_state_fn}(componentIdx);
const cstate = {get_or_create_async_state_fn}(ctx.componentIdx);
const errCtx = cstate.handles.get(handle);
if (!errCtx || errCtx.globalRep === undefined || errCtx.refCount === undefined) {{
throw new Error(`malformed error context (handle [${{handle}}], component [${{componentIdx}}])`);
throw new Error(`malformed error context (handle [${{handle}}], component [${{ctx.componentIdx}}])`);
}}

errCtx.refCount -= 1;
Expand Down
4 changes: 2 additions & 2 deletions crates/js-component-bindgen/src/intrinsics/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ impl LowerIntrinsic {
function {lower_flat_f64_fn}(ctx) {{
{debug_log_fn}('[{lower_flat_f64_fn}()] args', {{ ctx }});

if (vals.length !== 1) {{ throw new Error('unexpected number of vals'); }}
if (ctx.vals.length !== 1) {{ throw new Error('unexpected number of vals'); }}

const rem = ctx.storagePtr % 8;
if (rem !== 0) {{ ctx.storagePtr += (8 - rem); }}
Expand Down Expand Up @@ -994,7 +994,7 @@ impl LowerIntrinsic {
Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenReadFnFromLowerableStream)
.name();
let gen_host_inject_fn =
Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenHostInjectFn).name();
Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenStreamHostInjectFn).name();
let lower_u32_fn = Self::LowerFlatU32.name();

output.push_str(&format!(
Expand Down
Loading
Loading