From 75b07bcd02bde74f551c87194082da98967f42cf Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 5 Apr 2026 22:05:01 -0500 Subject: [PATCH 1/5] fix: prevent resource map overwrites and remove unsafe graph upgrades MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two targeted fixes for 3-component re-exporter resource chain correctness: 1. merger.rs: Change `.insert()` to `.entry().or_insert()` for resource_rep_by_component and resource_new_by_component maps. Re-exporter components have multiple [resource-rep/new] imports for the same resource name across different interfaces — the first mapping must be preserved. 2. resolver.rs: Change resource graph override to downgrade-only. The graph's `defines_resource()` returns true for re-exporters (they have ResourceRep), incorrectly upgrading callee_defines_resource from false back to true. Now the graph can only downgrade (true→false) when it has definitive evidence the component is NOT the definer, never upgrade (false→true). Co-Authored-By: Claude Opus 4.6 (1M context) --- meld-core/src/merger.rs | 16 ++++++------ meld-core/src/resolver.rs | 53 ++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/meld-core/src/merger.rs b/meld-core/src/merger.rs index 9ea8c6e..d44662a 100644 --- a/meld-core/src/merger.rs +++ b/meld-core/src/merger.rs @@ -1719,16 +1719,16 @@ impl Merger { let eff_field = &dedup_key.1; if let Some(rn) = eff_field.strip_prefix("[resource-rep]") { let bare_rn = rn.rsplit_once('$').map_or(rn, |(base, _)| base); - merged.resource_rep_by_component.insert( - (unresolved.component_idx, bare_rn.to_string()), - merged_func_idx, - ); + merged + .resource_rep_by_component + .entry((unresolved.component_idx, bare_rn.to_string())) + .or_insert(merged_func_idx); } else if let Some(rn) = eff_field.strip_prefix("[resource-new]") { let bare_rn = rn.rsplit_once('$').map_or(rn, |(base, _)| base); - merged.resource_new_by_component.insert( - (unresolved.component_idx, bare_rn.to_string()), - merged_func_idx, - ); + merged + .resource_new_by_component + .entry((unresolved.component_idx, bare_rn.to_string())) + .or_insert(merged_func_idx); } } ImportKind::Table(t) => { diff --git a/meld-core/src/resolver.rs b/meld-core/src/resolver.rs index 02c38d2..7ecaf4a 100644 --- a/meld-core/src/resolver.rs +++ b/meld-core/src/resolver.rs @@ -2279,37 +2279,39 @@ impl Resolver { ); // Graph-based override for callee_defines_resource. - // Only UPGRADE (false→true) or DOWNGRADE (true→false) - // when the graph has a definitive answer. If the graph - // has no entry, leave the heuristic value unchanged. + // Only DOWNGRADE (true→false) when the graph has a + // definitive answer that the callee does NOT define + // the resource. Never UPGRADE (false→true) — the + // heuristic's type_defs check (Import vs Defined) is + // authoritative for that direction, and upgrading + // would break re-exporters whose ResourceRep makes + // the graph think they define the resource. if let Some(ref rg) = graph.resource_graph { let iface = import_name.as_str(); for op in &mut requirements.resource_params { + if !op.callee_defines_resource { + continue; + } let rn = op .import_field .strip_prefix("[resource-rep]") .unwrap_or(&op.import_field); - if rg.defines_resource(*to_comp, iface, rn) { - op.callee_defines_resource = true; - } else if rg - .resource_definer(iface, rn) - .is_some() + if !rg.defines_resource(*to_comp, iface, rn) + && rg.resource_definer(iface, rn).is_some() { - // Graph knows about this resource and says - // this component is NOT the definer. op.callee_defines_resource = false; } } for op in &mut requirements.resource_results { + if !op.callee_defines_resource { + continue; + } let rn = op .import_field .strip_prefix("[resource-new]") .unwrap_or(&op.import_field); - if rg.defines_resource(*to_comp, iface, rn) { - op.callee_defines_resource = true; - } else if rg - .resource_definer(iface, rn) - .is_some() + if !rg.defines_resource(*to_comp, iface, rn) + && rg.resource_definer(iface, rn).is_some() { op.callee_defines_resource = false; } @@ -2525,29 +2527,34 @@ impl Resolver { true, ); - // Graph-based override for fallback path. - // Only change when the graph has a definitive answer. + // Graph-based override for fallback path (downgrade only). if let Some(ref rg) = graph.resource_graph { let iface = import_name.as_str(); for op in &mut requirements.resource_params { + if !op.callee_defines_resource { + continue; + } let rn = op .import_field .strip_prefix("[resource-rep]") .unwrap_or(&op.import_field); - if rg.defines_resource(*to_comp, iface, rn) { - op.callee_defines_resource = true; - } else if rg.resource_definer(iface, rn).is_some() { + if !rg.defines_resource(*to_comp, iface, rn) + && rg.resource_definer(iface, rn).is_some() + { op.callee_defines_resource = false; } } for op in &mut requirements.resource_results { + if !op.callee_defines_resource { + continue; + } let rn = op .import_field .strip_prefix("[resource-new]") .unwrap_or(&op.import_field); - if rg.defines_resource(*to_comp, iface, rn) { - op.callee_defines_resource = true; - } else if rg.resource_definer(iface, rn).is_some() { + if !rg.defines_resource(*to_comp, iface, rn) + && rg.resource_definer(iface, rn).is_some() + { op.callee_defines_resource = false; } } From bcf83fc564e2d78991305b4a448cb3401123154a Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 5 Apr 2026 23:18:35 -0500 Subject: [PATCH 2/5] feat: upgrade wasmparser/wasm-encoder to 0.246 with P3 async parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrade wasmparser, wasm-encoder, and wasmprinter from 0.230 to 0.246. This enables parsing of P3 (component model async) components that use task.return, async lift/lower, streams, futures, and other P3 builtins. API migration: - ImportSection::into_imports() replaces direct iteration - CM_FIXED_SIZE_LIST → CM_FIXED_LENGTH_LISTS - fixed_size_list() → fixed_length_list() - New enum variants: TypeRef::FuncExact, HeapType::Exact, CanonicalOption::Gc, ExternalKind::FuncExact, ComponentDefinedType::Map - Enable CM_ASYNC feature flag in validator P3 components are now parsed and their async features detected. Fusion correctly rejects them with a descriptive error listing each P3 feature used, pending full P3 async fusion support. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 681 ++++++++++++++----------- Cargo.toml | 6 +- meld-cli/src/main.rs | 7 +- meld-core/src/component_wrap.rs | 7 +- meld-core/src/merger.rs | 2 +- meld-core/src/parser.rs | 19 +- meld-core/src/resolver.rs | 1 + meld-core/src/rewriter.rs | 2 +- meld-core/src/segments.rs | 2 +- meld-core/tests/release_components.rs | 8 +- meld-core/tests/wit_bindgen_runtime.rs | 2 +- 11 files changed, 409 insertions(+), 328 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8709480..64c8a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -58,15 +58,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" @@ -143,9 +143,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitmaps" @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" dependencies = [ "allocator-api2", ] @@ -200,7 +200,7 @@ checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" dependencies = [ "cap-primitives", "cap-std", - "rustix 1.1.3", + "rustix 1.1.4", "smallvec", ] @@ -216,7 +216,7 @@ dependencies = [ "io-lifetimes", "ipnet", "maybe-owned", - "rustix 1.1.3", + "rustix 1.1.4", "rustix-linux-procfs", "windows-sys 0.59.0", "winx", @@ -241,7 +241,7 @@ dependencies = [ "cap-primitives", "io-extras", "io-lifetimes", - "rustix 1.1.3", + "rustix 1.1.4", ] [[package]] @@ -254,15 +254,15 @@ dependencies = [ "cap-primitives", "iana-time-zone", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "winx", ] [[package]] name = "cc" -version = "1.2.55" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -278,9 +278,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "num-traits", @@ -289,9 +289,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -299,9 +299,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cobs" @@ -338,9 +338,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "core-foundation-sys" @@ -628,9 +628,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -638,9 +638,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", @@ -673,9 +673,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" [[package]] name = "fd-lock" @@ -684,7 +684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -718,6 +718,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -734,15 +740,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" dependencies = [ "io-lifetimes", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.59.0", ] [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -754,9 +760,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -764,33 +770,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -799,7 +805,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -846,8 +851,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", "wasip2", + "wasip3", ] [[package]] @@ -867,7 +885,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", "serde", ] @@ -876,6 +894,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "heck" @@ -915,12 +938,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -928,9 +952,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -941,9 +965,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -955,15 +979,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -975,15 +999,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1037,9 +1061,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1065,9 +1089,9 @@ checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is_terminal_polyfill" @@ -1086,9 +1110,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "ittapi" @@ -1112,9 +1136,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -1125,9 +1149,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -1146,9 +1170,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "once_cell", "wasm-bindgen", @@ -1168,9 +1192,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" @@ -1180,11 +1204,10 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ - "bitflags", "libc", ] @@ -1196,15 +1219,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" @@ -1237,9 +1260,9 @@ dependencies = [ "log", "meld-core", "serde_json", - "wasm-encoder 0.230.0", - "wasmparser 0.230.0", - "wasmprinter 0.230.0", + "wasm-encoder 0.246.2", + "wasmparser 0.246.2", + "wasmprinter 0.246.2", ] [[package]] @@ -1256,9 +1279,9 @@ dependencies = [ "serde_json", "sha2", "thiserror 1.0.69", - "wasm-encoder 0.230.0", - "wasmparser 0.230.0", - "wasmprinter 0.230.0", + "wasm-encoder 0.246.2", + "wasmparser 0.246.2", + "wasmprinter 0.246.2", "wasmtime", "wasmtime-wasi", "wat", @@ -1267,9 +1290,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memfd" @@ -1277,14 +1300,14 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", ] [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -1314,9 +1337,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1354,15 +1377,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -1378,9 +1395,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] @@ -1399,9 +1416,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -1415,6 +1432,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1426,9 +1453,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", @@ -1474,9 +1501,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1487,6 +1514,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -1634,9 +1667,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-demangle" @@ -1646,9 +1679,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustix" @@ -1665,14 +1698,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -1683,7 +1716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" dependencies = [ "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", ] [[package]] @@ -1706,15 +1739,15 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -1765,9 +1798,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -1829,12 +1862,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1851,9 +1884,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1889,20 +1922,20 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -1957,9 +1990,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -1967,9 +2000,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", @@ -1981,9 +2014,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", @@ -1991,7 +2024,7 @@ dependencies = [ "toml_datetime", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -2005,18 +2038,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.1", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tracing" @@ -2063,9 +2096,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" @@ -2111,9 +2144,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "js-sys", "wasm-bindgen", @@ -2149,11 +2182,20 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -2164,9 +2206,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2174,9 +2216,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -2187,9 +2229,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -2217,39 +2259,51 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.230.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4349d0943718e6e434b51b9639e876293093dca4b96384fb136ab5bd5ce6660" +checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" dependencies = [ "leb128fmt", - "wasmparser 0.230.0", + "wasmparser 0.243.0", ] [[package]] name = "wasm-encoder" -version = "0.243.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser 0.243.0", + "wasmparser 0.244.0", ] [[package]] name = "wasm-encoder" -version = "0.244.0" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" dependencies = [ "leb128fmt", + "wasmparser 0.246.2", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.244.0", "wasmparser 0.244.0", ] [[package]] name = "wasmparser" -version = "0.230.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808198a69b5a0535583370a51d459baa14261dfab04800c4864ee9e1a14346ed" +checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" dependencies = [ "bitflags", "hashbrown 0.15.5", @@ -2260,48 +2314,49 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.243.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", "indexmap", "semver", - "serde", ] [[package]] name = "wasmparser" -version = "0.244.0" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" dependencies = [ "bitflags", + "hashbrown 0.16.1", "indexmap", "semver", + "serde", ] [[package]] name = "wasmprinter" -version = "0.230.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dc8e9a1e48f4b2247b006b3a9b0a02ba62a2e52cfcfd4bc4c70785a6104fc32" +checksum = "eb2b6035559e146114c29a909a3232928ee488d6507a1504d8934e8607b36d7b" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.230.0", + "wasmparser 0.243.0", ] [[package]] name = "wasmprinter" -version = "0.243.0" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2b6035559e146114c29a909a3232928ee488d6507a1504d8934e8607b36d7b" +checksum = "6e41f7493ba994b8a779430a4c25ff550fd5a40d291693af43a6ef48688f00e3" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.243.0", + "wasmparser 0.246.2", ] [[package]] @@ -2333,7 +2388,7 @@ dependencies = [ "postcard", "pulley-interpreter", "rayon", - "rustix 1.1.3", + "rustix 1.1.4", "semver", "serde", "serde_derive", @@ -2398,7 +2453,7 @@ dependencies = [ "directories-next", "log", "postcard", - "rustix 1.1.3", + "rustix 1.1.4", "serde", "serde_derive", "sha2", @@ -2420,7 +2475,7 @@ dependencies = [ "syn", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", - "wit-parser", + "wit-parser 0.243.0", ] [[package]] @@ -2465,7 +2520,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "rustix 1.1.3", + "rustix 1.1.4", "wasmtime-environ", "wasmtime-internal-versioned-export-macros", "windows-sys 0.61.2", @@ -2479,7 +2534,7 @@ checksum = "8e66ff7f90a8002187691ff6237ffd09f954a0ebb9de8b2ff7f5c62632134120" dependencies = [ "cc", "object", - "rustix 1.1.3", + "rustix 1.1.4", "wasmtime-internal-versioned-export-macros", ] @@ -2561,7 +2616,7 @@ dependencies = [ "bitflags", "heck", "indexmap", - "wit-parser", + "wit-parser 0.243.0", ] [[package]] @@ -2583,7 +2638,7 @@ dependencies = [ "futures", "io-extras", "io-lifetimes", - "rustix 1.1.3", + "rustix 1.1.4", "system-interface", "thiserror 2.0.18", "tokio", @@ -2619,24 +2674,24 @@ dependencies = [ [[package]] name = "wast" -version = "244.0.0" +version = "246.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e7b9f9e23311275920e3d6b56d64137c160cf8af4f84a7283b36cfecbf4acb" +checksum = "fe3fe8e3bf88ad96d031b4181ddbd64634b17cb0d06dfc3de589ef43591a9a62" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.244.0", + "wasm-encoder 0.246.2", ] [[package]] name = "wat" -version = "1.244.0" +version = "1.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf35b87ed352f9ab6cd0732abde5a67dd6153dfd02c493e61459218b19456fa" +checksum = "4bd7fda1199b94fff395c2d19a153f05dbe7807630316fa9673367666fd2ad8c" dependencies = [ - "wast 244.0.0", + "wast 246.0.2", ] [[package]] @@ -2795,16 +2850,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -2822,31 +2868,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -2855,84 +2884,42 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2940,16 +2927,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" +name = "winnow" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" [[package]] name = "winx" @@ -2966,6 +2953,70 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] [[package]] name = "wit-parser" @@ -2985,6 +3036,24 @@ dependencies = [ "wasmparser 0.243.0", ] +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + [[package]] name = "witx" version = "0.9.1" @@ -2999,9 +3068,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wsc-attestation" @@ -3020,9 +3089,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -3031,9 +3100,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -3043,18 +3112,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.38" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.38" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -3063,18 +3132,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -3084,9 +3153,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -3095,9 +3164,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -3106,9 +3175,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -3117,9 +3186,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index c6e6dc6..ba59ecf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,9 @@ rust-version = "1.85" [workspace.dependencies] # WebAssembly parsing and encoding -wasmparser = { version = "0.230", features = ["component-model"] } -wasm-encoder = { version = "0.230", features = ["component-model"] } -wasmprinter = "0.230" +wasmparser = { version = "0.246", features = ["component-model"] } +wasm-encoder = { version = "0.246", features = ["component-model"] } +wasmprinter = "0.246" # CLI clap = { version = "4.5", features = ["derive", "cargo"] } diff --git a/meld-cli/src/main.rs b/meld-cli/src/main.rs index 1bd6b24..797d527 100644 --- a/meld-cli/src/main.rs +++ b/meld-cli/src/main.rs @@ -449,9 +449,12 @@ fn write_import_map(wasm_bytes: &[u8], path: &str) -> Result<()> { for payload in parser.parse_all(wasm_bytes) { let payload = payload.context("Parse error while reading imports")?; if let Payload::ImportSection(reader) = payload { - for import in reader { + for import in reader.into_imports() { let import = import.context("Failed to read import entry")?; - if matches!(import.ty, wasmparser::TypeRef::Func(_)) { + if matches!( + import.ty, + wasmparser::TypeRef::Func(_) | wasmparser::TypeRef::FuncExact(_) + ) { imports.push(serde_json::json!({ "index": func_index, "module": import.module, diff --git a/meld-core/src/component_wrap.rs b/meld-core/src/component_wrap.rs index 1cc6563..eee6757 100644 --- a/meld-core/src/component_wrap.rs +++ b/meld-core/src/component_wrap.rs @@ -198,7 +198,7 @@ fn parse_fused_module(bytes: &[u8]) -> Result { } } wasmparser::Payload::ImportSection(reader) => { - for imp in reader { + for imp in reader.into_imports() { let imp = imp.map_err(|e| Error::ParseError(e.to_string()))?; if let wasmparser::TypeRef::Func(type_idx) = imp.ty { func_imports.push((imp.module.to_string(), imp.name.to_string(), type_idx)); @@ -572,7 +572,7 @@ fn convert_memory_to_import(original_bytes: &[u8], info: &FusedModuleInfo) -> Re wasmparser::Payload::ImportSection(reader) => { let mut imports = ImportSection::new(); // Re-emit all original imports - for imp in reader { + for imp in reader.into_imports() { let imp = imp.map_err(|e| Error::ParseError(e.to_string()))?; let entity = convert_type_ref(imp.ty)?; imports.import(imp.module, imp.name, entity); @@ -1983,7 +1983,7 @@ fn emit_defined_type( component_type_idx, type_remap, )?; - types.defined_type().fixed_size_list(elem_enc, *len); + types.defined_type().fixed_length_list(elem_enc, *len); } parser::ComponentValType::Option(inner) => { let inner_enc = convert_parser_val_to_encoder( @@ -2201,6 +2201,7 @@ fn convert_type_ref(ty: wasmparser::TypeRef) -> Result wasmparser::TypeRef::Tag(_) => Err(Error::UnsupportedFeature( "exception handling tags".to_string(), )), + wasmparser::TypeRef::FuncExact(idx) => Ok(wasm_encoder::EntityType::Function(idx)), } } diff --git a/meld-core/src/merger.rs b/meld-core/src/merger.rs index d44662a..ddad4cc 100644 --- a/meld-core/src/merger.rs +++ b/meld-core/src/merger.rs @@ -2330,7 +2330,7 @@ fn convert_init_expr( shared, ty: convert_abstract_heap_type(ty), }, - wasmparser::HeapType::Concrete(idx) => { + wasmparser::HeapType::Concrete(idx) | wasmparser::HeapType::Exact(idx) => { let old_idx = idx.as_module_index().unwrap_or(0); let new_idx = merged .type_index_map diff --git a/meld-core/src/parser.rs b/meld-core/src/parser.rs index 334293f..d36dcc9 100644 --- a/meld-core/src/parser.rs +++ b/meld-core/src/parser.rs @@ -581,8 +581,9 @@ impl ComponentParser { } if self.validate { - let features = - wasmparser::WasmFeatures::default() | wasmparser::WasmFeatures::CM_FIXED_SIZE_LIST; + let features = wasmparser::WasmFeatures::default() + | wasmparser::WasmFeatures::CM_FIXED_LENGTH_LISTS + | wasmparser::WasmFeatures::CM_ASYNC; let mut validator = wasmparser::Validator::new_with_features(features); validator.validate_all(bytes)?; } @@ -1042,10 +1043,11 @@ impl ComponentParser { } Payload::ImportSection(reader) => { - for import in reader { + for import in reader.into_imports() { let import = import?; let kind = match import.ty { - wasmparser::TypeRef::Func(idx) => ImportKind::Function(idx), + wasmparser::TypeRef::Func(idx) + | wasmparser::TypeRef::FuncExact(idx) => ImportKind::Function(idx), wasmparser::TypeRef::Table(t) => ImportKind::Table(TableType { element_type: convert_ref_type(t.element_type), initial: t.initial, @@ -1129,7 +1131,8 @@ impl ComponentParser { for export in reader { let export = export?; let kind = match export.kind { - wasmparser::ExternalKind::Func => ExportKind::Function, + wasmparser::ExternalKind::Func + | wasmparser::ExternalKind::FuncExact => ExportKind::Function, wasmparser::ExternalKind::Table => ExportKind::Table, wasmparser::ExternalKind::Memory => ExportKind::Memory, wasmparser::ExternalKind::Global => ExportKind::Global, @@ -2867,7 +2870,7 @@ fn convert_wp_defined_type(dt: &wasmparser::ComponentDefinedType) -> ComponentTy .collect(), )) } - wasmparser::ComponentDefinedType::FixedSizeList(ty, len) => ComponentTypeKind::Defined( + wasmparser::ComponentDefinedType::FixedLengthList(ty, len) => ComponentTypeKind::Defined( ComponentValType::FixedSizeList(Box::new(convert_wp_component_val_type(ty)), *len), ), // P3 async types — detected and flagged, not silently swallowed @@ -2885,6 +2888,9 @@ fn convert_wp_defined_type(dt: &wasmparser::ComponentDefinedType) -> ComponentTy }; ComponentTypeKind::P3Async(desc) } + wasmparser::ComponentDefinedType::Map(key_ty, val_ty) => { + ComponentTypeKind::P3Async(format!("map<{key_ty:?}, {val_ty:?}>")) + } } } @@ -2929,6 +2935,7 @@ fn convert_canonical_options( } } wasmparser::CanonicalOption::CoreType(_) => {} + wasmparser::CanonicalOption::Gc => {} } } result diff --git a/meld-core/src/resolver.rs b/meld-core/src/resolver.rs index 7ecaf4a..4c37ebe 100644 --- a/meld-core/src/resolver.rs +++ b/meld-core/src/resolver.rs @@ -1626,6 +1626,7 @@ impl Resolver { wasmparser::ExternalKind::Memory => ExportKind::Memory, wasmparser::ExternalKind::Global => ExportKind::Global, wasmparser::ExternalKind::Tag => ExportKind::Function, + wasmparser::ExternalKind::FuncExact => ExportKind::Function, }; (name.clone(), export_kind, *idx) }) diff --git a/meld-core/src/rewriter.rs b/meld-core/src/rewriter.rs index a724721..6beb855 100644 --- a/meld-core/src/rewriter.rs +++ b/meld-core/src/rewriter.rs @@ -626,7 +626,7 @@ fn convert_ref_type(rt: wasmparser::RefType, maps: &IndexMaps) -> Result Result { match ht { - wasmparser::HeapType::Concrete(idx) => { + wasmparser::HeapType::Concrete(idx) | wasmparser::HeapType::Exact(idx) => { // Extract the module-level type index and remap it. // wasmparser's UnpackedIndex can be Module(u32), RecGroup(u32), or // Id(CoreTypeId). Only Module indices are valid here -- RecGroup and diff --git a/meld-core/src/segments.rs b/meld-core/src/segments.rs index 3197744..4e4d2b0 100644 --- a/meld-core/src/segments.rs +++ b/meld-core/src/segments.rs @@ -401,7 +401,7 @@ fn parse_const_expr_with_value( shared, ty: convert_abstract_heap_type(ty), }, - wasmparser::HeapType::Concrete(idx) => { + wasmparser::HeapType::Concrete(idx) | wasmparser::HeapType::Exact(idx) => { // Preserve the concrete type index. It will be remapped // later during reindexing if needed. For initial parsing, // extract the module-level index. diff --git a/meld-core/tests/release_components.rs b/meld-core/tests/release_components.rs index e65d155..3d2a2d3 100644 --- a/meld-core/tests/release_components.rs +++ b/meld-core/tests/release_components.rs @@ -421,7 +421,7 @@ fn test_reasonable_memory_count() { memory_count += reader.count(); } Ok(wasmparser::Payload::ImportSection(reader)) => { - for imp in reader.into_iter().flatten() { + for imp in reader.into_imports().flatten() { if matches!(imp.ty, wasmparser::TypeRef::Memory(_)) { memory_count += 1; } @@ -568,7 +568,7 @@ fn test_no_duplicate_imports() { for payload in parser.parse_all(&fused) { if let Ok(wasmparser::Payload::ImportSection(reader)) = payload { - for imp in reader.into_iter().flatten() { + for imp in reader.into_imports().flatten() { let key = (imp.module.to_string(), imp.name.to_string()); if !seen.insert(key.clone()) { duplicates.push(key); @@ -721,7 +721,7 @@ fn test_adapter_call_site_wiring() { for payload in parser.parse_all(&fused) { match payload { Ok(wasmparser::Payload::ImportSection(reader)) => { - for imp in reader.into_iter().flatten() { + for imp in reader.into_imports().flatten() { match imp.ty { wasmparser::TypeRef::Func(_) => import_func_count += 1, wasmparser::TypeRef::Memory(_) => import_mem_count += 1, @@ -866,7 +866,7 @@ fn test_no_stale_resource_drop_versions() { for payload in parser.parse_all(&fused) { if let Ok(wasmparser::Payload::ImportSection(reader)) = payload { - for imp in reader.into_iter().flatten() { + for imp in reader.into_imports().flatten() { // Check for resource-drop imports with exactly @0.2.0 // (the stale adapter version). Exclude pre-release // versions like @0.2.0-rc-* which are legitimate. diff --git a/meld-core/tests/wit_bindgen_runtime.rs b/meld-core/tests/wit_bindgen_runtime.rs index a17e31e..6d9cc7d 100644 --- a/meld-core/tests/wit_bindgen_runtime.rs +++ b/meld-core/tests/wit_bindgen_runtime.rs @@ -442,7 +442,7 @@ fn test_fuse_component_wit_bindgen_fixed_length_lists() { let fused = fuse_fixture("fixed-length-lists", OutputFormat::Component) .expect("fixed-length-lists: component fusion should succeed"); let features = - wasmparser::WasmFeatures::default() | wasmparser::WasmFeatures::CM_FIXED_SIZE_LIST; + wasmparser::WasmFeatures::default() | wasmparser::WasmFeatures::CM_FIXED_LENGTH_LISTS; wasmparser::Validator::new_with_features(features) .validate_all(&fused) .expect("fixed-length-lists: fused component should validate"); From 613e5d7c6283a2a88dd67cf50373b2df67761daf Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 6 Apr 2026 07:29:31 -0500 Subject: [PATCH 3/5] feat: implement P3 async component fusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P3 components with async canonical operations (task.return, context.get/set, waitable-set.*, waitable.join, task.cancel) now fuse to valid core modules and component wrappers. Parser: store async/callback in CanonicalOptions, add ~31 CanonicalEntry variants for P3 builtins, fix creates_core_func for all P3 entries. Resolver: extend canon import name mapping for P3 entries, handle [async-lift] prefixed exports. Adapter: use callee_type_idx for non-retptr adapters so body matches callee's i32 task handle return type. Lib: add P3 async widening wrappers in wire_adapter_indices — when caller expects i64 but adapter returns i32, a thin wrapper with i64.extend_i32_u bridges the gap. Correct index arithmetic places wrappers before adapters. Component wrapper: add TaskBuiltin import resolution with P3BuiltinOp enum, emit canon task.return/context.get/set/waitable-set.*/waitable.join/ task.cancel, propagate async/callback options on canon lift exports. Validated: P3 core module + component wrapper pass wasm-tools validate (--features cm-async). All 73 P2 runtime tests + 11 release tests pass. Known limitation: P3 async runtime requires task context bridging for internal cross-component calls (canon lower elimination removes subtask context). Tracked as follow-up. Co-Authored-By: Claude Opus 4.6 (1M context) --- meld-core/src/adapter/fact.rs | 6 +- meld-core/src/component_wrap.rs | 568 +++++++++++++++++++++++++++++++- meld-core/src/lib.rs | 123 +++++-- meld-core/src/parser.rs | 275 +++++++++++----- meld-core/src/resolver.rs | 124 ++++++- 5 files changed, 973 insertions(+), 123 deletions(-) diff --git a/meld-core/src/adapter/fact.rs b/meld-core/src/adapter/fact.rs index 95bf31e..758e3dd 100644 --- a/meld-core/src/adapter/fact.rs +++ b/meld-core/src/adapter/fact.rs @@ -666,8 +666,10 @@ impl FactStyleGenerator { return self.generate_params_ptr_adapter(site, options, target_func, caller_type_idx); } - // --- Non-retptr path: use caller's type for declared signature --- - let adapter_type_idx = caller_type_idx; + // --- Non-retptr path: use callee's type so body is valid --- + // wire_adapter_indices generates a widening wrapper if caller expects + // wider result types (P3 async i64 vs i32). + let adapter_type_idx = callee_type_idx; let param_count = callee_param_count; let result_count = callee_result_count; let result_types = callee_result_types; diff --git a/meld-core/src/component_wrap.rs b/meld-core/src/component_wrap.rs index eee6757..be89ccc 100644 --- a/meld-core/src/component_wrap.rs +++ b/meld-core/src/component_wrap.rs @@ -105,6 +105,7 @@ pub fn wrap_as_component( &fused_info, merged, memory_strategy, + components, ) } @@ -157,6 +158,16 @@ enum ImportResolution { #[allow(dead_code)] component_idx: Option, }, + /// Import resolves to a P3 task/async canonical built-in. + /// + /// These are emitted as `canon task.return`, `canon context.get`, etc. + /// They appear in the fused module as imports from `[export]` modules + /// (for `[task-return]`) or from `$root` (for runtime builtins like + /// `[context-get-0]`, `[waitable-set-new]`, etc.). + TaskBuiltin { + /// The specific P3 canonical operation to emit. + op: P3BuiltinOp, + }, } /// A canonical resource operation. @@ -167,6 +178,44 @@ enum ResourceOp { Rep, } +/// A P3 task/async canonical built-in operation. +/// +/// Each variant corresponds to a `canon` instruction in the component model +/// for P3 async primitives. The fused core module imports these as regular +/// functions; the component wrapper re-emits them as canonical operations. +#[derive(Debug, Clone)] +enum P3BuiltinOp { + /// `canon task.return` — return a result from a lifted async export. + /// The associated `CanonicalEntry::TaskReturn` from one of the parsed + /// components provides the result type and options. + TaskReturn { + /// `(component_index, canon_index)` into the all_components array + /// and that component's canonical_functions, for recovering the + /// result type and options. + source_location: Option<(usize, usize)>, + }, + /// `canon task.cancel` — acknowledge cancellation of the current task. + TaskCancel, + /// `canon context.get ` — get task-local context slot. + ContextGet(u32), + /// `canon context.set ` — set task-local context slot. + ContextSet(u32), + /// `canon waitable.join` — add an item to a waitable-set. + WaitableJoin, + /// `canon waitable-set.new` — create a waitable-set pseudo-resource. + WaitableSetNew, + /// `canon waitable-set.drop` — dispose a waitable-set pseudo-resource. + WaitableSetDrop, + /// `canon waitable-set.poll` — non-blocking check on a waitable-set. + WaitableSetPoll, + /// `canon backpressure.inc` — increment the backpressure counter. + BackpressureInc, + /// `canon backpressure.dec` — decrement the backpressure counter. + BackpressureDec, + /// `canon subtask.drop` — drop a completed subtask. + SubtaskDrop, +} + /// Parse the fused module to extract structural info needed for wrapping. fn parse_fused_module(bytes: &[u8]) -> Result { let parser = wasmparser::Parser::new(0); @@ -713,6 +762,7 @@ fn assemble_component( fused_info: &FusedModuleInfo, merged: &MergedModule, memory_strategy: MemoryStrategy, + all_components: &[ParsedComponent], ) -> Result> { use wasm_encoder::*; @@ -755,7 +805,34 @@ fn assemble_component( .and_then(|imp| imp.component_idx); // Category A: [export]-prefixed modules provide canon resource operations - if let Some(inner_module) = module_name.strip_prefix("[export]") { + // OR P3 task built-ins ([task-return], [task-cancel], etc.) + if let Some(_inner_module) = module_name.strip_prefix("[export]") { + // Check for P3 task built-in first (before resource ops) + if let Some(p3_op) = parse_p3_builtin_field(field_name) { + // For task-return, find the matching source canon entry by + // comparing the core function type signature. Use the source + // component index from the merged import metadata to narrow + // the search. + let op = match p3_op { + P3BuiltinOp::TaskReturn { .. } => { + let import_ty = fused_info + .func_types + .get(*_type_idx as usize) + .cloned() + .unwrap_or_default(); + let location = + find_task_return_for_import(all_components, comp_idx, &import_ty); + P3BuiltinOp::TaskReturn { + source_location: location, + } + } + other => other, + }; + import_resolutions.push(ImportResolution::TaskBuiltin { op }); + continue; + } + + let inner_module = module_name.strip_prefix("[export]").unwrap(); let (op, resource_name) = parse_resource_field(field_name).ok_or_else(|| { Error::EncodingError(format!( "[export]-prefixed import has unexpected field name: {}::{}", @@ -771,6 +848,30 @@ fn assemble_component( continue; } + // Category P3-root: $root-prefixed P3 runtime builtins + // (e.g., $root::[context-get-0], $root::[waitable-set-new]) + if module_name == "$root" + && let Some(p3_op) = parse_p3_builtin_field(field_name) + { + let op = match p3_op { + P3BuiltinOp::TaskReturn { .. } => { + let import_ty = fused_info + .func_types + .get(*_type_idx as usize) + .cloned() + .unwrap_or_default(); + let location = + find_task_return_for_import(all_components, comp_idx, &import_ty); + P3BuiltinOp::TaskReturn { + source_location: location, + } + } + other => other, + }; + import_resolutions.push(ImportResolution::TaskBuiltin { op }); + continue; + } + // Category C: try resolving to a component import instance if let Some((inst_idx, func_name)) = resolve_import_to_instance(source, module_name, field_name, &instance_map) @@ -984,6 +1085,7 @@ fn assemble_component( !field.starts_with("[resource-drop]") && !field.starts_with("[resource-new]") && !field.starts_with("[resource-rep]") + && !is_p3_builtin_field(field) }); // realloc_core_indices[memory_idx] = core func idx of that component's cabi_realloc @@ -1052,6 +1154,10 @@ fn assemble_component( let mut component_type_idx = count_replayed_types(source); let mut lowered_func_indices: Vec = Vec::new(); + // Source type index → wrapper type index mapping (for recursive type defs). + // Shared between import resolution (P3 task-return types) and export lifting. + let mut type_remap: std::collections::HashMap = std::collections::HashMap::new(); + // Cache: resource_name → component type index. // All components share one canonical resource type per resource name, // regardless of interface. Re-exporters import and export the same @@ -1194,6 +1300,101 @@ fn assemble_component( core_func_idx += 1; } } + + ImportResolution::TaskBuiltin { op } => { + let mut canon = CanonicalFunctionSection::new(); + match op { + P3BuiltinOp::TaskReturn { + source_location, .. + } => { + // Recover the result type and options from the matching + // component's canonical function entry, if available. + let (result_ty, options, source_comp) = source_location + .as_ref() + .and_then(|(comp_idx, canon_idx)| { + let comp = all_components.get(*comp_idx)?; + let entry = comp.canonical_functions.get(*canon_idx)?; + match entry { + parser::CanonicalEntry::TaskReturn { result, options } => { + Some((result.clone(), options.clone(), Some(comp))) + } + _ => None, + } + }) + .unwrap_or_else(|| (None, parser::CanonicalOptions::default(), None)); + + // Use the source component for type resolution if available, + // otherwise fall back to the wrapper source component. + let type_source = source_comp.unwrap_or(source); + let enc_result = result_ty + .as_ref() + .map(|cvt| { + convert_parser_val_to_encoder( + &mut component, + type_source, + cvt, + &mut component_type_idx, + &mut type_remap, + ) + }) + .transpose()?; + + let mut enc_options: Vec = Vec::new(); + if let Some(mem) = options.memory { + enc_options + .push(CanonicalOption::Memory(memory_core_indices[mem as usize])); + } + match options.string_encoding { + parser::CanonStringEncoding::Utf8 => { + enc_options.push(CanonicalOption::UTF8); + } + parser::CanonStringEncoding::Utf16 => { + enc_options.push(CanonicalOption::UTF16); + } + parser::CanonStringEncoding::CompactUtf16 => { + enc_options.push(CanonicalOption::CompactUTF16); + } + } + canon.task_return(enc_result, enc_options); + } + P3BuiltinOp::TaskCancel => { + canon.task_cancel(); + } + P3BuiltinOp::ContextGet(slot) => { + canon.context_get(*slot); + } + P3BuiltinOp::ContextSet(slot) => { + canon.context_set(*slot); + } + P3BuiltinOp::WaitableJoin => { + canon.waitable_join(); + } + P3BuiltinOp::WaitableSetNew => { + canon.waitable_set_new(); + } + P3BuiltinOp::WaitableSetDrop => { + canon.waitable_set_drop(); + } + P3BuiltinOp::WaitableSetPoll => { + // waitable-set.poll requires async flag and memory index. + // Default: async_=false, memory=0 + canon.waitable_set_poll(false, memory_core_indices[0]); + } + P3BuiltinOp::BackpressureInc => { + canon.backpressure_inc(); + } + P3BuiltinOp::BackpressureDec => { + canon.backpressure_dec(); + } + P3BuiltinOp::SubtaskDrop => { + canon.subtask_drop(); + } + } + component.section(&canon); + + lowered_func_indices.push(core_func_idx); + core_func_idx += 1; + } } } @@ -1303,9 +1504,6 @@ fn assemble_component( .filter(|imp| matches!(imp.ty, wasmparser::ComponentTypeRef::Instance(_))) .count() as u32; - // Source type index → wrapper type index mapping (for recursive type defs) - let mut type_remap: std::collections::HashMap = std::collections::HashMap::new(); - for comp_export in &source.exports { if comp_export.kind != wasmparser::ComponentExternalKind::Instance { continue; @@ -1348,17 +1546,17 @@ fn assemble_component( None }; - // Find the source component's lift type for this export function. - // We trace: source export → component instance → canonical lift → type_index - let lift_type_idx = + // Find the source component's lift type and canonical options for + // this export function. + let lift_info = find_lift_type_for_interface_func(source, interface_name, &func_info.func_name); // Define the component function type in our wrapper - let wrapper_func_type = if let Some(source_type_idx) = lift_type_idx { + let wrapper_func_type = if let Some((source_type_idx, _)) = &lift_info { define_source_type_in_wrapper( &mut component, source, - source_type_idx, + *source_type_idx, &mut component_type_idx, &mut type_remap, )? @@ -1377,7 +1575,46 @@ fn assemble_component( lift_options.push(CanonicalOption::PostReturn(pr_idx)); } } - // Simple functions (like run: func() -> result) need no options + // Propagate P3 async/callback from source component's canon lift + if let Some((_, ref source_opts)) = lift_info { + if source_opts.async_ { + lift_options.push(CanonicalOption::Async); + // async requires memory + if !lift_options + .iter() + .any(|o| matches!(o, CanonicalOption::Memory(_))) + { + lift_options.push(CanonicalOption::Memory(0)); + } + if !lift_options.iter().any(|o| { + matches!( + o, + CanonicalOption::UTF8 + | CanonicalOption::UTF16 + | CanonicalOption::CompactUTF16 + ) + }) { + lift_options.push(CanonicalOption::UTF8); + } + } + if source_opts.callback.is_some() { + // Callback function is exported from the fused module as + // [callback][async-lift]# + let cb_name = format!( + "[callback][async-lift]{}#{}", + interface_name, func_info.func_name + ); + let mut alias_section = ComponentAliasSection::new(); + alias_section.alias(Alias::CoreInstanceExport { + instance: fused_instance, + kind: ExportKind::Func, + name: &cb_name, + }); + component.section(&alias_section); + lift_options.push(CanonicalOption::Callback(core_func_idx)); + core_func_idx += 1; + } + } let mut canon = CanonicalFunctionSection::new(); canon.lift(aliased_core_func, wrapper_func_type, lift_options); @@ -1779,6 +2016,172 @@ fn read_leb128_with_len(data: &[u8]) -> Option<(u32, usize)> { None } +/// Find a `TaskReturn` entry for a given fused module import. +/// +/// Uses the source component index (from merged import metadata) to +/// search the correct component first, then falls back to all components. +/// Matches by comparing the flat core type of each TaskReturn's result +/// against the import's core function parameters. +/// +/// Returns `(component_index, canon_index)`. +fn find_task_return_for_import( + all_components: &[ParsedComponent], + source_comp_idx: Option, + import_type: &(Vec, Vec), +) -> Option<(usize, usize)> { + let import_params = &import_type.0; + + // Helper: search one component for a matching TaskReturn + let search_comp = |comp_idx: usize, comp: &ParsedComponent| -> Option<(usize, usize)> { + for (canon_idx, entry) in comp.canonical_functions.iter().enumerate() { + if let parser::CanonicalEntry::TaskReturn { result, .. } = entry { + let expected_params = flat_task_return_params_resolved(result.as_ref(), comp); + if expected_params == *import_params { + return Some((comp_idx, canon_idx)); + } + } + } + None + }; + + // Prefer the source component if known + if let Some(src_idx) = source_comp_idx + && let Some(comp) = all_components.get(src_idx) + && let Some(result) = search_comp(src_idx, comp) + { + return Some(result); + } + + // Search all components + for (comp_idx, comp) in all_components.iter().enumerate() { + if let Some(result) = search_comp(comp_idx, comp) { + return Some(result); + } + } + + // Fallback: return the first TaskReturn entry from any component + for (comp_idx, comp) in all_components.iter().enumerate() { + for (canon_idx, entry) in comp.canonical_functions.iter().enumerate() { + if matches!(entry, parser::CanonicalEntry::TaskReturn { .. }) { + return Some((comp_idx, canon_idx)); + } + } + } + + None +} + +/// Compute flat task.return params with Type(idx) resolution. +/// +/// Unlike `flat_task_return_params`, this version resolves `Type(idx)` +/// references using the component's type definitions. +fn flat_task_return_params_resolved( + result: Option<&parser::ComponentValType>, + comp: &ParsedComponent, +) -> Vec { + match result { + None => vec![], + Some(ty) => flat_component_val_type_resolved(ty, comp), + } +} + +/// Compute flat core representation with Type(idx) resolution. +fn flat_component_val_type_resolved( + ty: &parser::ComponentValType, + comp: &ParsedComponent, +) -> Vec { + use wasm_encoder::ValType; + match ty { + parser::ComponentValType::Type(idx) => { + // Resolve the type index to its definition + if let Some(type_def) = comp.get_type_definition(*idx) { + match &type_def.kind { + parser::ComponentTypeKind::Defined(inner) => { + flat_component_val_type_resolved(inner, comp) + } + _ => vec![ValType::I32], // function types etc. → handle + } + } else { + vec![ValType::I32] // unknown → default i32 + } + } + parser::ComponentValType::Record(fields) => { + let mut params = Vec::new(); + for (_, field_ty) in fields { + params.extend(flat_component_val_type_resolved(field_ty, comp)); + } + params + } + parser::ComponentValType::Tuple(elems) => { + let mut params = Vec::new(); + for elem in elems { + params.extend(flat_component_val_type_resolved(elem, comp)); + } + params + } + parser::ComponentValType::List(_) | parser::ComponentValType::FixedSizeList(_, _) => { + vec![ValType::I32, ValType::I32] + } + parser::ComponentValType::String => vec![ValType::I32, ValType::I32], + parser::ComponentValType::Option(inner) => { + let mut params = vec![ValType::I32]; // discriminant + params.extend(flat_component_val_type_resolved(inner, comp)); + params + } + parser::ComponentValType::Result { ok, err } => { + let ok_flat = ok + .as_ref() + .map(|t| flat_component_val_type_resolved(t, comp)) + .unwrap_or_default(); + let err_flat = err + .as_ref() + .map(|t| flat_component_val_type_resolved(t, comp)) + .unwrap_or_default(); + let mut params = vec![ValType::I32]; // discriminant + let longer = if ok_flat.len() >= err_flat.len() { + &ok_flat + } else { + &err_flat + }; + params.extend_from_slice(longer); + params + } + parser::ComponentValType::Variant(cases) => { + let mut params = vec![ValType::I32]; // discriminant + let mut max_flat: Vec = Vec::new(); + for (_, case_ty) in cases { + if let Some(ct) = case_ty { + let case_flat = flat_component_val_type_resolved(ct, comp); + if case_flat.len() > max_flat.len() { + max_flat = case_flat; + } + } + } + params.extend(max_flat); + params + } + // Simple/primitive types + parser::ComponentValType::Primitive(p) => vec![match p { + parser::PrimitiveValType::Bool + | parser::PrimitiveValType::U8 + | parser::PrimitiveValType::S8 + | parser::PrimitiveValType::U16 + | parser::PrimitiveValType::S16 + | parser::PrimitiveValType::U32 + | parser::PrimitiveValType::S32 + | parser::PrimitiveValType::Char => wasm_encoder::ValType::I32, + parser::PrimitiveValType::U64 | parser::PrimitiveValType::S64 => { + wasm_encoder::ValType::I64 + } + parser::PrimitiveValType::F32 => wasm_encoder::ValType::F32, + parser::PrimitiveValType::F64 => wasm_encoder::ValType::F64, + }], + parser::ComponentValType::Own(_) | parser::ComponentValType::Borrow(_) => { + vec![wasm_encoder::ValType::I32] + } + } +} + /// Info about a fused module export that belongs to a component interface. struct ExportFuncInfo { /// Function name within the interface (e.g., "run", "greet") @@ -1796,11 +2199,13 @@ struct ExportFuncInfo { /// /// Falls back to scanning all Lift entries for one whose core export name /// matches the `#` pattern. +/// Returns (type_index, canonical_options) for the source component's lift +/// entry that matches the given interface function. fn find_lift_type_for_interface_func( source: &ParsedComponent, interface_name: &str, func_name: &str, -) -> Option { +) -> Option<(u32, parser::CanonicalOptions)> { let target_export_name = format!("{}#{}", interface_name, func_name); // Strategy 1: Find a Lift entry whose core function is exported with the @@ -1811,7 +2216,12 @@ fn find_lift_type_for_interface_func( // The Lift entry references the core function by its core_func_index. // We can match by looking at core aliases that reference the export name. for (canon_idx, canon) in source.canonical_functions.iter().enumerate() { - if let parser::CanonicalEntry::Lift { type_index, .. } = canon { + if let parser::CanonicalEntry::Lift { + type_index, + options, + .. + } = canon + { // Check if any component_func_def points to this lift, and if // the corresponding export matches our interface for func_def in &source.component_func_defs { @@ -1820,7 +2230,7 @@ fn find_lift_type_for_interface_func( { // This is a lifted function. Check if the source // component exports it as our interface. - return Some(*type_index); + return Some((*type_index, options.clone())); } } } @@ -1829,8 +2239,13 @@ fn find_lift_type_for_interface_func( // Strategy 2: Look for any Lift entry (fallback for simple components // with only one export). for canon in &source.canonical_functions { - if let parser::CanonicalEntry::Lift { type_index, .. } = canon { - return Some(*type_index); + if let parser::CanonicalEntry::Lift { + type_index, + options, + .. + } = canon + { + return Some((*type_index, options.clone())); } } @@ -1995,6 +2410,58 @@ fn emit_defined_type( )?; types.defined_type().option(inner_enc); } + parser::ComponentValType::Record(fields) => { + let enc_fields: Vec<(&str, wasm_encoder::ComponentValType)> = fields + .iter() + .map(|(name, ty)| { + let enc = convert_parser_val_to_encoder( + component, + source, + ty, + component_type_idx, + type_remap, + )?; + Ok((name.as_str(), enc)) + }) + .collect::>()?; + types.defined_type().record(enc_fields); + } + parser::ComponentValType::Tuple(elems) => { + let enc_elems: Vec = elems + .iter() + .map(|ty| { + convert_parser_val_to_encoder( + component, + source, + ty, + component_type_idx, + type_remap, + ) + }) + .collect::>()?; + types.defined_type().tuple(enc_elems); + } + parser::ComponentValType::Variant(cases) => { + let enc_cases: Vec<(&str, Option)> = cases + .iter() + .map(|(name, ty)| { + let enc = ty + .as_ref() + .map(|t| { + convert_parser_val_to_encoder( + component, + source, + t, + component_type_idx, + type_remap, + ) + }) + .transpose()?; + Ok((name.as_str(), enc)) + }) + .collect::>()?; + types.defined_type().variant(enc_cases); + } _ => { return Err(Error::EncodingError(format!( "unsupported defined type for export: {:?}", @@ -2043,7 +2510,10 @@ fn convert_parser_val_to_encoder( } parser::ComponentValType::List(_) | parser::ComponentValType::FixedSizeList(_, _) - | parser::ComponentValType::Option(_) => { + | parser::ComponentValType::Option(_) + | parser::ComponentValType::Record(_) + | parser::ComponentValType::Tuple(_) + | parser::ComponentValType::Variant(_) => { let wrapper_idx = emit_defined_type(component, source, ty, component_type_idx, type_remap)?; Ok(wasm_encoder::ComponentValType::Type(wrapper_idx)) @@ -2164,6 +2634,72 @@ fn parse_resource_field(field_name: &str) -> Option<(ResourceOp, String)> { }) } +/// Parse a P3 task/async built-in field name into a `P3BuiltinOp`. +/// +/// Recognizes field names like `[task-return]is-prime`, `[task-cancel]`, +/// `[context-get-0]`, `[waitable-set-new]`, `[backpressure-inc]`, etc. +/// +/// The `$N` suffix (multi-memory deduplication) is stripped before matching. +/// +/// Returns `None` if the field doesn't match any P3 built-in prefix. +fn parse_p3_builtin_field(field_name: &str) -> Option { + // Strip $N suffix if present (multi-memory deduplication) + let base = if let Some(dollar_pos) = field_name.rfind('$') { + let suffix = &field_name[dollar_pos + 1..]; + if suffix.chars().all(|c| c.is_ascii_digit()) { + &field_name[..dollar_pos] + } else { + field_name + } + } else { + field_name + }; + + // [task-return] — return a result from an async export + if base.starts_with("[task-return]") { + return Some(P3BuiltinOp::TaskReturn { + source_location: None, + }); + } + + // Exact-match builtins (no trailing name) + match base { + "[task-cancel]" => return Some(P3BuiltinOp::TaskCancel), + "[waitable-join]" => return Some(P3BuiltinOp::WaitableJoin), + "[waitable-set-new]" => return Some(P3BuiltinOp::WaitableSetNew), + "[waitable-set-drop]" => return Some(P3BuiltinOp::WaitableSetDrop), + "[waitable-set-poll]" => return Some(P3BuiltinOp::WaitableSetPoll), + "[backpressure-inc]" => return Some(P3BuiltinOp::BackpressureInc), + "[backpressure-dec]" => return Some(P3BuiltinOp::BackpressureDec), + "[subtask-drop]" => return Some(P3BuiltinOp::SubtaskDrop), + _ => {} + } + + // [context-get-N] and [context-set-N] — slot index encoded in the name + if let Some(rest) = base.strip_prefix("[context-get-") + && let Some(idx_str) = rest.strip_suffix(']') + && let Ok(slot) = idx_str.parse::() + { + return Some(P3BuiltinOp::ContextGet(slot)); + } + if let Some(rest) = base.strip_prefix("[context-set-") + && let Some(idx_str) = rest.strip_suffix(']') + && let Ok(slot) = idx_str.parse::() + { + return Some(P3BuiltinOp::ContextSet(slot)); + } + + None +} + +/// Check whether a field name is a P3 task/async built-in. +/// +/// Used to distinguish P3 fields from resource operations when processing +/// `[export]`-prefixed imports. +fn is_p3_builtin_field(field_name: &str) -> bool { + parse_p3_builtin_field(field_name).is_some() +} + /// Convert wasmparser TypeRef to wasm-encoder EntityType. fn convert_type_ref(ty: wasmparser::TypeRef) -> Result { match ty { diff --git a/meld-core/src/lib.rs b/meld-core/src/lib.rs index 51d054c..760af62 100644 --- a/meld-core/src/lib.rs +++ b/meld-core/src/lib.rs @@ -240,28 +240,17 @@ impl Fuser { )); } - // Reject P3 async components — meld cannot yet fuse them correctly. - // Collect all detected features across all components for a single - // actionable error message. - let mut p3_details: Vec = Vec::new(); + // Log P3 async feature usage (informational, no longer a rejection). for (idx, comp) in self.components.iter().enumerate() { if !comp.p3_async_features.is_empty() { let default_name = format!("component {idx}"); let comp_name = comp.name.as_deref().unwrap_or(&default_name); - // Deduplicate features within a single component - let mut feats = comp.p3_async_features.clone(); - feats.sort(); - feats.dedup(); - p3_details.push(format!("'{comp_name}' uses: {}", feats.join(", "))); + log::info!( + "P3 async types in '{comp_name}': {}", + comp.p3_async_features.join(", ") + ); } } - if !p3_details.is_empty() { - return Err(Error::P3AsyncNotSupported(format!( - "{}. P3 async features (stream, future, async lift/lower, task builtins) \ - are not yet supported by meld. Use P2 components or wait for meld P3 support.", - p3_details.join("; ") - ))); - } let mut stats = FusionStats { components_fused: self.components.len(), @@ -375,18 +364,83 @@ impl Fuser { adapters: &[adapter::AdapterFunction], graph: &resolver::DependencyGraph, ) -> Result<()> { - use std::collections::HashSet; + use std::collections::{HashMap, HashSet}; + use wasm_encoder::{Function, Instruction, ValType}; + + let original_func_count = merged.functions.len() as u32; + let func_base = merged.import_counts.func; + + // Pre-scan: identify adapters needing P3 async widening wrappers. + // A wrapper is needed when the caller's import type has wider result + // types than the adapter's (e.g., caller expects i64, adapter returns + // i32 task handle). Wrappers are placed in merged.functions BEFORE the + // adapters, so we must pre-count them to compute correct adapter indices. + struct WrapperInfo { + adapter_offset: usize, + comp_idx: usize, + mod_idx: usize, + caller_type_idx: u32, + } + let mut wrapper_infos: Vec = Vec::new(); - let adapter_base = merged.import_counts.func + merged.functions.len() as u32; + for (adapter_offset, (adapter, site)) in + adapters.iter().zip(graph.adapter_sites.iter()).enumerate() + { + if let Some(local_ti) = site.import_func_type_idx + && let Some(&caller_ti) = + merged + .type_index_map + .get(&(site.from_component, site.from_module, local_ti)) + && caller_ti != adapter.type_idx + { + let caller_type = &merged.types[caller_ti as usize]; + let adapter_type = &merged.types[adapter.type_idx as usize]; + // Only wrap when there is actual result widening (i32→i64) + let has_widening = caller_type.params.len() == adapter_type.params.len() + && caller_type.results.len() == adapter_type.results.len() + && caller_type + .results + .iter() + .zip(adapter_type.results.iter()) + .any(|(c, a)| *a == ValType::I32 && *c == ValType::I64); + if has_widening { + wrapper_infos.push(WrapperInfo { + adapter_offset, + comp_idx: site.from_component, + mod_idx: site.from_module, + caller_type_idx: caller_ti, + }); + } + } + } + + let num_wrappers = wrapper_infos.len() as u32; + + // Adapter base accounts for wrappers prepended into merged.functions. + // Layout: [imports] [original funcs] [wrappers] [adapters] + let adapter_base = func_base + original_func_count + num_wrappers; + + // Map adapter_offset → wrapper merged index for adapters that have wrappers. + let mut adapter_to_wrapper: HashMap = HashMap::new(); + for (wi, info) in wrapper_infos.iter().enumerate() { + adapter_to_wrapper.insert( + info.adapter_offset, + func_base + original_func_count + wi as u32, + ); + } // For each adapter, update function_index_map to point the source - // import to the adapter's merged index rather than the direct target. + // import to the wrapper (if one exists) or the adapter's merged index. let mut affected_modules: HashSet<(usize, usize)> = HashSet::new(); for (adapter_offset, (adapter, site)) in adapters.iter().zip(graph.adapter_sites.iter()).enumerate() { - let adapter_merged_idx = adapter_base + adapter_offset as u32; + let target_idx = if let Some(&wrapper_idx) = adapter_to_wrapper.get(&adapter_offset) { + wrapper_idx + } else { + adapter_base + adapter_offset as u32 + }; let comp_idx = adapter.source_component; let mod_idx = adapter.source_module; let module = &self.components[comp_idx].core_modules[mod_idx]; @@ -404,7 +458,7 @@ impl Fuser { { merged .function_index_map - .insert((comp_idx, mod_idx, import_func_idx), adapter_merged_idx); + .insert((comp_idx, mod_idx, import_func_idx), target_idx); affected_modules.insert((comp_idx, mod_idx)); found = true; break; @@ -422,6 +476,33 @@ impl Fuser { } } + // Create wrapper functions for P3 async type widening. + for info in &wrapper_infos { + let adapter_merged_idx = adapter_base + info.adapter_offset as u32; + let caller_type = &merged.types[info.caller_type_idx as usize]; + let adapter_type = &merged.types[adapters[info.adapter_offset].type_idx as usize]; + + let mut body = Function::new([]); + for i in 0..caller_type.params.len() { + body.instruction(&Instruction::LocalGet(i as u32)); + } + body.instruction(&Instruction::Call(adapter_merged_idx)); + for (caller_r, adapter_r) in caller_type.results.iter().zip(adapter_type.results.iter()) + { + if *adapter_r == ValType::I32 && *caller_r == ValType::I64 { + body.instruction(&Instruction::I64ExtendI32U); + } + } + body.instruction(&Instruction::End); + + merged.functions.push(merger::MergedFunction { + type_idx: info.caller_type_idx, + body, + origin: (info.comp_idx, info.mod_idx, u32::MAX), + }); + affected_modules.insert((info.comp_idx, info.mod_idx)); + } + // Re-rewrite function bodies for every module that had an import // redirected to an adapter, so the already-encoded `call` instructions // pick up the corrected indices. diff --git a/meld-core/src/parser.rs b/meld-core/src/parser.rs index d36dcc9..48be314 100644 --- a/meld-core/src/parser.rs +++ b/meld-core/src/parser.rs @@ -465,6 +465,8 @@ pub struct CanonicalOptions { pub memory: Option, pub realloc: Option, pub post_return: Option, + pub async_: bool, + pub callback: Option, } impl Default for CanonicalOptions { @@ -474,6 +476,8 @@ impl Default for CanonicalOptions { memory: None, realloc: None, post_return: None, + async_: false, + callback: None, } } } @@ -498,11 +502,78 @@ pub enum CanonicalEntry { ResourceDrop { resource: u32 }, /// Get representation of a resource handle ResourceRep { resource: u32 }, - /// Spawn a new thread + /// Spawn a new thread (via ref) ThreadSpawn { func_ty_index: u32 }, /// Query hardware thread concurrency ThreadHwConcurrency, - /// Unsupported canonical function (P3 async, stream, future, etc.) + /// Async drop of a resource handle + ResourceDropAsync { resource: u32 }, + /// Increment backpressure counter + BackpressureInc, + /// Decrement backpressure counter + BackpressureDec, + /// Return a result from a lifted async export + TaskReturn { + result: Option, + options: CanonicalOptions, + }, + /// Acknowledge cancellation of the current task + TaskCancel, + /// Get task-local context slot + ContextGet(u32), + /// Set task-local context slot + ContextSet(u32), + /// Yield control to the host + ThreadYield { cancellable: bool }, + /// Drop a completed subtask + SubtaskDrop, + /// Cancel an in-progress subtask + SubtaskCancel { async_: bool }, + /// Create a new stream handle + StreamNew { ty: u32 }, + /// Read from a stream + StreamRead { ty: u32, options: CanonicalOptions }, + /// Write to a stream + StreamWrite { ty: u32, options: CanonicalOptions }, + /// Cancel an in-progress stream read + StreamCancelRead { ty: u32, async_: bool }, + /// Cancel an in-progress stream write + StreamCancelWrite { ty: u32, async_: bool }, + /// Drop the readable end of a stream + StreamDropReadable { ty: u32 }, + /// Drop the writable end of a stream + StreamDropWritable { ty: u32 }, + /// Create a new future handle + FutureNew { ty: u32 }, + /// Read from a future + FutureRead { ty: u32, options: CanonicalOptions }, + /// Write to a future + FutureWrite { ty: u32, options: CanonicalOptions }, + /// Cancel an in-progress future read + FutureCancelRead { ty: u32, async_: bool }, + /// Cancel an in-progress future write + FutureCancelWrite { ty: u32, async_: bool }, + /// Drop the readable end of a future + FutureDropReadable { ty: u32 }, + /// Drop the writable end of a future + FutureDropWritable { ty: u32 }, + /// Create a new error-context with a debug message + ErrorContextNew { options: CanonicalOptions }, + /// Get the debug message for an error-context + ErrorContextDebugMessage { options: CanonicalOptions }, + /// Drop an error-context + ErrorContextDrop, + /// Create a new waitable-set + WaitableSetNew, + /// Block on the next item within a waitable-set + WaitableSetWait { cancellable: bool, memory: u32 }, + /// Check if any items are ready within a waitable-set + WaitableSetPoll { cancellable: bool, memory: u32 }, + /// Drop a waitable-set + WaitableSetDrop, + /// Add an item to a waitable-set + WaitableJoin, + /// Unsupported canonical function (thread.index, thread.*indirect, etc.) Unsupported, } @@ -868,13 +939,8 @@ impl ComponentParser { Payload::ComponentCanonicalSection(reader) => { for canon in reader { let canon = canon?; - let creates_core_func = matches!( - &canon, - wasmparser::CanonicalFunction::Lower { .. } - | wasmparser::CanonicalFunction::ResourceNew { .. } - | wasmparser::CanonicalFunction::ResourceDrop { .. } - | wasmparser::CanonicalFunction::ResourceRep { .. } - ); + let creates_core_func = + !matches!(&canon, wasmparser::CanonicalFunction::Lift { .. }); let is_lift = matches!(&canon, wasmparser::CanonicalFunction::Lift { .. }); let canon_idx = component.canonical_functions.len(); component @@ -2896,11 +2962,12 @@ fn convert_wp_defined_type(dt: &wasmparser::ComponentDefinedType) -> ComponentTy /// Convert wasmparser CanonicalOption list into our CanonicalOptions. /// -/// If `p3_async_features` is `Some`, detected P3 async options (Async, -/// Callback) are pushed into the vec instead of being silently ignored. +/// The `_p3_async_features` parameter is retained for call-site +/// compatibility but is no longer used — async/callback options are +/// now stored directly in `CanonicalOptions`. fn convert_canonical_options( options: &[wasmparser::CanonicalOption], - mut p3_async_features: Option<&mut Vec>, + _p3_async_features: Option<&mut Vec>, ) -> CanonicalOptions { let mut result = CanonicalOptions::default(); for opt in options { @@ -2923,16 +2990,11 @@ fn convert_canonical_options( wasmparser::CanonicalOption::PostReturn(idx) => { result.post_return = Some(*idx); } - // P3 async canonical options — detected and flagged wasmparser::CanonicalOption::Async => { - if let Some(ref mut feats) = p3_async_features { - feats.push("async canonical option".to_string()); - } + result.async_ = true; } - wasmparser::CanonicalOption::Callback(_) => { - if let Some(ref mut feats) = p3_async_features { - feats.push("callback canonical option".to_string()); - } + wasmparser::CanonicalOption::Callback(idx) => { + result.callback = Some(*idx); } wasmparser::CanonicalOption::CoreType(_) => {} wasmparser::CanonicalOption::Gc => {} @@ -2979,35 +3041,101 @@ fn convert_canonical_function( wasmparser::CanonicalFunction::ThreadSpawnRef { func_ty_index } => { CanonicalEntry::ThreadSpawn { func_ty_index } } - // P3 async/stream/future/error-context canonical built-ins — detected - // and flagged. We use Debug formatting to capture the specific variant. + wasmparser::CanonicalFunction::ResourceDropAsync { resource } => { + CanonicalEntry::ResourceDropAsync { resource } + } + wasmparser::CanonicalFunction::BackpressureInc => CanonicalEntry::BackpressureInc, + wasmparser::CanonicalFunction::BackpressureDec => CanonicalEntry::BackpressureDec, + wasmparser::CanonicalFunction::TaskReturn { result, options } => { + CanonicalEntry::TaskReturn { + result: result.as_ref().map(convert_wp_component_val_type), + options: convert_canonical_options(&options, None), + } + } + wasmparser::CanonicalFunction::TaskCancel => CanonicalEntry::TaskCancel, + wasmparser::CanonicalFunction::ContextGet(idx) => CanonicalEntry::ContextGet(idx), + wasmparser::CanonicalFunction::ContextSet(idx) => CanonicalEntry::ContextSet(idx), + wasmparser::CanonicalFunction::ThreadYield { cancellable } => { + CanonicalEntry::ThreadYield { cancellable } + } + wasmparser::CanonicalFunction::SubtaskDrop => CanonicalEntry::SubtaskDrop, + wasmparser::CanonicalFunction::SubtaskCancel { async_ } => { + CanonicalEntry::SubtaskCancel { async_ } + } + wasmparser::CanonicalFunction::StreamNew { ty } => CanonicalEntry::StreamNew { ty }, + wasmparser::CanonicalFunction::StreamRead { ty, options } => CanonicalEntry::StreamRead { + ty, + options: convert_canonical_options(&options, None), + }, + wasmparser::CanonicalFunction::StreamWrite { ty, options } => CanonicalEntry::StreamWrite { + ty, + options: convert_canonical_options(&options, None), + }, + wasmparser::CanonicalFunction::StreamCancelRead { ty, async_ } => { + CanonicalEntry::StreamCancelRead { ty, async_ } + } + wasmparser::CanonicalFunction::StreamCancelWrite { ty, async_ } => { + CanonicalEntry::StreamCancelWrite { ty, async_ } + } + wasmparser::CanonicalFunction::StreamDropReadable { ty } => { + CanonicalEntry::StreamDropReadable { ty } + } + wasmparser::CanonicalFunction::StreamDropWritable { ty } => { + CanonicalEntry::StreamDropWritable { ty } + } + wasmparser::CanonicalFunction::FutureNew { ty } => CanonicalEntry::FutureNew { ty }, + wasmparser::CanonicalFunction::FutureRead { ty, options } => CanonicalEntry::FutureRead { + ty, + options: convert_canonical_options(&options, None), + }, + wasmparser::CanonicalFunction::FutureWrite { ty, options } => CanonicalEntry::FutureWrite { + ty, + options: convert_canonical_options(&options, None), + }, + wasmparser::CanonicalFunction::FutureCancelRead { ty, async_ } => { + CanonicalEntry::FutureCancelRead { ty, async_ } + } + wasmparser::CanonicalFunction::FutureCancelWrite { ty, async_ } => { + CanonicalEntry::FutureCancelWrite { ty, async_ } + } + wasmparser::CanonicalFunction::FutureDropReadable { ty } => { + CanonicalEntry::FutureDropReadable { ty } + } + wasmparser::CanonicalFunction::FutureDropWritable { ty } => { + CanonicalEntry::FutureDropWritable { ty } + } + wasmparser::CanonicalFunction::ErrorContextNew { options } => { + CanonicalEntry::ErrorContextNew { + options: convert_canonical_options(&options, None), + } + } + wasmparser::CanonicalFunction::ErrorContextDebugMessage { options } => { + CanonicalEntry::ErrorContextDebugMessage { + options: convert_canonical_options(&options, None), + } + } + wasmparser::CanonicalFunction::ErrorContextDrop => CanonicalEntry::ErrorContextDrop, + wasmparser::CanonicalFunction::WaitableSetNew => CanonicalEntry::WaitableSetNew, + wasmparser::CanonicalFunction::WaitableSetWait { + cancellable, + memory, + } => CanonicalEntry::WaitableSetWait { + cancellable, + memory, + }, + wasmparser::CanonicalFunction::WaitableSetPoll { + cancellable, + memory, + } => CanonicalEntry::WaitableSetPoll { + cancellable, + memory, + }, + wasmparser::CanonicalFunction::WaitableSetDrop => CanonicalEntry::WaitableSetDrop, + wasmparser::CanonicalFunction::WaitableJoin => CanonicalEntry::WaitableJoin, + // Truly unsupported variants (thread.index, thread.*indirect, etc.) other => { let desc = format!("{other:?}"); - // Provide human-readable names for common P3 built-ins - let friendly = match &desc { - d if d.starts_with("TaskWait") => "task.wait built-in", - d if d.starts_with("TaskPoll") => "task.poll built-in", - d if d.starts_with("TaskYield") => "task.yield built-in", - d if d.starts_with("TaskReturn") => "task.return built-in", - d if d.starts_with("SubtaskDrop") => "subtask.drop built-in", - d if d.starts_with("StreamNew") => "stream.new built-in", - d if d.starts_with("StreamRead") => "stream.read built-in", - d if d.starts_with("StreamWrite") => "stream.write built-in", - d if d.starts_with("StreamCancel") => "stream.cancel-read/write built-in", - d if d.starts_with("StreamClose") => "stream.close-readable/writable built-in", - d if d.starts_with("FutureNew") => "future.new built-in", - d if d.starts_with("FutureRead") => "future.read built-in", - d if d.starts_with("FutureWrite") => "future.write built-in", - d if d.starts_with("FutureCancel") => "future.cancel-read/write built-in", - d if d.starts_with("FutureClose") => "future.close-readable/writable built-in", - d if d.starts_with("ErrorContextNew") => "error-context.new built-in", - d if d.starts_with("ErrorContextDebugMessage") => { - "error-context.debug-message built-in" - } - d if d.starts_with("ErrorContextDrop") => "error-context.drop built-in", - _ => &desc, - }; - p3_async_features.push(friendly.to_string()); + p3_async_features.push(desc); CanonicalEntry::Unsupported } } @@ -3802,47 +3930,33 @@ mod tests { #[test] fn test_p3_async_canonical_option_detected() { - // Async and Callback canonical options should be detected - let mut feats = Vec::new(); + // Async canonical option should be stored in CanonicalOptions let opts = convert_canonical_options( &[ wasmparser::CanonicalOption::UTF8, wasmparser::CanonicalOption::Memory(0), wasmparser::CanonicalOption::Async, ], - Some(&mut feats), + None, ); // Standard options should still be parsed correctly assert_eq!(opts.string_encoding, CanonStringEncoding::Utf8); assert_eq!(opts.memory, Some(0)); - // P3 async feature should be recorded - assert_eq!(feats.len(), 1); - assert!( - feats[0].contains("async"), - "expected 'async' in feature description: {}", - feats[0] - ); + // P3 async option should be stored directly + assert!(opts.async_); + assert_eq!(opts.callback, None); } #[test] fn test_p3_callback_canonical_option_detected() { - let mut feats = Vec::new(); - let _opts = convert_canonical_options( - &[wasmparser::CanonicalOption::Callback(42)], - Some(&mut feats), - ); - assert_eq!(feats.len(), 1); - assert!( - feats[0].contains("callback"), - "expected 'callback' in feature description: {}", - feats[0] - ); + let opts = convert_canonical_options(&[wasmparser::CanonicalOption::Callback(42)], None); + assert_eq!(opts.callback, Some(42)); } #[test] - fn test_p3_async_option_not_detected_when_tracking_disabled() { - // When p3_async_features is None, async options are silently ignored - // (backward-compatible with callers that don't care) + fn test_p3_async_option_stored_regardless_of_tracking() { + // Async option is always stored in CanonicalOptions, regardless + // of the p3_async_features parameter. let opts = convert_canonical_options( &[ wasmparser::CanonicalOption::UTF8, @@ -3851,7 +3965,7 @@ mod tests { None, ); assert_eq!(opts.string_encoding, CanonStringEncoding::Utf8); - // No panic, no error — just silently ignored + assert!(opts.async_); } #[test] @@ -3888,7 +4002,9 @@ mod tests { #[test] fn test_p3_async_features_collected_in_parsed_component() { - // Verify that p3_async_features are accumulated during type parsing + // Verify that p3_async_features are accumulated during type parsing. + // Only P3 async *types* (future, stream, map) push to p3_async_features + // now; async/callback canonical options are stored in CanonicalOptions. let mut comp = empty_parsed_component(); // Simulate what the parse loop does when it encounters a P3 type @@ -3898,15 +4014,12 @@ mod tests { } comp.types.push(ComponentType { kind }); - // Also simulate a P3 canonical function detection - let mut p3_feats = Vec::new(); - let _entry = - convert_canonical_options(&[wasmparser::CanonicalOption::Async], Some(&mut p3_feats)); - comp.p3_async_features.extend(p3_feats); - - assert_eq!(comp.p3_async_features.len(), 2); + assert_eq!(comp.p3_async_features.len(), 1); assert!(comp.p3_async_features[0].contains("future")); - assert!(comp.p3_async_features[1].contains("async")); + + // Async canonical option is now stored directly, not in p3_async_features + let opts = convert_canonical_options(&[wasmparser::CanonicalOption::Async], None); + assert!(opts.async_); } #[test] diff --git a/meld-core/src/resolver.rs b/meld-core/src/resolver.rs index 4c37ebe..e3ea758 100644 --- a/meld-core/src/resolver.rs +++ b/meld-core/src/resolver.rs @@ -702,6 +702,113 @@ fn build_canon_import_names(component: &ParsedComponent) -> HashMap { + // task.return is emitted under [export] or + // [export]$root. The exact field name comes from + // the inner module's import; we can't recover it + // here, but the inner module already has the right + // import name. Mark it so the merger skips fixup. + // We use a sentinel module "$root" with a generic field. + result.insert( + core_func_idx, + ( + "$root".to_string(), + format!("[task-return]{}", core_func_idx), + ), + ); + } + CanonicalEntry::TaskCancel => { + result.insert( + core_func_idx, + ("[export]$root".to_string(), "[task-cancel]".to_string()), + ); + } + CanonicalEntry::BackpressureInc => { + result.insert( + core_func_idx, + ("$root".to_string(), "[backpressure-inc]".to_string()), + ); + } + CanonicalEntry::BackpressureDec => { + result.insert( + core_func_idx, + ("$root".to_string(), "[backpressure-dec]".to_string()), + ); + } + CanonicalEntry::ContextGet(slot) => { + result.insert( + core_func_idx, + ("$root".to_string(), format!("[context-get-{}]", slot)), + ); + } + CanonicalEntry::ContextSet(slot) => { + result.insert( + core_func_idx, + ("$root".to_string(), format!("[context-set-{}]", slot)), + ); + } + CanonicalEntry::WaitableJoin => { + result.insert( + core_func_idx, + ("$root".to_string(), "[waitable-join]".to_string()), + ); + } + CanonicalEntry::WaitableSetNew => { + result.insert( + core_func_idx, + ("$root".to_string(), "[waitable-set-new]".to_string()), + ); + } + CanonicalEntry::WaitableSetDrop => { + result.insert( + core_func_idx, + ("$root".to_string(), "[waitable-set-drop]".to_string()), + ); + } + CanonicalEntry::WaitableSetPoll { .. } => { + result.insert( + core_func_idx, + ("$root".to_string(), "[waitable-set-poll]".to_string()), + ); + } + CanonicalEntry::WaitableSetWait { .. } => { + result.insert( + core_func_idx, + ("$root".to_string(), "[waitable-set-wait]".to_string()), + ); + } + CanonicalEntry::SubtaskDrop => { + result.insert( + core_func_idx, + ("$root".to_string(), "[subtask-drop]".to_string()), + ); + } + CanonicalEntry::SubtaskCancel { .. } => { + result.insert( + core_func_idx, + ("$root".to_string(), "[subtask-cancel]".to_string()), + ); + } + CanonicalEntry::ThreadYield { .. } => { + result.insert( + core_func_idx, + ("$root".to_string(), "[thread-yield]".to_string()), + ); + } + CanonicalEntry::ResourceDropAsync { resource } => { + if let Some((inst_idx, type_name)) = + comp_type_to_instance_export.get(resource) + && let Some(module_name) = + comp_instance_to_import_name.get(inst_idx) + { + let field = format!("[resource-drop-async]{}", type_name); + result.insert(core_func_idx, (module_name.clone(), field)); + } + } _ => {} } } @@ -2145,18 +2252,29 @@ impl Resolver { _ => None, }); + // P3 async exports use `[async-lift]` prefix on the + // core module export name. Try matching both with and + // without the prefix. + let async_qualified = format!("[async-lift]{}", qualified); + for (to_mod_idx, to_module) in to_component.core_modules.iter().enumerate() { if let Some(export) = to_module.exports.iter().find(|exp| { - exp.name == qualified && exp.kind == ExportKind::Function + exp.kind == ExportKind::Function + && (exp.name == qualified || exp.name == async_qualified) }) { found = true; let mut requirements = AdapterRequirements::default(); // Use provenance-based reverse map for correct // component-level core func index lookup. - let comp_core_idx = - callee_export_to_core.get(&(to_mod_idx, qualified.clone())); + // Try the actual export name first, then the plain qualified name. + let comp_core_idx = callee_export_to_core + .get(&(to_mod_idx, export.name.clone())) + .or_else(|| { + callee_export_to_core + .get(&(to_mod_idx, qualified.clone())) + }); let lift_info = comp_core_idx.and_then(|idx| callee_lift_info.get(idx)); if comp_core_idx.is_some() && lift_info.is_none() { From d7dc78bd0eb27ace53d0feb904e7924b33a76291 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 6 Apr 2026 08:54:50 -0500 Subject: [PATCH 4/5] feat: preserve async cross-component calls via canon lift/lower MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P3 async cross-component calls cannot be fused into direct adapter calls because canon lower creates subtask contexts that async machinery (task.return, waitable-set) requires. Without these contexts, task.return has no matching task at runtime. Fix: mark async adapter sites (is_async_lift) and skip them in: - merger: leave async imports unresolved (not mapped to target export) - adapter generator: emit unreachable stub instead of real adapter - wire_adapter_indices: skip wiring for async sites The component wrapper detects these unresolved async imports by checking for matching [async-lift] exports in the fused module, then generates: alias [async-lift] core func → canon lift async (callback, realloc) → canon lower → provide as import to fused instance Type matching uses flattened canonical ABI comparison (including retptr convention) to find the correct Lift entry across flattened components. Result: P3 components fuse to valid core modules AND valid components (wasm-tools validate --features cm-async passes). All P2 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) --- meld-core/src/adapter/fact.rs | 19 +++ meld-core/src/component_wrap.rs | 234 ++++++++++++++++++++++++++++++++ meld-core/src/lib.rs | 7 + meld-core/src/merger.rs | 16 ++- meld-core/src/resolver.rs | 12 +- 5 files changed, 285 insertions(+), 3 deletions(-) diff --git a/meld-core/src/adapter/fact.rs b/meld-core/src/adapter/fact.rs index 758e3dd..243e8cd 100644 --- a/meld-core/src/adapter/fact.rs +++ b/meld-core/src/adapter/fact.rs @@ -3175,6 +3175,25 @@ impl AdapterGenerator for FactStyleGenerator { let mut adapters = Vec::new(); for (idx, site) in graph.adapter_sites.iter().enumerate() { + if site.is_async_lift { + // Async adapter sites are preserved as component-level canon + // lift/lower pairs. Generate a dummy (unreachable) adapter to + // maintain 1:1 correspondence with adapter_sites. + let mut body = Function::new([]); + body.instruction(&Instruction::Unreachable); + body.instruction(&Instruction::End); + adapters.push(AdapterFunction { + name: format!("$async_stub_{}", idx), + type_idx: 0, + body, + source_component: site.from_component, + source_module: site.from_module, + target_component: site.to_component, + target_module: site.to_module, + target_function: 0, + }); + continue; + } let adapter = self.generate_adapter( site, merged, diff --git a/meld-core/src/component_wrap.rs b/meld-core/src/component_wrap.rs index be89ccc..5fd7199 100644 --- a/meld-core/src/component_wrap.rs +++ b/meld-core/src/component_wrap.rs @@ -168,6 +168,25 @@ enum ImportResolution { /// The specific P3 canonical operation to emit. op: P3BuiltinOp, }, + /// Import resolves to an internal P3 async cross-component call. + /// + /// The fused module exports `[async-lift]#` and + /// `[callback][async-lift]#`. The wrapper creates + /// `canon lift ... async (callback ...)` → `canon lower` to provide + /// a synchronous import to the fused core module. + AsyncLiftLower { + /// The async-lift export name in the fused module + async_lift_export: String, + /// The callback export name in the fused module + callback_export: String, + /// Index of the source component that exports this async function + /// (used to look up the correct component-level type) + source_comp_idx: usize, + /// The interface name (e.g., "compute:concurrent/tasks@1.0.0") + interface_name: String, + /// The function name (e.g., "collatz-steps") + func_name: String, + }, } /// A canonical resource operation. @@ -896,6 +915,38 @@ fn assemble_component( continue; } + // Category P3-async: internal async cross-component call. + // The fused module exports [async-lift]# for functions + // that were canon-lifted with `async` in the original component. + // We provide these via canon lift async + canon lower. + { + let async_lift_name = format!("[async-lift]{}#{}", module_name, field_name); + let callback_name = format!("[callback][async-lift]{}#{}", module_name, field_name); + let has_async_lift = fused_info + .exports + .iter() + .any(|(n, k, _)| *k == wasmparser::ExternalKind::Func && *n == async_lift_name); + if has_async_lift { + // Find which component exports this async function + let source_comp = all_components + .iter() + .position(|c| { + c.core_modules + .iter() + .any(|m| m.exports.iter().any(|e| e.name == async_lift_name)) + }) + .unwrap_or(0); + import_resolutions.push(ImportResolution::AsyncLiftLower { + async_lift_export: async_lift_name, + callback_export: callback_name, + source_comp_idx: source_comp, + interface_name: module_name.clone(), + func_name: field_name.to_string(), + }); + continue; + } + } + return Err(Error::EncodingError(format!( "cannot resolve fused import {}::{} to a component instance", module_name, field_name @@ -1166,6 +1217,87 @@ fn assemble_component( let mut local_resource_types: std::collections::HashMap = std::collections::HashMap::new(); + // Pre-define component function types for async lift/lower imports. + // Match the correct Lift entry by comparing its flattened core type + // with the actual core import type from the fused module. + let mut async_func_types: std::collections::HashMap = + std::collections::HashMap::new(); + for (i, resolution) in import_resolutions.iter().enumerate() { + if let ImportResolution::AsyncLiftLower { + source_comp_idx, .. + } = resolution + { + let inner_comp = &all_components[*source_comp_idx]; + let import_type_idx = fused_info.func_imports[i].2; + let core_type = fused_info + .func_types + .get(import_type_idx as usize) + .cloned() + .unwrap_or_default(); + + // Search Lift entries for one whose flattened type matches + // the core import type (params and results). + let mut found = false; + for canon in &inner_comp.canonical_functions { + if let parser::CanonicalEntry::Lift { + type_index, + options, + .. + } = canon + { + if !options.async_ { + continue; + } + if let Some(type_def) = inner_comp.get_type_definition(*type_index) + && let parser::ComponentTypeKind::Function { params, results } = + &type_def.kind + { + // Flatten params + let flat_params: Vec = params + .iter() + .flat_map(|(_, cvt)| flat_component_val_type_resolved(cvt, inner_comp)) + .collect(); + // Flatten results + let flat_results: Vec = results + .iter() + .flat_map(|(_, cvt)| flat_component_val_type_resolved(cvt, inner_comp)) + .collect(); + // Check direct match or retptr convention: + // If flat_results > 1, canon lower uses a retptr param + // and returns nothing. + let direct_match = + flat_params == core_type.0 && flat_results == core_type.1; + let retptr_match = flat_results.len() > 1 && { + let mut expected_params = flat_params.clone(); + expected_params.push(wasm_encoder::ValType::I32); // retptr + expected_params == core_type.0 && core_type.1.is_empty() + }; + if direct_match || retptr_match { + let mut inner_remap = std::collections::HashMap::new(); + let wrapper_type = define_source_type_in_wrapper( + &mut component, + inner_comp, + *type_index, + &mut component_type_idx, + &mut inner_remap, + )?; + async_func_types.insert(i, wrapper_type); + found = true; + break; + } + } + } + } + if !found { + log::warn!( + "no matching async Lift found for import {}::{}", + fused_info.func_imports[i].0, + fused_info.func_imports[i].1, + ); + } + } + } + for (i, resolution) in import_resolutions.iter().enumerate() { match resolution { ImportResolution::Instance { @@ -1301,6 +1433,108 @@ fn assemble_component( } } + ImportResolution::AsyncLiftLower { + async_lift_export, + callback_export, + interface_name, + func_name, + .. + } => { + // Alias the [async-lift] core function from the fused instance + let mut alias_section = ComponentAliasSection::new(); + alias_section.alias(Alias::CoreInstanceExport { + instance: fused_instance, + kind: ExportKind::Func, + name: async_lift_export, + }); + component.section(&alias_section); + let async_lift_core_idx = core_func_idx; + core_func_idx += 1; + + // Alias the [callback] core function + let has_callback = fused_info + .exports + .iter() + .any(|(n, k, _)| *k == wasmparser::ExternalKind::Func && n == callback_export); + let callback_core_idx = if has_callback { + let mut alias_section = ComponentAliasSection::new(); + alias_section.alias(Alias::CoreInstanceExport { + instance: fused_instance, + kind: ExportKind::Func, + name: callback_export, + }); + component.section(&alias_section); + let idx = core_func_idx; + core_func_idx += 1; + Some(idx) + } else { + None + }; + + // Use the pre-defined component function type. + // If type matching failed, return an error. + let comp_func_type = *async_func_types.get(&i).ok_or_else(|| { + Error::EncodingError(format!( + "cannot find component type for async import {}::{}", + interface_name, func_name + )) + })?; + + // Alias realloc (needed for both lift and lower) + let realloc_name = "cabi_realloc"; + let has_realloc = fused_info + .exports + .iter() + .any(|(n, k, _)| *k == wasmparser::ExternalKind::Func && *n == realloc_name); + let realloc_core_idx = if has_realloc { + let mut alias_section = ComponentAliasSection::new(); + alias_section.alias(Alias::CoreInstanceExport { + instance: fused_instance, + kind: ExportKind::Func, + name: realloc_name, + }); + component.section(&alias_section); + let idx = core_func_idx; + core_func_idx += 1; + Some(idx) + } else { + None + }; + + // canon lift ... async (callback N) (realloc N) + let mut lift_options = vec![ + CanonicalOption::Memory(memory_core_indices[0]), + CanonicalOption::UTF8, + CanonicalOption::Async, + ]; + if let Some(cb_idx) = callback_core_idx { + lift_options.push(CanonicalOption::Callback(cb_idx)); + } + if let Some(realloc_idx) = realloc_core_idx { + lift_options.push(CanonicalOption::Realloc(realloc_idx)); + } + let mut canon = CanonicalFunctionSection::new(); + canon.lift(async_lift_core_idx, comp_func_type, lift_options); + component.section(&canon); + let lifted_func_idx = component_func_idx; + component_func_idx += 1; + + // canon lower (blocking synchronous call for the caller) + let mut lower_options = vec![ + CanonicalOption::Memory(memory_core_indices[0]), + CanonicalOption::UTF8, + ]; + if let Some(realloc_idx) = realloc_core_idx { + lower_options.push(CanonicalOption::Realloc(realloc_idx)); + } + let mut canon = CanonicalFunctionSection::new(); + canon.lower(lifted_func_idx, lower_options); + component.section(&canon); + + lowered_func_indices.push(core_func_idx); + core_func_idx += 1; + } + ImportResolution::TaskBuiltin { op } => { let mut canon = CanonicalFunctionSection::new(); match op { diff --git a/meld-core/src/lib.rs b/meld-core/src/lib.rs index 760af62..b13959d 100644 --- a/meld-core/src/lib.rs +++ b/meld-core/src/lib.rs @@ -386,6 +386,9 @@ impl Fuser { for (adapter_offset, (adapter, site)) in adapters.iter().zip(graph.adapter_sites.iter()).enumerate() { + if site.is_async_lift { + continue; + } if let Some(local_ti) = site.import_func_type_idx && let Some(&caller_ti) = merged @@ -436,6 +439,10 @@ impl Fuser { for (adapter_offset, (adapter, site)) in adapters.iter().zip(graph.adapter_sites.iter()).enumerate() { + // Skip async-lifted sites — their imports stay unresolved. + if site.is_async_lift { + continue; + } let target_idx = if let Some(&wrapper_idx) = adapter_to_wrapper.get(&adapter_offset) { wrapper_idx } else { diff --git a/meld-core/src/merger.rs b/meld-core/src/merger.rs index ddad4cc..3172cf3 100644 --- a/meld-core/src/merger.rs +++ b/meld-core/src/merger.rs @@ -1087,9 +1087,12 @@ impl Merger { continue; } - // Check adapter_sites first (cross-component + intra-component adapters) + // Check adapter_sites first (cross-component + intra-component adapters). + // Skip async-lifted sites — their imports stay unresolved so the + // component wrapper can provide them via canon lift/lower. let resolved = graph.adapter_sites.iter().find(|site| { - site.from_component == comp_idx + !site.is_async_lift + && site.from_component == comp_idx && site.from_module == mod_idx && (imp.name == site.import_name || imp.module == site.import_name) && (imp.module == site.import_module || imp.name == site.import_module) @@ -1576,7 +1579,11 @@ impl Merger { for unresolved in &graph.unresolved_imports { // Skip imports resolved by adapter sites (must match the // filter in compute_unresolved_import_assignments). + // Async-lifted sites are excluded — their imports stay unresolved. let resolved_by_adapter = graph.adapter_sites.iter().any(|site| { + if site.is_async_lift { + return false; + } if site.from_component != unresolved.component_idx { return false; } @@ -2469,6 +2476,11 @@ fn compute_unresolved_import_assignments( // because indirect-table shim modules use synthetic names (module="", // field="0") while their display names carry the original interface names. let resolved_by_adapter = graph.adapter_sites.iter().any(|site| { + // Async-lifted sites are NOT fused — their imports stay unresolved + // so the component wrapper handles them via canon lift/lower. + if site.is_async_lift { + return false; + } if site.from_component != unresolved.component_idx { return false; } diff --git a/meld-core/src/resolver.rs b/meld-core/src/resolver.rs index e3ea758..9d947c0 100644 --- a/meld-core/src/resolver.rs +++ b/meld-core/src/resolver.rs @@ -85,6 +85,11 @@ pub struct AdapterSite { /// Whether this crosses a memory boundary pub crosses_memory: bool, + /// Whether the callee export is an async-lifted function (P3). + /// When true, fusion must preserve the component-model async boundary + /// rather than generating a direct adapter call. + pub is_async_lift: bool, + /// Adapter requirements (string transcoding, etc.) pub requirements: AdapterRequirements, } @@ -1605,6 +1610,7 @@ impl Resolver { export_name: export.name.clone(), export_func_idx: export.index, crosses_memory: false, + is_async_lift: export.name.starts_with("[async-lift]"), requirements: AdapterRequirements::default(), }); edges.push((target_comp, unresolved.component_idx)); @@ -2477,6 +2483,7 @@ impl Resolver { requirements.string_transcoding = ce != ce2; } + let is_async = export.name.starts_with("[async-lift]"); graph.adapter_sites.push(AdapterSite { from_component: *from_comp, from_module: from_mod_idx, @@ -2485,9 +2492,10 @@ impl Resolver { import_func_type_idx: caller_import_type_idx, to_component: *to_comp, to_module: to_mod_idx, - export_name: qualified.clone(), + export_name: export.name.clone(), export_func_idx: export.index, crosses_memory, + is_async_lift: is_async, requirements, }); per_func_matched = true; @@ -2749,6 +2757,7 @@ impl Resolver { export_name: export_name.clone(), export_func_idx, crosses_memory, + is_async_lift: export_name.starts_with("[async-lift]"), requirements, }); } @@ -2943,6 +2952,7 @@ impl Resolver { export_name: res.export_name.clone(), export_func_idx, crosses_memory, + is_async_lift: res.export_name.starts_with("[async-lift]"), requirements, }); From 6f60e152b1e0a92e4764ecda1d9159b060184035 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 6 Apr 2026 11:15:04 -0500 Subject: [PATCH 5/5] refactor: conditional canon lower options for async imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make canon lower options conditional based on core type complexity: only add memory/realloc/encoding for functions with compound params. Note: runtime still traps due to wasmtime limitation — same-component canon lift + canon lower always produces AlwaysTrap (wasmtime-environ inline.rs:564). Fix requires nested component to separate lift/lower into different component instances. The structural output is valid per wasm-tools validate --features cm-async. Co-Authored-By: Claude Opus 4.6 (1M context) --- meld-core/src/component_wrap.rs | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/meld-core/src/component_wrap.rs b/meld-core/src/component_wrap.rs index 5fd7199..4db4c37 100644 --- a/meld-core/src/component_wrap.rs +++ b/meld-core/src/component_wrap.rs @@ -1519,13 +1519,30 @@ fn assemble_component( let lifted_func_idx = component_func_idx; component_func_idx += 1; - // canon lower (blocking synchronous call for the caller) - let mut lower_options = vec![ - CanonicalOption::Memory(memory_core_indices[0]), - CanonicalOption::UTF8, - ]; - if let Some(realloc_idx) = realloc_core_idx { - lower_options.push(CanonicalOption::Realloc(realloc_idx)); + // canon lower (blocking synchronous call for the caller). + // Use the caller's memory for data passing. The caller's + // component index is encoded in the fused import's component + // origin. For now, find the memory from the import's position + // in the merged module — imports from the same component share + // a memory. Use minimal options (matching unfused pattern): + // only add memory+realloc+encoding when the function needs them. + let import_type_idx = fused_info.func_imports[i].2; + let core_type = fused_info + .func_types + .get(import_type_idx as usize) + .cloned() + .unwrap_or_default(); + // Needs memory if there are string/list params (>1 flat result or >2 flat params + // with pointer-like patterns) or if this is an async function with memory. + let needs_memory_on_lower = + core_type.0.len() > 2 || core_type.1.len() > 1 || core_type.1.is_empty(); + let mut lower_options: Vec = Vec::new(); + if needs_memory_on_lower { + lower_options.push(CanonicalOption::Memory(memory_core_indices[0])); + lower_options.push(CanonicalOption::UTF8); + if let Some(realloc_idx) = realloc_core_idx { + lower_options.push(CanonicalOption::Realloc(realloc_idx)); + } } let mut canon = CanonicalFunctionSection::new(); canon.lower(lifted_func_idx, lower_options);