diff --git a/crates/js-component-bindgen/src/intrinsics/lift.rs b/crates/js-component-bindgen/src/intrinsics/lift.rs index ffd46edae..9dcbb6dae 100644 --- a/crates/js-component-bindgen/src/intrinsics/lift.rs +++ b/crates/js-component-bindgen/src/intrinsics/lift.rs @@ -730,15 +730,37 @@ impl LiftIntrinsic { let lift_flat_record_fn = self.name(); output.push_str(&format!( r#" - function {lift_flat_record_fn}(keysAndLiftFns) {{ + function {lift_flat_record_fn}(meta) {{ + const {{ fieldMetas, size32: recordSize32, align32: recordAlign32 }} = meta; return function {lift_flat_record_fn}Inner(ctx) {{ {debug_log_fn}('[{lift_flat_record_fn}()] args', {{ ctx }}); + const originalPtr = ctx.storagePtr; const res = {{}}; - for (const [key, liftFn, _size32, _align32] of keysAndLiftFns) {{ + for (const [key, liftFn, size32, align32] of fieldMetas) {{ + let fieldPtr; + if (ctx.storagePtr !== undefined) {{ + const rem = ctx.storagePtr % align32; + if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} + fieldPtr = ctx.storagePtr; + }} + let [val, newCtx] = liftFn(ctx); res[key] = val; ctx = newCtx; + + if (fieldPtr !== undefined) {{ + ctx.storagePtr = Math.max(ctx.storagePtr, fieldPtr + size32); + }} + }} + + if (originalPtr !== undefined) {{ + ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + recordSize32); + }} + + if (ctx.storagePtr !== undefined) {{ + const rem = ctx.storagePtr % recordAlign32; + if (rem !== 0) {{ ctx.storagePtr += recordAlign32 - rem; }} }} return [res, ctx]; @@ -777,7 +799,7 @@ impl LiftIntrinsic { caseIdx = liftRes[0]; ctx = liftRes[1]; - const [ tag, liftFn, size32, align32, payloadOffset32 ] = casesAndLiftFns[caseIdx]; + const [ tag, liftFn, size32, align32, payloadOffset32, caseFlatCount, variantFlatCount ] = casesAndLiftFns[caseIdx]; if (payloadOffset32 === undefined) {{ throw new Error('unexpectedly missing payload offset'); }} if (originalPtr !== undefined) {{ @@ -789,7 +811,9 @@ impl LiftIntrinsic { val = {{ tag }}; // NOTE: here we need to move past the entire object in memory // despite moving to the payload which we now know is missing/unnecessary - ctx.storagePtr = originalPtr + size32; + if (originalPtr !== undefined) {{ + ctx.storagePtr = originalPtr + size32; + }} }} else {{ const [newVal, newCtx] = liftFn(ctx); val = {{ tag, val: newVal }}; @@ -797,13 +821,32 @@ impl LiftIntrinsic { // NOTE: Padding can be left over after doing the lift if it was less than // space left for the payload normally. - if (ctx.storagePtr < originalPtr + size32) {{ - ctx.storagePtr = originalPtr + size32; + if (originalPtr !== undefined) {{ + ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + size32); }} }} - const rem = ctx.storagePtr % align32; - if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} + if (origUseParams) {{ + if (caseFlatCount === undefined || variantFlatCount === undefined) {{ + throw new Error('variant flat count metadata is missing'); + }} + if (caseFlatCount === null || variantFlatCount === null) {{ + throw new Error('cannot lift variant with unknown flat count'); + }} + const remainingPayloadParams = variantFlatCount - 1 - caseFlatCount; + if (remainingPayloadParams < 0) {{ + throw new Error(`invalid variant flat count metadata`); + }} + if (ctx.params.length < remainingPayloadParams) {{ + throw new Error(`expected at least [${{remainingPayloadParams}}] remaining variant payload params, but got [${{ctx.params.length}}]`); + }} + ctx.params = ctx.params.slice(remainingPayloadParams); + }} + + if (ctx.storagePtr !== undefined) {{ + const rem = ctx.storagePtr % align32; + if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} + }} return [val, ctx]; }} @@ -824,12 +867,13 @@ impl LiftIntrinsic { ctx.storagePtr = dataPtr; const val = []; for (var i = 0; i < len; i++) {{ + const elemPtr = dataPtr + i * elemSize32; + ctx.storagePtr = elemPtr; const [res, nextCtx] = elemLiftFn(ctx); val.push(res); ctx = nextCtx; - const rem = ctx.storagePtr % elemAlign32; - if (rem !== 0) {{ ctx.storagePtr += elemAlign32 - rem; }} + ctx.storagePtr = Math.max(ctx.storagePtr, elemPtr + elemSize32); }} if (originalPtr !== null) {{ ctx.storagePtr = originalPtr; }} return [val, ctx]; @@ -930,18 +974,37 @@ impl LiftIntrinsic { let lift_flat_tuple_fn = self.name(); output.push_str(&format!( " - function {lift_flat_tuple_fn}(elemLiftFns) {{ + function {lift_flat_tuple_fn}(meta) {{ + const {{ elemLiftFns, size32: tupleSize32, align32: tupleAlign32 }} = meta; return function {lift_flat_tuple_fn}Inner(ctx) {{ {debug_log_fn}('[{lift_flat_tuple_fn}()] args', {{ ctx }}); + const originalPtr = ctx.storagePtr; const val = []; for (const [ liftFn, size32, align32 ] of elemLiftFns) {{ + let elemPtr; + if (ctx.storagePtr !== undefined) {{ + const rem = ctx.storagePtr % align32; + if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} + elemPtr = ctx.storagePtr; + }} + const [newValue, newCtx] = liftFn(ctx); val.push(newValue); ctx = newCtx; - const rem = ctx.storagePtr % align32; - if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} + if (elemPtr !== undefined) {{ + ctx.storagePtr = Math.max(ctx.storagePtr, elemPtr + size32); + }} + }} + + if (originalPtr !== undefined) {{ + ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + tupleSize32); + }} + + if (ctx.storagePtr !== undefined) {{ + const rem = ctx.storagePtr % tupleAlign32; + if (rem !== 0) {{ ctx.storagePtr += tupleAlign32 - rem; }} }} return [val, ctx]; diff --git a/crates/js-component-bindgen/src/intrinsics/lower.rs b/crates/js-component-bindgen/src/intrinsics/lower.rs index f07d6ae0b..ade040515 100644 --- a/crates/js-component-bindgen/src/intrinsics/lower.rs +++ b/crates/js-component-bindgen/src/intrinsics/lower.rs @@ -610,14 +610,29 @@ impl LowerIntrinsic { output.push_str(&format!( r#" - function {lower_flat_record_fn}(fieldMetas) {{ + function {lower_flat_record_fn}(meta) {{ + const {{ fieldMetas, size32: recordSize32, align32: recordAlign32 }} = meta; return function {lower_flat_record_fn}Inner(ctx) {{ {debug_log_fn}('[{lower_flat_record_fn}()] args', {{ ctx }}); + const originalPtr = ctx.storagePtr; const r = ctx.vals[0]; for (const [tag, lowerFn, size32, align32 ] of fieldMetas) {{ + const rem = ctx.storagePtr % align32; + if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} + + const fieldPtr = ctx.storagePtr; ctx.vals = [r[tag]]; lowerFn(ctx); + + ctx.storagePtr = Math.max(ctx.storagePtr, fieldPtr + size32); + }} + + ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + recordSize32); + + const rem = ctx.storagePtr % recordAlign32; + if (rem !== 0) {{ + ctx.storagePtr += recordAlign32 - rem; }} }} }} @@ -669,16 +684,10 @@ impl LowerIntrinsic { ctx.vals = [val]; if (lowerFn) {{ lowerFn(ctx); }} - let bytesWritten = ctx.storagePtr - payloadOffsetPtr; + ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + size32); const rem = ctx.storagePtr % align32; - if (rem !== 0) {{ - const pad = align32 - rem; - ctx.storagePtr += pad; - bytesWritten += pad; - }} - - ctx.storagePtr += bytesWritten; + if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} }} }} "#)); @@ -720,16 +729,16 @@ impl LowerIntrinsic { stringEncoding: ctx.stringEncoding, }}; for (let idx = 0; idx < list.length; idx++) {{ + const elemPtr = storagePtr + idx * elemSize32; + lowerCtx.storagePtr = elemPtr; lowerCtx.vals = list.slice(idx, idx+1); elemLowerFn(lowerCtx); + lowerCtx.storagePtr = Math.max(lowerCtx.storagePtr, elemPtr + elemSize32); }} - - const bytesLowered = lowerCtx.storagePtr - ctx.storagePtr; ctx.storagePtr = lowerCtx.storagePtr; // TODO: implement parma-only known-length processing - ctx.storagePtr += bytesLowered; return; }} @@ -751,10 +760,12 @@ impl LowerIntrinsic { const origPtr = ctx.storagePtr; ctx.storagePtr = dataPtr; - ctx.storagePtr = dataPtr; - for (const elem of elems) {{ + for (const [idx, elem] of elems.entries()) {{ + const elemPtr = dataPtr + idx * elemSize32; + ctx.storagePtr = elemPtr; ctx.vals = [elem]; elemLowerFn(ctx); + ctx.storagePtr = Math.max(ctx.storagePtr, elemPtr + elemSize32); }} ctx.storagePtr = origPtr; @@ -766,9 +777,13 @@ impl LowerIntrinsic { throw new TypeError(`invalid list input of length [${{elems.length}}], must be length [${{knownLen}}]`); }} - for (const elem of elems) {{ + const originalPtr = ctx.storagePtr; + for (const [idx, elem] of elems.entries()) {{ + const elemPtr = originalPtr + idx * elemSize32; + ctx.storagePtr = elemPtr; ctx.vals = [elem]; elemLowerFn(ctx); + ctx.storagePtr = Math.max(ctx.storagePtr, elemPtr + elemSize32); }} }} @@ -789,13 +804,27 @@ impl LowerIntrinsic { output.push_str(&format!( r#" - function {lower_flat_tuple_fn}(elemLowerMetas) {{ + function {lower_flat_tuple_fn}(meta) {{ + const {{ elemLowerMetas, size32: tupleSize32, align32: tupleAlign32 }} = meta; return function {lower_flat_tuple_fn}Inner(ctx) {{ {debug_log_fn}('[{lower_flat_tuple_fn}()] args', {{ ctx }}); + const originalPtr = ctx.storagePtr; const tuple = ctx.vals[0]; for (const [idx, [ lowerFn, size32, align32 ]] of elemLowerMetas.entries()) {{ + const rem = ctx.storagePtr % align32; + if (rem !== 0) {{ ctx.storagePtr += align32 - rem; }} + + const elemPtr = ctx.storagePtr; ctx.vals = [tuple[idx]]; lowerFn(ctx); + ctx.storagePtr = Math.max(ctx.storagePtr, elemPtr + size32); + }} + + ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + tupleSize32); + + const rem = ctx.storagePtr % tupleAlign32; + if (rem !== 0) {{ + ctx.storagePtr += tupleAlign32 - rem; }} }} }} diff --git a/crates/js-component-bindgen/src/transpile_bindgen.rs b/crates/js-component-bindgen/src/transpile_bindgen.rs index 5e0c9807e..a4604468d 100644 --- a/crates/js-component-bindgen/src/transpile_bindgen.rs +++ b/crates/js-component-bindgen/src/transpile_bindgen.rs @@ -4795,6 +4795,12 @@ pub fn gen_flat_lift_fn_list_js_expr( format!("[{}]", lift_fns.join(",")) } +fn flat_count_js_expr(flat_count: &Option) -> String { + flat_count + .map(|count| count.to_string()) + .unwrap_or_else(|| "null".into()) +} + /// Generate the javascript lifting function for a given type /// /// This function will a function object that can be executed with the right @@ -4898,42 +4904,53 @@ pub fn gen_flat_lift_fn_js_expr( instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatRecord)); let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatRecord).name(); let record_ty = &component_types[*ty_idx]; + let size32 = record_ty.abi.size32; + let align32 = record_ty.abi.align32; let mut keys_and_lifts_expr = String::from("["); // For each field we build a list of [name, liftFn, 32bit alignment] // so that the record lifting function (which is a higher level function) // can properly generate a function that lifts the fields. for f in &record_ty.fields { + let field_abi = component_types.canonical_abi(&f.ty); + let field_size32 = field_abi.size32; + let field_align32 = field_abi.align32; keys_and_lifts_expr.push_str(&format!( "['{}', {}, {}, {}],", f.name.to_lower_camel_case(), gen_flat_lift_fn_js_expr(instantiator, &f.ty, extra_resource_map), - component_types.canonical_abi(ty).size32, - component_types.canonical_abi(ty).align32, + field_size32, + field_align32, )); } keys_and_lifts_expr.push(']'); - format!("{lift_fn}({keys_and_lifts_expr})") + format!( + "{lift_fn}({{ fieldMetas: {keys_and_lifts_expr}, size32: {size32}, align32: {align32} }})" + ) } InterfaceType::Variant(ty_idx) => { instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatVariant)); let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatVariant).name(); let variant_ty = &component_types[*ty_idx]; + let variant_flat_count = flat_count_js_expr(&variant_ty.abi.flat_count); let mut cases_and_lifts_expr = String::from("["); for (name, maybe_ty) in &variant_ty.cases { - let lift_args = match maybe_ty { - None => format!("['{}', null, 0, 0, 0],", name), - Some(ty) => { - format!( - "['{name}', {}, {}, {}, {}],", - gen_flat_lift_fn_js_expr(instantiator, ty, extra_resource_map), - variant_ty.abi.size32, - variant_ty.abi.align32, - variant_ty.info.payload_offset32, - ) - } + let (lift_fn_js, case_flat_count) = match maybe_ty { + Some(ty) => ( + gen_flat_lift_fn_js_expr(instantiator, ty, extra_resource_map), + flat_count_js_expr(&component_types.canonical_abi(ty).flat_count), + ), + None => ("null".into(), "0".into()), }; - cases_and_lifts_expr.push_str(&lift_args); + cases_and_lifts_expr.push_str(&format!( + "['{name}', {}, {}, {}, {}, {}, {}],", + lift_fn_js, + variant_ty.abi.size32, + variant_ty.abi.align32, + variant_ty.info.payload_offset32, + case_flat_count, + variant_flat_count, + )); } cases_and_lifts_expr.push(']'); format!("{lift_fn}({cases_and_lifts_expr})") @@ -4991,11 +5008,17 @@ pub fn gen_flat_lift_fn_js_expr( let mut elem_lifts_expr = String::from("["); for ty in &tuple_ty.types { let lift_fn_js = gen_flat_lift_fn_js_expr(instantiator, ty, extra_resource_map); - elem_lifts_expr.push_str(&format!("[{lift_fn_js}, {size_u32}, {align_u32}],")); + let elem_abi = component_types.canonical_abi(ty); + let elem_size32 = elem_abi.size32; + let elem_align32 = elem_abi.align32; + elem_lifts_expr + .push_str(&format!("[{lift_fn_js}, {elem_size32}, {elem_align32}],")); } elem_lifts_expr.push(']'); - format!("{f}({elem_lifts_expr})") + format!( + "{f}({{ elemLiftFns: {elem_lifts_expr}, size32: {size_u32}, align32: {align_u32} }})" + ) } InterfaceType::Flags(ty_idx) => { @@ -5034,11 +5057,12 @@ pub fn gen_flat_lift_fn_js_expr( let size_32 = enum_ty.abi.size32; let align_32 = enum_ty.abi.align32; let payload_offset_32 = enum_ty.info.payload_offset32; + let flat_count = flat_count_js_expr(&enum_ty.abi.flat_count); let mut elem_lifts_expr = String::from("["); for name in &enum_ty.names { elem_lifts_expr.push_str(&format!( - "['{name}', null, {size_32}, {align_32}, {payload_offset_32}]," + "['{name}', null, {size_32}, {align_32}, {payload_offset_32}, 0, {flat_count}]," )); } elem_lifts_expr.push(']'); @@ -5053,13 +5077,16 @@ pub fn gen_flat_lift_fn_js_expr( let payload_offset_32 = option_ty.info.payload_offset32; let align_32 = option_ty.abi.align32; let size_32 = option_ty.abi.size32; + let flat_count = flat_count_js_expr(&option_ty.abi.flat_count); + let some_flat_count = + flat_count_js_expr(&component_types.canonical_abi(&option_ty.ty).flat_count); let lift_fn_js = gen_flat_lift_fn_js_expr(instantiator, &option_ty.ty, extra_resource_map); // NOTE: options are treated as variants format!( "{f}([ - ['none', null, {size_32}, {align_32}, {payload_offset_32} ], - ['some', {lift_fn_js}, {size_32}, {align_32}, {payload_offset_32} ], + ['none', null, {size_32}, {align_32}, {payload_offset_32}, 0, {flat_count} ], + ['some', {lift_fn_js}, {size_32}, {align_32}, {payload_offset_32}, {some_flat_count}, {flat_count} ], ])" ) } @@ -5068,30 +5095,51 @@ pub fn gen_flat_lift_fn_js_expr( instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatResult)); let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatResult).name(); let result_ty = &component_types[*ty_idx]; + let result_flat_count = flat_count_js_expr(&result_ty.abi.flat_count); let mut cases_and_lifts_expr = String::from("["); if let Some(ok_ty) = result_ty.ok { + let ok_flat_count = + flat_count_js_expr(&component_types.canonical_abi(&ok_ty).flat_count); cases_and_lifts_expr.push_str(&format!( - "['ok', {}, {}, {}, {}],", + "['ok', {}, {}, {}, {}, {}, {}],", gen_flat_lift_fn_js_expr(instantiator, &ok_ty, extra_resource_map), result_ty.abi.size32, result_ty.abi.align32, result_ty.info.payload_offset32, + ok_flat_count, + result_flat_count, )) } else { - cases_and_lifts_expr.push_str("['ok', null, 0, 0, 0],"); + cases_and_lifts_expr.push_str(&format!( + "['ok', null, {}, {}, {}, 0, {}],", + result_ty.abi.size32, + result_ty.abi.align32, + result_ty.info.payload_offset32, + result_flat_count, + )); } if let Some(err_ty) = &result_ty.err { + let err_flat_count = + flat_count_js_expr(&component_types.canonical_abi(err_ty).flat_count); cases_and_lifts_expr.push_str(&format!( - "['err', {}, {}, {}, {}],", + "['err', {}, {}, {}, {}, {}, {}],", gen_flat_lift_fn_js_expr(instantiator, err_ty, extra_resource_map), result_ty.abi.size32, result_ty.abi.align32, result_ty.info.payload_offset32, + err_flat_count, + result_flat_count, )) } else { - cases_and_lifts_expr.push_str("['err', null, 0, 0, 0],"); + cases_and_lifts_expr.push_str(&format!( + "['err', null, {}, {}, {}, 0, {}],", + result_ty.abi.size32, + result_ty.abi.align32, + result_ty.info.payload_offset32, + result_flat_count, + )); } cases_and_lifts_expr.push(']'); @@ -5379,21 +5427,28 @@ pub fn gen_flat_lower_fn_js_expr( instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatRecord)); let lower_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatRecord).name(); let record_ty = &component_types[*ty_idx]; + let size32 = record_ty.abi.size32; + let align32 = record_ty.abi.align32; let mut keys_and_lowers_expr = String::from("["); for f in &record_ty.fields { // For each field we build a list of [name, lowerFn, 32bit alignment] // so that the record lowering function (which is a higher level function) // can properly generate a function that lowers the fields. + let field_abi = component_types.canonical_abi(&f.ty); + let field_size32 = field_abi.size32; + let field_align32 = field_abi.align32; keys_and_lowers_expr.push_str(&format!( "['{}', {}, {}, {} ],", f.name.to_lower_camel_case(), gen_flat_lower_fn_js_expr(instantiator, &f.ty, &None), - component_types.canonical_abi(ty).size32, - component_types.canonical_abi(ty).align32, + field_size32, + field_align32, )); } keys_and_lowers_expr.push(']'); - format!("{lower_fn}({keys_and_lowers_expr})") + format!( + "{lower_fn}({{ fieldMetas: {keys_and_lowers_expr}, size32: {size32}, align32: {align32} }})" + ) } InterfaceType::Variant(ty_idx) => { @@ -5472,11 +5527,17 @@ pub fn gen_flat_lower_fn_js_expr( let mut elem_lowers_expr = String::from("["); for ty in &tuple_ty.types { let lower_fn_js = gen_flat_lower_fn_js_expr(instantiator, ty, extra_resource_map); - elem_lowers_expr.push_str(&format!("[{lower_fn_js}, {size_u32}, {align_u32}],")); + let elem_abi = component_types.canonical_abi(ty); + let elem_size32 = elem_abi.size32; + let elem_align32 = elem_abi.align32; + elem_lowers_expr + .push_str(&format!("[{lower_fn_js}, {elem_size32}, {elem_align32}],")); } elem_lowers_expr.push(']'); - format!("{f}({elem_lowers_expr})") + format!( + "{f}({{ elemLowerMetas: {elem_lowers_expr}, size32: {size_u32}, align32: {align_u32} }})" + ) } InterfaceType::Flags(ty_idx) => { diff --git a/crates/test-components/src/bin/async_simple_return.rs b/crates/test-components/src/bin/async_simple_return.rs index bb5e1138d..ca3804e0b 100644 --- a/crates/test-components/src/bin/async_simple_return.rs +++ b/crates/test-components/src/bin/async_simple_return.rs @@ -16,6 +16,10 @@ impl bindings::Guest for Component { async fn get_string() -> String { "Hello World!".into() } + + async fn get_layout_variant_and_u32() -> (bindings::LayoutVariant, u32) { + (bindings::LayoutVariant::Empty, 42) + } } // Stub only to ensure this works as a binary diff --git a/crates/test-components/src/bin/stream_lower.rs b/crates/test-components/src/bin/stream_lower.rs index 7d189eea3..cfc4c1dab 100644 --- a/crates/test-components/src/bin/stream_lower.rs +++ b/crates/test-components/src/bin/stream_lower.rs @@ -13,7 +13,8 @@ use bindings::exports::jco::test_components::stream_lower_sync; use bindings::jco::test_components::resources; use bindings::exports::jco::test_components::stream_lower_async::{ - ExampleEnum, ExampleFlags, ExampleRecord, ExampleVariant, + ExampleEnum, ExampleFlags, ExampleRecord, ExampleVariant, LayoutVariant, PaddedRecord, + VariantStringRecord, }; struct Component; @@ -85,6 +86,18 @@ impl stream_lower_async::Guest for Component { read_async_values(rx).await } + async fn read_stream_values_layout_variant( + rx: StreamReader, + ) -> Vec { + read_async_values(rx).await + } + + async fn read_stream_values_variant_string_record( + rx: StreamReader, + ) -> Vec { + read_async_values(rx).await + } + async fn read_stream_values_option_string( rx: StreamReader>, ) -> Vec> { @@ -103,6 +116,10 @@ impl stream_lower_async::Guest for Component { read_async_values(rx).await } + async fn read_stream_values_tight_tuple(rx: StreamReader<(u8, u8, u32)>) -> Vec<(u8, u8, u32)> { + read_async_values(rx).await + } + async fn read_stream_values_flags(rx: StreamReader) -> Vec { read_async_values(rx).await } @@ -131,6 +148,12 @@ impl stream_lower_async::Guest for Component { read_async_values(rx).await } + async fn read_stream_values_list_padded_record( + rx: StreamReader>, + ) -> Vec> { + read_async_values(rx).await + } + async fn read_stream_values_example_resource_own(rx: StreamReader) { let _ = read_async_values(rx).await; // All vals dropped at the end of this function diff --git a/crates/test-components/src/bin/stream_tx.rs b/crates/test-components/src/bin/stream_tx.rs index 896ed11c6..6add19781 100644 --- a/crates/test-components/src/bin/stream_tx.rs +++ b/crates/test-components/src/bin/stream_tx.rs @@ -117,10 +117,26 @@ impl get_stream_async::Guest for Component { stream_values_async(vals) } + async fn get_stream_layout_variant( + vals: Vec, + ) -> StreamReader { + stream_values_async(vals) + } + + async fn get_stream_variant_string_record( + vals: Vec, + ) -> StreamReader { + stream_values_async(vals) + } + async fn get_stream_tuple(vals: Vec<(u32, i32, String)>) -> StreamReader<(u32, i32, String)> { stream_values_async(vals) } + async fn get_stream_tight_tuple(vals: Vec<(u8, u8, u32)>) -> StreamReader<(u8, u8, u32)> { + stream_values_async(vals) + } + async fn get_stream_flags( vals: Vec, ) -> StreamReader { @@ -157,6 +173,12 @@ impl get_stream_async::Guest for Component { stream_values_async(vals) } + async fn get_stream_list_padded_record( + vals: Vec>, + ) -> StreamReader> { + stream_values_async(vals) + } + async fn get_stream_fixed_list_u32(vals: Vec<[u32; 5]>) -> StreamReader<[u32; 5]> { stream_values_async(vals) } diff --git a/crates/test-components/wit/all.wit b/crates/test-components/wit/all.wit index 446dfa7b8..ec246bbb3 100644 --- a/crates/test-components/wit/all.wit +++ b/crates/test-components/wit/all.wit @@ -31,6 +31,11 @@ interface example-types { maybe-u32(option), } + variant layout-variant { + empty, + name(string), + } + enum example-enum { first, second, @@ -42,6 +47,16 @@ interface example-types { id-str: string, } + record variant-string-record { + kind: layout-variant, + name: string, + } + + record padded-record { + id: u32, + byte: u8, + } + flags example-flags { first, second, @@ -51,7 +66,15 @@ interface example-types { interface get-stream-async { use resources.{example-resource}; - use example-types.{example-variant, example-enum, example-record, example-flags}; + use example-types.{ + example-variant, + example-enum, + example-record, + example-flags, + layout-variant, + variant-string-record, + padded-record, + }; resource example-guest-resource { constructor(id: u32); @@ -86,6 +109,9 @@ interface get-stream-async { get-stream-record: async func(vals: list) -> stream; get-stream-variant: async func(vals: list) -> stream; + get-stream-layout-variant: async func(vals: list) -> stream; + get-stream-variant-string-record: async func(vals: list) -> stream; + get-stream-list-padded-record: async func(vals: list>) -> stream>; get-stream-list-u8: async func(vals: list>) -> stream>; get-stream-list-string: async func(vals: list>) -> stream>; @@ -93,6 +119,7 @@ interface get-stream-async { get-stream-fixed-list-u32: async func(vals: list>) -> stream>; get-stream-tuple: async func(vals: list>) -> stream>; + get-stream-tight-tuple: async func(vals: list>) -> stream>; get-stream-flags: async func(vals: list) -> stream; @@ -209,8 +236,11 @@ world async-flat-param-adder { } world async-simple-return { + use example-types.{layout-variant}; + export get-u32: async func() -> u32; export get-string: async func() -> string; + export get-layout-variant-and-u32: async func() -> tuple; } world stream-tx { @@ -289,7 +319,15 @@ interface stream-lower-sync { interface stream-lower-async { use resources.{example-resource}; - use example-types.{example-variant, example-enum, example-record, example-flags}; + use example-types.{ + example-variant, + example-enum, + example-record, + example-flags, + layout-variant, + variant-string-record, + padded-record, + }; stream-passthrough: async func(s: stream) -> stream; @@ -316,8 +354,11 @@ interface stream-lower-async { read-stream-values-record: async func(s: stream) -> list; read-stream-values-variant: async func(s: stream) -> list; + read-stream-values-layout-variant: async func(s: stream) -> list; + read-stream-values-variant-string-record: async func(s: stream) -> list; read-stream-values-tuple: async func(s: stream>) -> list>; + read-stream-values-tight-tuple: async func(s: stream>) -> list>; read-stream-values-flags: async func(s: stream) -> list; @@ -331,6 +372,7 @@ interface stream-lower-async { read-stream-values-list-string: async func(s: stream>) -> list>; read-stream-values-fixed-list-u32: async func(s: stream>>) -> list>>; read-stream-values-list-record: async func(s: stream>) -> list>; + read-stream-values-list-padded-record: async func(s: stream>) -> list>; read-stream-values-example-resource-own: async func(s: stream); diff --git a/packages/jco/test/p3/async.js b/packages/jco/test/p3/async.js index f9ce8d16b..1be6039ac 100644 --- a/packages/jco/test/p3/async.js +++ b/packages/jco/test/p3/async.js @@ -51,6 +51,9 @@ suite("Async (WASI P3)", () => { assert.instanceOf(instance.getU32, AsyncFunction); assert.strictEqual(42, await instance.getU32()); + assert.instanceOf(instance.getLayoutVariantAndU32, AsyncFunction); + assert.deepEqual(await instance.getLayoutVariantAndU32(), [{ tag: "empty" }, 42]); + await cleanup(); }); diff --git a/packages/jco/test/p3/stream-lifts.js b/packages/jco/test/p3/stream-lifts.js index e4d5c5796..228c68e0d 100644 --- a/packages/jco/test/p3/stream-lifts.js +++ b/packages/jco/test/p3/stream-lifts.js @@ -256,6 +256,33 @@ suite("stream lifts", () => { }); }); + test.concurrent("variant layout", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/get-stream-async"].getStreamLayoutVariant, AsyncFunction); + assert.instanceOf(instance["jco:test-components/get-stream-async"].getStreamVariantStringRecord, AsyncFunction); + + const variants = [{ tag: "empty" }, { tag: "name", val: "payload-name" }]; + let stream = await instance["jco:test-components/get-stream-async"].getStreamLayoutVariant(variants); + await checkStreamValues({ + stream, + vals: variants, + typeName: "layout-variant", + assertEqFn: assert.deepEqual, + }); + + const records = [ + { kind: { tag: "empty" }, name: "empty-name" }, + { kind: { tag: "name", val: "kind-name" }, name: "record-name" }, + ]; + stream = await instance["jco:test-components/get-stream-async"].getStreamVariantStringRecord(records); + await checkStreamValues({ + stream, + vals: records, + typeName: "variant-string-record", + assertEqFn: assert.deepEqual, + }); + }); + test.concurrent("tuple", async () => { const instance = await getInstance(); assert.instanceOf(instance["jco:test-components/get-stream-async"].getStreamTuple, AsyncFunction); @@ -269,6 +296,24 @@ suite("stream lifts", () => { await checkStreamValues({ stream, vals, typeName: "tuple", assertEqFn: assert.deepEqual }); }); + test.concurrent("tight tuple layout", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/get-stream-async"].getStreamTightTuple, AsyncFunction); + + const vals = [ + [1, 2, 0x01020304], + [3, 4, 0x05060708], + [255, 0, 0xffffffff], + ]; + const stream = await instance["jco:test-components/get-stream-async"].getStreamTightTuple(vals); + await checkStreamValues({ + stream, + vals, + typeName: "tight-tuple", + assertEqFn: assert.deepEqual, + }); + }); + test.concurrent("flags", async () => { const instance = await getInstance(); assert.instanceOf(instance["jco:test-components/get-stream-async"].getStreamFlags, AsyncFunction); @@ -372,6 +417,29 @@ suite("stream lifts", () => { await checkStreamValues({ stream, vals, typeName: "list", assertEqFn: assert.deepEqual }); }); + test.concurrent("list", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/get-stream-async"].getStreamListPaddedRecord, AsyncFunction); + + const vals = [ + [ + { id: 1, byte: 7 }, + { id: 2, byte: 8 }, + ], + [ + { id: 0x01020304, byte: 255 }, + { id: 0x05060708, byte: 0 }, + ], + ]; + const stream = await instance["jco:test-components/get-stream-async"].getStreamListPaddedRecord(vals); + await checkStreamValues({ + stream, + vals, + typeName: "list", + assertEqFn: assert.deepEqual, + }); + }); + test.concurrent("result", async () => { const instance = await getInstance(); assert.instanceOf(instance["jco:test-components/get-stream-async"].getStreamResultString, AsyncFunction); diff --git a/packages/jco/test/p3/stream-lowers.js b/packages/jco/test/p3/stream-lowers.js index 6d3f333a4..b48725c7b 100644 --- a/packages/jco/test/p3/stream-lowers.js +++ b/packages/jco/test/p3/stream-lowers.js @@ -309,6 +309,37 @@ suite("stream lowers", () => { assert.closeTo(returnedVals[0].val, 123.1, 0.01); }); + test.concurrent("variant layout", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesLayoutVariant, + AsyncFunction, + ); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesVariantStringRecord, + AsyncFunction, + ); + + const variants = [{ tag: "empty" }, { tag: "name", val: "payload-name" }]; + assert.deepEqual( + await instance["jco:test-components/stream-lower-async"].readStreamValuesLayoutVariant( + createReadableStreamFromValues(variants), + ), + variants, + ); + + const records = [ + { kind: { tag: "empty" }, name: "empty-name" }, + { kind: { tag: "name", val: "kind-name" }, name: "record-name" }, + ]; + assert.deepEqual( + await instance["jco:test-components/stream-lower-async"].readStreamValuesVariantStringRecord( + createReadableStreamFromValues(records), + ), + records, + ); + }); + test.concurrent("tuple", async () => { const instance = await getInstance(); assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesTuple, AsyncFunction); @@ -324,6 +355,24 @@ suite("stream lowers", () => { assert.deepEqual(returnedVals, vals); }); + test.concurrent("tight tuple layout", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesTightTuple, + AsyncFunction, + ); + + const vals = [ + [1, 2, 0x01020304], + [3, 4, 0x05060708], + [255, 0, 0xffffffff], + ]; + const returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesTightTuple( + createReadableStreamFromValues(vals), + ); + assert.deepEqual(returnedVals, vals); + }); + test.concurrent("flags", async () => { const instance = await getInstance(); assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesFlags, AsyncFunction); @@ -472,6 +521,29 @@ suite("stream lowers", () => { assert.deepEqual(returnedVals, vals); }); + test.concurrent("list", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesListPaddedRecord, + AsyncFunction, + ); + + const vals = [ + [ + { id: 1, byte: 7 }, + { id: 2, byte: 8 }, + ], + [ + { id: 0x01020304, byte: 255 }, + { id: 0x05060708, byte: 0 }, + ], + ]; + const returnedVals = await instance[ + "jco:test-components/stream-lower-async" + ].readStreamValuesListPaddedRecord(createReadableStreamFromValues(vals)); + assert.deepEqual(returnedVals, vals); + }); + test.concurrent("example-resource", async () => { const instance = await getInstance(); assert.instanceOf(