From a28c31a2734401a7f887da4cd7690030630d24ee Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Sun, 4 Oct 2026 00:13:49 +0000 Subject: [PATCH 1/4] build(wasix): upgrade embedded runtime to Wasmer 7.5 Refresh the coherent Wasmer/WASIX family, parser, Node-API bindings, and Rust build toolchain. Adapt upstream package metadata and regenerate affected dependency-license contracts. Linux core execution is verified. Keep the browser and Postmaster pins unchanged pending adapter and patch ports; the existing Windows LLVM AOT producer is not supported by upstream 7.5. --- Cargo.lock | 828 +++++++++++++----- clippy.toml | 1 - rust-toolchain.toml | 2 +- src/database-resources/seeds/wasix/Cargo.toml | 8 +- ...6f8bacc519ef555aa7a13e239908907b3fd.base64 | 28 - ...cd79c92942356e3408c6fb66332bad6d977.base64 | 29 + ...802033e5ae2fa7638098173f80836daac60.base64 | 27 + src/native/broker/dependency-licenses.json | 44 +- ...6f8bacc519ef555aa7a13e239908907b3fd.base64 | 28 - ...cd79c92942356e3408c6fb66332bad6d977.base64 | 29 + ...802033e5ae2fa7638098173f80836daac60.base64 | 27 + .../mobile-bindings/dependency-licenses.json | 58 +- src/native/node-addon/Cargo.toml | 6 +- ...6f8bacc519ef555aa7a13e239908907b3fd.base64 | 28 - ...cd79c92942356e3408c6fb66332bad6d977.base64 | 29 + ...802033e5ae2fa7638098173f80836daac60.base64 | 27 + .../node-addon/dependency-licenses.json | 246 +++--- src/native/node-addon/src/lib.rs | 2 +- src/query/rust/src/lib.rs | 4 +- src/wasix/node-addon/Cargo.toml | 8 +- src/wasix/node-addon/README.md | 2 +- src/wasix/node-addon/src/lib.rs | 2 +- .../tools/build-linux-wasix-napi-baseline.sh | 8 +- src/wasix/pgwire-server/Cargo.toml | 2 +- .../aot/aarch64-apple-darwin/build-support.rs | 2 +- .../build-support.rs | 2 +- .../x86_64-pc-windows-msvc/build-support.rs | 2 +- .../x86_64-unknown-linux-gnu/build-support.rs | 2 +- .../aot/aarch64-apple-darwin/build-support.rs | 2 +- .../build-support.rs | 2 +- .../x86_64-pc-windows-msvc/build-support.rs | 2 +- .../x86_64-unknown-linux-gnu/build-support.rs | 2 +- src/wasix/runtime/toolchain.toml | 6 +- src/wasix/runtime/tools/xtask/Cargo.toml | 8 +- .../runtime/tools/xtask/src/aot_serializer.rs | 4 +- .../runtime/tools/xtask/src/asset_manifest.rs | 2 +- .../runtime/tools/xtask/src/asset_pipeline.rs | 7 +- src/wasix/sdks/rust/Cargo.toml | 24 +- src/wasix/sdks/rust/README.md | 2 + src/wasix/sdks/rust/src/oliphaunt/aot.rs | 8 +- src/wasix/sdks/rust/src/oliphaunt/base.rs | 2 +- .../sdks/rust/src/oliphaunt/postgres_mod.rs | 1 + 42 files changed, 1005 insertions(+), 548 deletions(-) delete mode 100644 src/native/broker/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 create mode 100644 src/native/broker/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 create mode 100644 src/native/broker/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 delete mode 100644 src/native/mobile-bindings/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 create mode 100644 src/native/mobile-bindings/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 create mode 100644 src/native/mobile-bindings/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 delete mode 100644 src/native/node-addon/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 create mode 100644 src/native/node-addon/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 create mode 100644 src/native/node-addon/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 diff --git a/Cargo.lock b/Cargo.lock index 9eef16d8f..4a57d0495 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,22 @@ dependencies = [ "gimli 0.32.3", ] +[[package]] +name = "addr2line" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e567177890eb1617b1f774005b66b26b2377afd138a2ca37aae7d8f0c81429d4" +dependencies = [ + "cpp_demangle 0.5.1", + "fallible-iterator 0.3.0", + "gimli 0.34.0", + "memmap2 0.9.10", + "object 0.40.0", + "rustc-demangle", + "smallvec", + "typed-arena", +] + [[package]] name = "adler2" version = "2.0.1" @@ -185,6 +201,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + [[package]] name = "autocfg" version = "1.5.1" @@ -197,7 +219,7 @@ version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ - "addr2line", + "addr2line 0.25.1", "cfg-if", "libc", "miniz_oxide", @@ -212,6 +234,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "basic-toml" version = "0.1.10" @@ -247,7 +275,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "cexpr", "clang-sys", "itertools 0.13.0", @@ -269,9 +297,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "serde_core", +] [[package]] name = "blake3" @@ -285,6 +316,7 @@ dependencies = [ "cfg-if", "constant_time_eq", "cpufeatures 0.3.0", + "rayon-core", ] [[package]] @@ -321,6 +353,15 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bumpalo-herd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a57952053bc5530247762011239f17c49e82376130cdcd03da0dc49ab8bd6c3" +dependencies = [ + "bumpalo", +] + [[package]] name = "bus" version = "2.4.1" @@ -454,6 +495,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chacha20" version = "0.10.2" @@ -567,12 +614,36 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "colosseum" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370c83b49aedf022ee27942e8ae1d9de1cf40dc9653ee6550e4455d08f6406f9" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -616,9 +687,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +checksum = "1af709f1f33454bf52eadfc8c78b3b9ef9cb26fb54d16dc9cd9a7299f899fd1b" dependencies = [ "unicode-segmentation", ] @@ -651,6 +722,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cpp_demangle" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -695,9 +775,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] @@ -868,15 +948,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "defmt" -version = "0.3.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" -dependencies = [ - "defmt 1.1.0", -] - [[package]] name = "defmt" version = "1.1.0" @@ -1063,6 +1134,18 @@ dependencies = [ "serde", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1208,6 +1291,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1275,9 +1359,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1290,9 +1374,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1300,15 +1384,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1328,38 +1412,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1431,12 +1515,12 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gimli" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +checksum = "1033caf0b349c518623b5396bfb2cf0bddf44f0306d543a250e5743297aafd10" dependencies = [ "fnv", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap", "stable_deref_trait", ] @@ -1508,12 +1592,6 @@ dependencies = [ "foldhash 0.1.5", ] -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - [[package]] name = "hashbrown" version = "0.17.1" @@ -1771,11 +1849,11 @@ dependencies = [ [[package]] name = "inkwell" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7decbc9dfa45a4a827a6ff7b822c113b1285678a937e84213417d4ca8a095782" +checksum = "30932a1258d02e3c7344618abcdbb5dca03e0f8d2ad34496067d1f371cfceda6" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "inkwell_internals", "libc", "llvm-sys", @@ -1784,13 +1862,13 @@ dependencies = [ [[package]] name = "inkwell_internals" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfe97ee860815a90ed17e09639513269e39420a7440f3f4c996f238c514cf8d" +checksum = "04a85f1cf8de4f1df2ebd93b6afe957fa2acd0081c97dabfa41651eeeb44756e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -1904,9 +1982,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" [[package]] name = "leb128fmt" @@ -1960,7 +2038,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tar", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -2010,7 +2088,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "libc", "plain", "redox_syscall 0.8.1", @@ -2045,6 +2123,65 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6639b70a7ce854b79c70d7e83f16b5dc0137cc914f3d7d03803b513ecc67ac" +[[package]] +name = "libwild" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f7edaa8ff7a14a1a9915d5ff85c65b24426d81edc0362635a687c70ce7c1ccc" +dependencies = [ + "anyhow", + "atomic-take", + "bitflags 2.13.2", + "blake3", + "bumpalo-herd", + "bytesize", + "cc", + "colored", + "colosseum", + "crossbeam-queue", + "crossbeam-utils", + "derive_more", + "flate2", + "foldhash 0.2.0", + "gimli 0.34.0", + "glob", + "hashbrown 0.17.1", + "hex", + "indexmap", + "itertools 0.15.0", + "jobserver", + "leb128", + "libc", + "linker-layout", + "linker-trace", + "linker-utils", + "memchr", + "memmap2 0.9.10", + "nix 0.31.3", + "object 0.40.0", + "perf-event", + "perfetto-recorder", + "rayon", + "serde", + "serde_yaml", + "sha2 0.11.0", + "sharded-offset-map", + "sharded-vec-writer", + "smallvec", + "strum", + "symbolic-common", + "symbolic-demangle", + "thread_local", + "tracing", + "tracing-subscriber", + "uuid", + "wasm-encoder 0.255.0", + "wasmparser 0.255.0", + "winnow 1.0.3", + "zerocopy", + "zlib-rs", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -2060,6 +2197,40 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "linker-layout" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b117d6790188a855ea548db622f1b9d3ae8fc7df7bc66f296defd78f7c2e10" +dependencies = [ + "anyhow", + "postcard", + "serde", +] + +[[package]] +name = "linker-trace" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728b8a0f9a835c0debd6ae954531169bc76cb430fae909a701303b907e5b518f" +dependencies = [ + "anyhow", + "postcard", + "serde", +] + +[[package]] +name = "linker-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198c6aa21803baa1398e7fb169adadba421164c27ee7946b3a7dcc57d40b1f7e" +dependencies = [ + "anyhow", + "leb128", + "object 0.40.0", + "paste", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2109,27 +2280,18 @@ checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "lz4_flex" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e" +checksum = "ecbdfe44b1bd960b68170b417450a628c43f7cf56bb3c5317e61cb230ee7f226" dependencies = [ "twox-hash", ] [[package]] name = "mach2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] - -[[package]] -name = "mach2" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" +checksum = "b0d28e293f2b8c9d2b2d1c0193bd3c1cbdc0d2cf396888dc01162f7cf9d6c3f3" [[package]] name = "macho-unwind-info" @@ -2148,6 +2310,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "maybe-owned" version = "0.3.4" @@ -2198,15 +2369,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2241,16 +2403,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" -[[package]] -name = "msvc-demangler" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" -dependencies = [ - "bitflags 2.12.1", - "itoa", -] - [[package]] name = "munge" version = "0.4.7" @@ -2273,11 +2425,11 @@ dependencies = [ [[package]] name = "napi" -version = "3.12.2" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c5f4d5375213fdb7be2655e152386e82f026f9a5ba36a75556e11359aafe09" +checksum = "15301c22171e8397a7b17beb3c75bf486bf67ca3bdad8ea347835b770df61969" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "ctor", "futures", "libc", @@ -2289,17 +2441,17 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60fdf9b392c50e7c4170fa633bd909490ed7835cea4c046776d1a4dd8d2ae0ab" +checksum = "b899b545d3aa6dca985939059f258c5488d34e4ecf39c274e20009748f4b846d" [[package]] name = "napi-derive" -version = "3.6.3" +version = "3.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55ea69990c90b888e9e77044410e304ce7f35de599dc6d0b5c1923d2e59af" +checksum = "d11174f7507d7f6cdd69874f541eff194e81c95652b775e69036bcf8d5b7a805" dependencies = [ - "convert_case 0.11.0", + "convert_case 0.12.0", "ctor", "napi-derive-backend", "proc-macro2", @@ -2309,11 +2461,11 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "6.1.2" +version = "6.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df4056ac7c18e4438ccf0edaed4340ca0d269278c8ec19284f7b23cb039fd0ae" +checksum = "fd6856fe1686867da90ed0dc292d7c0c87a695eb7c578b913360f33619ee01e3" dependencies = [ - "convert_case 0.11.0", + "convert_case 0.12.0", "proc-macro2", "quote", "semver", @@ -2322,13 +2474,37 @@ dependencies = [ [[package]] name = "napi-sys" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a" +checksum = "e22a4f25c16a5c5411d987cd6fbd48313522ae1789ade2d6dd3efdc6d40a0fc8" dependencies = [ "libloading 0.9.0", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2355,6 +2531,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -2408,7 +2593,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", ] [[package]] @@ -2431,9 +2616,9 @@ dependencies = [ [[package]] name = "object" -version = "0.39.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +checksum = "dd229a0361b9d0d4396176e02d65897f487eebeab7caa6d443855ee152ca0b9c" dependencies = [ "crc32fast", "flate2", @@ -2483,7 +2668,7 @@ version = "0.2.2" dependencies = [ "sha2 0.10.9", "tar", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -2516,7 +2701,7 @@ dependencies = [ "tar", "tempfile", "toml 0.9.12+spec-1.1.0", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -2624,7 +2809,7 @@ dependencies = [ "wasmer-wasix", "wasmer-wasix-types", "webc", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -2760,6 +2945,38 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "perf-event" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d6393d9238342159080d79b78cb59c67399a8e7ecfa5d410bd614169e4e823" +dependencies = [ + "libc", + "perf-event-open-sys", +] + +[[package]] +name = "perf-event-open-sys" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c44fb1c7651a45a3652c4afc6e754e40b3d6e6556f1487e2b230bfc4f33c2a8" +dependencies = [ + "libc", +] + +[[package]] +name = "perfetto-recorder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14fb4634b7df50e07a4672c50fb2096b59fa2f5f5b3efe41aecae38c55c41f78" +dependencies = [ + "libc", + "nix 0.30.1", + "prost", + "rand 0.9.4", + "windows-sys 0.61.2", +] + [[package]] name = "petgraph" version = "0.8.3" @@ -2777,30 +2994,40 @@ name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.14.0", "serde", ] [[package]] name = "phf_generator" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100" dependencies = [ "fastrand", - "phf_shared", + "phf_shared 0.14.0", ] [[package]] name = "phf_macros" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -2815,6 +3042,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -2859,13 +3095,25 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "postgres-protocol" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" dependencies = [ - "base64", + "base64 0.22.1", "byteorder", "bytes", "fallible-iterator 0.2.0", @@ -2962,6 +3210,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ptr_meta" version = "0.3.1" @@ -3131,7 +3402,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", ] [[package]] @@ -3140,7 +3411,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", ] [[package]] @@ -3211,14 +3482,14 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "region" -version = "3.0.2" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +checksum = "9bc10200af9f8297cb52df7e0e1db8d9ad3e8782981d69b25eedf858f5455ca2" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.2", "libc", - "mach2 0.4.3", - "windows-sys 0.52.0", + "mach2", + "windows-sys 0.61.2", ] [[package]] @@ -3272,7 +3543,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "fallible-iterator 0.3.0", "fallible-streaming-iterator", "hashlink", @@ -3307,7 +3578,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", @@ -3332,9 +3603,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty_pool" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed36cdb20de66d89a17ea04b8883fc7a386f2cf877aaedca5005583ce4876ff" +checksum = "30fe7ad7073d11d12f12a0b29c6a526aa3cd9cc637e042a25d9fb8739c5c7c95" dependencies = [ "crossbeam-channel", "futures", @@ -3345,9 +3616,9 @@ dependencies = [ [[package]] name = "ruzstd" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" +checksum = "a252f5e20f038fe7b4ea53e073e65398d652c864cc162fc77c56c2f13717b888" dependencies = [ "twox-hash", ] @@ -3555,6 +3826,30 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sharded-offset-map" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39ce062cd707cdf07301f39c6bef3a58c75ca2d1139998edc75e7bff54248c6" +dependencies = [ + "sharded-vec-writer", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "sharded-vec-writer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b990b2e423f971a9afaea1fdf46ab3e42197d2577e8ef89f2266c1c26a0731a6" + [[package]] name = "shared-buffer" version = "0.1.4" @@ -3624,14 +3919,14 @@ checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" [[package]] name = "smoltcp" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f73d40463bba65efc9adc6370b56df76d563cc46e2482bba58351b4afb7535e" +checksum = "b6f8b28ad56c6e35524a37dd492af5d1a47e31e1a4d175cd12f89c075f01980f" dependencies = [ "bitflags 1.3.2", "byteorder", "cfg-if", - "defmt 0.3.100", + "defmt", "heapless", "managed", ] @@ -3663,7 +3958,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "crc", "crossbeam-queue", @@ -3734,8 +4029,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", - "bitflags 2.12.1", + "base64 0.22.1", + "bitflags 2.13.2", "byteorder", "crc", "dotenvy", @@ -3793,6 +4088,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3817,8 +4133,7 @@ version = "13.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1245acf80236b4a0d99e9216532102a1670950e79c70b980b607c2040966e83d" dependencies = [ - "cpp_demangle", - "msvc-demangler", + "cpp_demangle 0.4.5", "rustc-demangle", "symbolic-common", ] @@ -3946,6 +4261,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.47" @@ -4043,7 +4367,7 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf", + "phf 0.13.1", "pin-project-lite", "postgres-protocol", "postgres-types", @@ -4186,12 +4510,34 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + [[package]] name = "twox-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.20.1" @@ -4417,6 +4763,7 @@ version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -4435,11 +4782,10 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "virtual-fs" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f2136946b8a66d2bc7548fc4396c72e7a8671e8ab878342f5483e6c6dfd61f" +checksum = "412131c1369d812684470f44c4803d7447f36676bce6c265b5d0af2f9b1e7d9a" dependencies = [ - "anyhow", "async-trait", "bytes", "dashmap", @@ -4449,7 +4795,6 @@ dependencies = [ "fs_extra", "futures", "getrandom 0.4.2", - "indexmap", "libc", "pin-project-lite", "replace_with", @@ -4465,9 +4810,9 @@ dependencies = [ [[package]] name = "virtual-mio" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b449ceba2087fe0a7e436b9f1105dd9868addb294ff96a4a28100eadb9442b15" +checksum = "ff91e15c102aa561ee0b18c6f9443bed528679b68bcc10023f22ceb05cd25ebb" dependencies = [ "async-trait", "bytes", @@ -4482,13 +4827,13 @@ dependencies = [ [[package]] name = "virtual-net" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb377443085e3e30d0e3357a8d55925da670d070a04c9dd2284236a20cf308f1" +checksum = "bd95d7124df8647ae6d74b8074ac683479f14a8ee0cdc909a0a21a171052b566" dependencies = [ "anyhow", "async-trait", - "base64", + "base64 0.23.1", "bincode", "bytecheck", "bytes", @@ -4702,12 +5047,21 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.252.0" +version = "0.255.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" +checksum = "9b524283fb5df62eec102ed0574838961bdd7ba5ac9c50d38e2756c51c971a42" dependencies = [ "leb128fmt", - "wasmparser 0.252.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d0246511d901aacf25d2dc9111f0054947de5e093fe662099e709ff7530dc3" +dependencies = [ + "leb128fmt", + "wasmparser 0.259.0", ] [[package]] @@ -4724,13 +5078,12 @@ dependencies = [ [[package]] name = "wasmer" -version = "7.2.1" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05af716b1bf3de5b8081133223f77f37916e9b4f41e0781c7fbe3e0d7b6da43a" +checksum = "ddfde3972691f1082774c71c42e21add1d1838cb6482a6921a0ba8a993e0046a" dependencies = [ "bindgen", "bytes", - "cfg-if", "cmake", "corosensei", "dashmap", @@ -4745,7 +5098,6 @@ dependencies = [ "serde", "serde-wasm-bindgen", "shared-buffer", - "symbolic-demangle", "tar", "target-lexicon", "thiserror", @@ -4761,23 +5113,27 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "7.2.1" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d984ed2ecd52a8e68ea0f4959bc342bf3b2381262105898a8cb416ecd15003" +checksum = "2a657969bd92c09676e72916b6fed181ebb239f441266458edff37c5e13eb1f8" dependencies = [ + "addr2line 0.27.1", "backtrace", "bytes", - "cfg-if", + "cc", "crossbeam-channel", "enum-iterator", "enumset", + "gimli 0.34.0", "itertools 0.15.0", "leb128", "libc", + "libwild", "macho-unwind-info", "memmap2 0.9.10", "more-asserts", - "object 0.39.1", + "object 0.40.0", + "phf 0.14.0", "rangemap", "rayon", "region", @@ -4785,21 +5141,22 @@ dependencies = [ "self_cell", "shared-buffer", "smallvec", + "strum", "target-lexicon", "tempfile", "thiserror", "wasmer-types", "wasmer-vm", - "wasmparser 0.252.0", + "wasmparser 0.259.0", "which", "windows-sys 0.61.2", ] [[package]] name = "wasmer-compiler-llvm" -version = "7.2.1" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384fe3e32f4b2b5a3135b0cdc68d261ed10499ba98af249f814118348d93fdd7" +checksum = "80f9c2f463d8a240b11647591dbf782e7a58dc935b6f34cd18b885a6af35d9b6" dependencies = [ "byteorder", "cc", @@ -4809,8 +5166,8 @@ dependencies = [ "inkwell", "itertools 0.15.0", "libc", - "object 0.39.1", - "phf", + "object 0.40.0", + "phf 0.14.0", "rayon", "regex", "rustc_version", @@ -4825,9 +5182,9 @@ dependencies = [ [[package]] name = "wasmer-config" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98700e4516eb825f1ad22cd5273bafd0694941722686010dc1af79b70812393f" +checksum = "5ca578bbe6dc558690265038377b35cbf76b5495b2b3a051fa808d9b1775ca58" dependencies = [ "anyhow", "bytesize", @@ -4848,25 +5205,25 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "7.2.1" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882cc3b1909d543b4c5cd84c21d659c9c3608225240a79b68e39554749592b79" +checksum = "e65a98d988cf249317ec2af01526e04aafafc32503e23525e9d5fbe1c498d2db" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "wasmer-journal" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d02acd34af8c89ce772dd492da754dc999d4e3d3f30cbbf11a9c58f5e6c3a659" +checksum = "d1ba6aab53ebc49f540787f2daf9a8865b0933de8a8d081b22a78b19259d279c" dependencies = [ "anyhow", "async-trait", - "base64", + "base64 0.23.1", "bincode", "bytecheck", "bytes", @@ -4887,13 +5244,12 @@ dependencies = [ [[package]] name = "wasmer-package" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54f12fd1711df6b12ade23b5db433d3f1aae4f31a7580d7db480f2cce03ccdb1" +checksum = "61b4bef3b56202cc95546e8c0f21bc6a5bb08034ffd1578a022baee614a4373a" dependencies = [ "anyhow", "bytes", - "cfg-if", "ciborium", "flate2", "ignore", @@ -4916,9 +5272,9 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "7.2.1" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87234e5a975dc1343c08692165e01def9725a74cf527277c0e36b0eb66b8f427" +checksum = "e1a2572e35ea0fe9ac0d3c289b83ee457220e2b1a0b0d9e74a8dfef240064223" dependencies = [ "bytecheck", "crc32fast", @@ -4929,36 +5285,35 @@ dependencies = [ "indexmap", "itertools 0.15.0", "more-asserts", + "num_enum", "rkyv", "serde", "sha2 0.11.0", "target-lexicon", "thiserror", - "wasmparser 0.252.0", + "wasmparser 0.259.0", ] [[package]] name = "wasmer-vm" -version = "7.2.1" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8112dae928ca0d5341180e1ea1418d78bdb3788a78da625a4a3889fe908a91bb" +checksum = "3b60bc87e4993702174a81a6dd4503d915331b1d50caf868fd0998f61f34afa5" dependencies = [ "backtrace", "bytesize", "cc", - "cfg-if", "corosensei", "crossbeam-queue", "dashmap", "enum-iterator", "fnv", - "gimli 0.33.0", + "gimli 0.34.0", "indexmap", "itertools 0.15.0", "libc", "libunwind", - "mach2 0.6.0", - "memoffset", + "mach2", "more-asserts", "parking_lot", "region", @@ -4971,19 +5326,18 @@ dependencies = [ [[package]] name = "wasmer-wasix" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90a93bb66bc9bb1c766d53c68676f8a4f23582b0feef6135c184dfd3bf762e2e" +checksum = "1d5bbce1be073ab1e40f5ed2468653832c22aecfd73e2ba763c41a5d831a4475" dependencies = [ "anyhow", "async-trait", - "base64", + "base64 0.23.1", "bincode", "blake3", "bus", "bytecheck", "bytes", - "cfg-if", "cooked-waker", "crossbeam-channel", "dashmap", @@ -5032,31 +5386,29 @@ dependencies = [ "virtual-net", "waker-fn", "walkdir", - "wasm-encoder 0.252.0", + "wasm-encoder 0.259.0", "wasmer", "wasmer-config", "wasmer-journal", "wasmer-package", "wasmer-types", "wasmer-wasix-types", - "wasmparser 0.252.0", + "wasmparser 0.259.0", "webc", - "weezl", "windows-sys 0.61.2", "xxhash-rust", - "zstd", + "zstd 0.14.0", ] [[package]] name = "wasmer-wasix-types" -version = "0.702.1" +version = "0.705.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f982c57b8ccfcb6bc6f7d7ba420bf53a2099ea9c77135b45cdf45198a00c90db" +checksum = "910259427f2e3af0f2238cf4d2d3dbf284ca471cf02965f4635de7f4b2eee66e" dependencies = [ "anyhow", - "bitflags 2.12.1", + "bitflags 2.13.2", "byteorder", - "cfg-if", "num_enum", "serde", "time", @@ -5077,7 +5429,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "hashbrown 0.15.5", "indexmap", "semver", @@ -5085,25 +5437,36 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.250.0" +version = "0.255.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071d99cdfb8111603ed05500506c3298a940b58d609dd0259d3981785dd33556" +checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "hashbrown 0.17.1", "indexmap", - "semver", - "serde", ] [[package]] name = "wasmparser" -version = "0.252.0" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7c12eac7bb587801590f6a67ff0bc84d0748513864b71108e4b311cc3df694" +dependencies = [ + "bitflags 2.13.2", + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c" +checksum = "4f20f20e44f7e8aeb6744823ea9d869ede51e51be4fdaedede2852282e54d2d8" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", + "hashbrown 0.17.1", "indexmap", + "semver", + "serde", ] [[package]] @@ -5118,12 +5481,12 @@ dependencies = [ [[package]] name = "webc" -version = "12.0.0" +version = "12.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cb48ee4bc7a902c0f1d9eb0c0656f0e78149f1190b7f78e1f28256e88279a84" +checksum = "715cbfae9eb87236aedca786d0f094e4b513ae5c1bc328a81760d1f9b19f187e" dependencies = [ "anyhow", - "base64", + "base64 0.22.1", "bytes", "cfg-if", "ciborium", @@ -5138,7 +5501,7 @@ dependencies = [ "rand 0.9.4", "serde", "serde_json", - "sha2 0.10.9", + "sha2 0.11.0", "shared-buffer", "thiserror", "url", @@ -5153,12 +5516,6 @@ dependencies = [ "nom 7.1.3", ] -[[package]] -name = "weezl" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ca08e5ef825b65b056d9efbd95c8750683f0a6d0466d02e96dc2e4e360f3d2" - [[package]] name = "which" version = "8.0.2" @@ -5237,15 +5594,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -5406,7 +5754,7 @@ version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.2", "windows-sys 0.59.0", ] @@ -5474,7 +5822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.12.1", + "bitflags 2.13.2", "indexmap", "log", "serde", @@ -5534,8 +5882,8 @@ dependencies = [ "walkdir", "wasmer", "wasmer-types", - "wasmparser 0.250.0", - "zstd", + "wasmparser 0.261.0", + "zstd 0.13.3", ] [[package]] @@ -5641,6 +5989,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.21" @@ -5653,7 +6007,16 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zstd-safe", + "zstd-safe 7.2.4", +] + +[[package]] +name = "zstd" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf06bd8162af0734b344780deb55b42a2429ae430870d13fcc12f238e880fe6e" +dependencies = [ + "zstd-safe 8.0.0", ] [[package]] @@ -5665,11 +6028,20 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae42c0555055784c70058d19ba8e275528e8a99a706684868ace5da4e716a4ab" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", diff --git a/clippy.toml b/clippy.toml index 3dfc3206e..5f304987a 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1 @@ -msrv = "1.93.0" avoid-breaking-exported-api = true diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9efad5bea..fd7a8005d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.93.1" +channel = "1.99.0" components = ["rustfmt", "clippy", "llvm-tools-preview"] profile = "minimal" diff --git a/src/database-resources/seeds/wasix/Cargo.toml b/src/database-resources/seeds/wasix/Cargo.toml index d47bbd121..8ee6c4ff3 100644 --- a/src/database-resources/seeds/wasix/Cargo.toml +++ b/src/database-resources/seeds/wasix/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-seed-producer" version = "0.0.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" license.workspace = true publish = false @@ -21,6 +21,6 @@ async-trait = { version = "0.1", optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["rt-multi-thread"], optional = true } -wasmer = { version = "=7.2.1", default-features = false, features = ["sys", "llvm"], optional = true } -wasmer-wasix = { version = "=0.702.1", default-features = false, features = ["sys-minimal", "sys-poll", "sys-thread", "time", "host-fs"], optional = true } -webc = { version = "=12.0.0", optional = true } +wasmer = { version = "=7.5.0", default-features = false, features = ["sys", "llvm"], optional = true } +wasmer-wasix = { version = "=0.705.0", default-features = false, features = ["sys-minimal", "sys-poll", "sys-thread", "time", "host-fs"], optional = true } +webc = { version = "=12.0.1", optional = true } diff --git a/src/native/broker/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 b/src/native/broker/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 deleted file mode 100644 index d8a50af44..000000000 --- a/src/native/broker/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 +++ /dev/null @@ -1,28 +0,0 @@ -VGhlIGF1dG8tZ2VuZXJhdGVkIGJpbmRpbmdzIGFyZSB1bmRlciB0aGUgMy1jbGF1c2UgQlNEIGxp -Y2Vuc2U6CgpCU0QgTGljZW5zZQoKRm9yIFpzdGFuZGFyZCBzb2Z0d2FyZQoKQ29weXJpZ2h0IChj -KSAyMDE2LXByZXNlbnQsIEZhY2Vib29rLCBJbmMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuCgpSZWRp -c3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdp -dGhvdXQgbW9kaWZpY2F0aW9uLAphcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxv -d2luZyBjb25kaXRpb25zIGFyZSBtZXQ6CgogKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNv -ZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsIHRoaXMKICAgbGlzdCBv -ZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCgogKiBSZWRpc3RyaWJ1 -dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlIGNvcHlyaWdodCBu -b3RpY2UsCiAgIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2Ns -YWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb24KICAgYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92 -aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24uCgogKiBOZWl0aGVyIHRoZSBuYW1lIEZhY2Vib29r -IG5vciB0aGUgbmFtZXMgb2YgaXRzIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0bwogICBlbmRv -cnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZCBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91 -dCBzcGVjaWZpYwogICBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgpUSElTIFNPRlRXQVJFIElT -IFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTICJBUyBJ -UyIgQU5ECkFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQg -Tk9UIExJTUlURUQgVE8sIFRIRSBJTVBMSUVECldBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZ -IEFORCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUKRElTQ0xBSU1FRC4gSU4g -Tk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVCBIT0xERVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJ -QUJMRSBGT1IKQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsIFNQRUNJQUwsIEVYRU1Q -TEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTCihJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRF -RCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsKTE9TUyBP -RiBVU0UsIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZF -UiBDQVVTRUQgQU5EIE9OCkFOWSBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRS -QUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUCihJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBP -VEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRSBPRiBUSElTClNPRlRX -QVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLgo= diff --git a/src/native/broker/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 b/src/native/broker/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 new file mode 100644 index 000000000..b143c953c --- /dev/null +++ b/src/native/broker/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 @@ -0,0 +1,29 @@ +VGhlIGF1dG8tZ2VuZXJhdGVkIGJpbmRpbmdzIGFyZSB1bmRlciB0aGUgMy1jbGF1c2UgQlNEIGxp +Y2Vuc2U6CgpCU0QgTGljZW5zZQoKRm9yIFpzdGFuZGFyZCBzb2Z0d2FyZQoKQ29weXJpZ2h0IChj +KSBNZXRhIFBsYXRmb3JtcywgSW5jLiBhbmQgYWZmaWxpYXRlcy4gQWxsIHJpZ2h0cyByZXNlcnZl +ZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdp +dGggb3Igd2l0aG91dCBtb2RpZmljYXRpb24sCmFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0 +aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDoKCiAqIFJlZGlzdHJpYnV0aW9ucyBvZiBz +b3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgdGhpcwog +ICBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KCiAqIFJl +ZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29w +eXJpZ2h0IG5vdGljZSwKICAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dp +bmcgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbgogICBhbmQvb3Igb3RoZXIgbWF0ZXJp +YWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KCiAqIE5laXRoZXIgdGhlIG5hbWUg +RmFjZWJvb2ssIG5vciBNZXRhLCBub3IgdGhlIG5hbWVzIG9mIGl0cyBjb250cmlidXRvcnMgbWF5 +CiAgIGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbSB0 +aGlzIHNvZnR3YXJlIHdpdGhvdXQKICAgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9u +LgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5E +IENPTlRSSUJVVE9SUyAiQVMgSVMiIEFORApBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJ +RVMsIElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRApXQVJSQU5USUVT +IE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0Ug +QVJFCkRJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQgSE9MREVSIE9S +IENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SCkFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVO +VEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUwooSU5DTFVE +SU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMg +T1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJ +TlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTgpBTlkgVEhFT1JZIE9GIExJQUJJTElU +WSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAooSU5DTFVE +SU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRI +RSBVU0UgT0YgVEhJUwpTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElU +WSBPRiBTVUNIIERBTUFHRS4K diff --git a/src/native/broker/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 b/src/native/broker/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 new file mode 100644 index 000000000..693c36fe3 --- /dev/null +++ b/src/native/broker/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 @@ -0,0 +1,27 @@ +QlNEIDMtQ2xhdXNlIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiwgQWxleGFuZHJlIEJ1cnkK +ClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGgg +b3Igd2l0aG91dAptb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUg +Zm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDoKCjEuIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3Vy +Y2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgdGhpcwogICBs +aXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KCjIuIFJlZGlz +dHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJp +Z2h0IG5vdGljZSwKICAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcg +ZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbgogICBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxz +IHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KCjMuIE5laXRoZXIgdGhlIG5hbWUgb2Yg +dGhlIGNvcHlyaWdodCBob2xkZXIgbm9yIHRoZSBuYW1lcyBvZiBpdHMKICAgY29udHJpYnV0b3Jz +IG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20K +ICAgdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lv +bi4KClRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFO +RCBDT05UUklCVVRPUlMgIkFTIElTIgpBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5U +SUVTLCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFCklNUExJRUQgV0FSUkFOVElF +UyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NF +IEFSRQpESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIEhPTERFUiBP +UiBDT05UUklCVVRPUlMgQkUgTElBQkxFCkZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURF +TlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMCkRBTUFHRVMgKElOQ0xV +RElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RT +IE9SClNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwgREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1Mg +SU5URVJSVVBUSU9OKSBIT1dFVkVSCkNBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJ +VFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksCk9SIFRPUlQgKElOQ0xV +RElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBU +SEUgVVNFCk9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJ +VFkgT0YgU1VDSCBEQU1BR0UuCg== diff --git a/src/native/broker/dependency-licenses.json b/src/native/broker/dependency-licenses.json index 2aba460b2..5135e77aa 100644 --- a/src/native/broker/dependency-licenses.json +++ b/src/native/broker/dependency-licenses.json @@ -7,7 +7,7 @@ "linux-x64-gnu": { "cargoTarget": "x86_64-unknown-linux-gnu", "packages": [ - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "cfg-if@1.0.4", "cpufeatures@0.2.17", @@ -37,14 +37,14 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, "linux-arm64-gnu": { "cargoTarget": "aarch64-unknown-linux-gnu", "packages": [ - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "cfg-if@1.0.4", "cpufeatures@0.2.17", @@ -74,14 +74,14 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, "macos-arm64": { "cargoTarget": "aarch64-apple-darwin", "packages": [ - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "cfg-if@1.0.4", "cpufeatures@0.2.17", @@ -111,7 +111,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, @@ -145,7 +145,7 @@ "windows-link@0.2.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] } @@ -153,8 +153,8 @@ "packages": [ { "name": "bitflags", - "version": "2.12.1", - "checksum": "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a", + "version": "2.13.2", + "checksum": "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64"], @@ -838,31 +838,21 @@ }, { "name": "zstd-sys", - "version": "2.0.16+zstd.1.5.7", - "checksum": "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748", - "declaredLicense": "MIT/Apache-2.0", - "selectedLicense": "MIT AND BSD-3-Clause", + "version": "2.1.0+zstd.1.5.7", + "checksum": "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0", + "declaredLicense": "BSD-3-Clause", + "selectedLicense": "BSD-3-Clause", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], "licenseFiles": [ { "name": "LICENSE", - "sha256": "a77b7cfeaf911ed410ffbe76f0cb2b24ad8a4d94e7ead5727e914425c416cc63", - "bytes": 18 - }, - { - "name": "LICENSE.Apache-2.0", - "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "bytes": 10174 + "sha256": "ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60", + "bytes": 1501 }, { "name": "LICENSE.BSD-3-Clause", - "sha256": "48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd", - "bytes": 1595 - }, - { - "name": "LICENSE.Mit", - "sha256": "129e8edef29e9abcd2ebabe252f4ef1b1289cdca356bf0040284a2fbccfb96c8", - "bytes": 1080 + "sha256": "bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977", + "bytes": 1614 }, { "name": "zstd/COPYING", diff --git a/src/native/mobile-bindings/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 b/src/native/mobile-bindings/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 deleted file mode 100644 index d8a50af44..000000000 --- a/src/native/mobile-bindings/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 +++ /dev/null @@ -1,28 +0,0 @@ -VGhlIGF1dG8tZ2VuZXJhdGVkIGJpbmRpbmdzIGFyZSB1bmRlciB0aGUgMy1jbGF1c2UgQlNEIGxp -Y2Vuc2U6CgpCU0QgTGljZW5zZQoKRm9yIFpzdGFuZGFyZCBzb2Z0d2FyZQoKQ29weXJpZ2h0IChj -KSAyMDE2LXByZXNlbnQsIEZhY2Vib29rLCBJbmMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuCgpSZWRp -c3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdp -dGhvdXQgbW9kaWZpY2F0aW9uLAphcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxv -d2luZyBjb25kaXRpb25zIGFyZSBtZXQ6CgogKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNv -ZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsIHRoaXMKICAgbGlzdCBv -ZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCgogKiBSZWRpc3RyaWJ1 -dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlIGNvcHlyaWdodCBu -b3RpY2UsCiAgIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2Ns -YWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb24KICAgYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92 -aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24uCgogKiBOZWl0aGVyIHRoZSBuYW1lIEZhY2Vib29r -IG5vciB0aGUgbmFtZXMgb2YgaXRzIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0bwogICBlbmRv -cnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZCBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91 -dCBzcGVjaWZpYwogICBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgpUSElTIFNPRlRXQVJFIElT -IFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTICJBUyBJ -UyIgQU5ECkFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQg -Tk9UIExJTUlURUQgVE8sIFRIRSBJTVBMSUVECldBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZ -IEFORCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUKRElTQ0xBSU1FRC4gSU4g -Tk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVCBIT0xERVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJ -QUJMRSBGT1IKQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsIFNQRUNJQUwsIEVYRU1Q -TEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTCihJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRF -RCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsKTE9TUyBP -RiBVU0UsIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZF -UiBDQVVTRUQgQU5EIE9OCkFOWSBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRS -QUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUCihJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBP -VEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRSBPRiBUSElTClNPRlRX -QVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLgo= diff --git a/src/native/mobile-bindings/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 b/src/native/mobile-bindings/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 new file mode 100644 index 000000000..b143c953c --- /dev/null +++ b/src/native/mobile-bindings/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 @@ -0,0 +1,29 @@ +VGhlIGF1dG8tZ2VuZXJhdGVkIGJpbmRpbmdzIGFyZSB1bmRlciB0aGUgMy1jbGF1c2UgQlNEIGxp +Y2Vuc2U6CgpCU0QgTGljZW5zZQoKRm9yIFpzdGFuZGFyZCBzb2Z0d2FyZQoKQ29weXJpZ2h0IChj +KSBNZXRhIFBsYXRmb3JtcywgSW5jLiBhbmQgYWZmaWxpYXRlcy4gQWxsIHJpZ2h0cyByZXNlcnZl +ZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdp +dGggb3Igd2l0aG91dCBtb2RpZmljYXRpb24sCmFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0 +aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDoKCiAqIFJlZGlzdHJpYnV0aW9ucyBvZiBz +b3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgdGhpcwog +ICBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KCiAqIFJl +ZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29w +eXJpZ2h0IG5vdGljZSwKICAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dp +bmcgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbgogICBhbmQvb3Igb3RoZXIgbWF0ZXJp +YWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KCiAqIE5laXRoZXIgdGhlIG5hbWUg +RmFjZWJvb2ssIG5vciBNZXRhLCBub3IgdGhlIG5hbWVzIG9mIGl0cyBjb250cmlidXRvcnMgbWF5 +CiAgIGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbSB0 +aGlzIHNvZnR3YXJlIHdpdGhvdXQKICAgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9u +LgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5E +IENPTlRSSUJVVE9SUyAiQVMgSVMiIEFORApBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJ +RVMsIElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRApXQVJSQU5USUVT +IE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0Ug +QVJFCkRJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQgSE9MREVSIE9S +IENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SCkFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVO +VEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUwooSU5DTFVE +SU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMg +T1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJ +TlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTgpBTlkgVEhFT1JZIE9GIExJQUJJTElU +WSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAooSU5DTFVE +SU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRI +RSBVU0UgT0YgVEhJUwpTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElU +WSBPRiBTVUNIIERBTUFHRS4K diff --git a/src/native/mobile-bindings/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 b/src/native/mobile-bindings/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 new file mode 100644 index 000000000..693c36fe3 --- /dev/null +++ b/src/native/mobile-bindings/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 @@ -0,0 +1,27 @@ +QlNEIDMtQ2xhdXNlIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiwgQWxleGFuZHJlIEJ1cnkK +ClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGgg +b3Igd2l0aG91dAptb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUg +Zm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDoKCjEuIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3Vy +Y2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgdGhpcwogICBs +aXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KCjIuIFJlZGlz +dHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJp +Z2h0IG5vdGljZSwKICAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcg +ZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbgogICBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxz +IHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KCjMuIE5laXRoZXIgdGhlIG5hbWUgb2Yg +dGhlIGNvcHlyaWdodCBob2xkZXIgbm9yIHRoZSBuYW1lcyBvZiBpdHMKICAgY29udHJpYnV0b3Jz +IG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20K +ICAgdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lv +bi4KClRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFO +RCBDT05UUklCVVRPUlMgIkFTIElTIgpBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5U +SUVTLCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFCklNUExJRUQgV0FSUkFOVElF +UyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NF +IEFSRQpESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIEhPTERFUiBP +UiBDT05UUklCVVRPUlMgQkUgTElBQkxFCkZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURF +TlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMCkRBTUFHRVMgKElOQ0xV +RElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RT +IE9SClNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwgREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1Mg +SU5URVJSVVBUSU9OKSBIT1dFVkVSCkNBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJ +VFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksCk9SIFRPUlQgKElOQ0xV +RElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBU +SEUgVVNFCk9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJ +VFkgT0YgU1VDSCBEQU1BR0UuCg== diff --git a/src/native/mobile-bindings/dependency-licenses.json b/src/native/mobile-bindings/dependency-licenses.json index 8e47fb8b1..8bdf8876c 100644 --- a/src/native/mobile-bindings/dependency-licenses.json +++ b/src/native/mobile-bindings/dependency-licenses.json @@ -8,7 +8,7 @@ "cargoTarget": "aarch64-linux-android", "packages": [ "anyhow@1.0.103", - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "bytes@1.11.1", "camino@1.2.5", @@ -66,7 +66,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, @@ -74,7 +74,7 @@ "cargoTarget": "x86_64-linux-android", "packages": [ "anyhow@1.0.103", - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "bytes@1.11.1", "camino@1.2.5", @@ -132,7 +132,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, @@ -140,7 +140,7 @@ "cargoTarget": "armv7-linux-androideabi", "packages": [ "anyhow@1.0.103", - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "bytes@1.11.1", "camino@1.2.5", @@ -197,7 +197,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, @@ -205,7 +205,7 @@ "cargoTarget": "i686-linux-android", "packages": [ "anyhow@1.0.103", - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "bytes@1.11.1", "camino@1.2.5", @@ -263,7 +263,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, @@ -271,7 +271,7 @@ "cargoTarget": "aarch64-apple-ios", "packages": [ "anyhow@1.0.103", - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "bytes@1.11.1", "camino@1.2.5", @@ -329,7 +329,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, @@ -337,7 +337,7 @@ "cargoTarget": "aarch64-apple-ios-sim", "packages": [ "anyhow@1.0.103", - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "bytes@1.11.1", "camino@1.2.5", @@ -395,7 +395,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, @@ -403,7 +403,7 @@ "cargoTarget": "aarch64-apple-darwin", "packages": [ "anyhow@1.0.103", - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "bytes@1.11.1", "camino@1.2.5", @@ -461,7 +461,7 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] } @@ -497,8 +497,8 @@ }, { "name": "bitflags", - "version": "2.12.1", - "checksum": "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a", + "version": "2.13.2", + "checksum": "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": [ @@ -2130,10 +2130,10 @@ }, { "name": "zstd-sys", - "version": "2.0.16+zstd.1.5.7", - "checksum": "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748", - "declaredLicense": "MIT/Apache-2.0", - "selectedLicense": "MIT AND BSD-3-Clause", + "version": "2.1.0+zstd.1.5.7", + "checksum": "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0", + "declaredLicense": "BSD-3-Clause", + "selectedLicense": "BSD-3-Clause", "targets": [ "android-arm", "android-arm64", @@ -2146,23 +2146,13 @@ "licenseFiles": [ { "name": "LICENSE", - "sha256": "a77b7cfeaf911ed410ffbe76f0cb2b24ad8a4d94e7ead5727e914425c416cc63", - "bytes": 18 - }, - { - "name": "LICENSE.Apache-2.0", - "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "bytes": 10174 + "sha256": "ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60", + "bytes": 1501 }, { "name": "LICENSE.BSD-3-Clause", - "sha256": "48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd", - "bytes": 1595 - }, - { - "name": "LICENSE.Mit", - "sha256": "129e8edef29e9abcd2ebabe252f4ef1b1289cdca356bf0040284a2fbccfb96c8", - "bytes": 1080 + "sha256": "bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977", + "bytes": 1614 }, { "name": "zstd/COPYING", diff --git a/src/native/node-addon/Cargo.toml b/src/native/node-addon/Cargo.toml index a3e4b6ae1..289b8f0fa 100644 --- a/src/native/node-addon/Cargo.toml +++ b/src/native/node-addon/Cargo.toml @@ -11,8 +11,8 @@ crate-type = ["cdylib"] [dependencies] liboliphaunt-native-bindings = { path = "../rust-bindings" } -napi = { version = "=3.12.2", default-features = false, features = ["napi8"] } -napi-derive = { version = "=3.6.3", default-features = false, features = ["strict", "type-def"] } +napi = { version = "=3.14.0", default-features = false, features = ["napi8"] } +napi-derive = { version = "=3.6.10", default-features = false, features = ["strict", "type-def"] } [build-dependencies] -napi-build = "=2.4.1" +napi-build = "=2.6.0" diff --git a/src/native/node-addon/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 b/src/native/node-addon/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 deleted file mode 100644 index d8a50af44..000000000 --- a/src/native/node-addon/dependency-license-blobs/48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd.base64 +++ /dev/null @@ -1,28 +0,0 @@ -VGhlIGF1dG8tZ2VuZXJhdGVkIGJpbmRpbmdzIGFyZSB1bmRlciB0aGUgMy1jbGF1c2UgQlNEIGxp -Y2Vuc2U6CgpCU0QgTGljZW5zZQoKRm9yIFpzdGFuZGFyZCBzb2Z0d2FyZQoKQ29weXJpZ2h0IChj -KSAyMDE2LXByZXNlbnQsIEZhY2Vib29rLCBJbmMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuCgpSZWRp -c3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdp -dGhvdXQgbW9kaWZpY2F0aW9uLAphcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxv -d2luZyBjb25kaXRpb25zIGFyZSBtZXQ6CgogKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNv -ZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsIHRoaXMKICAgbGlzdCBv -ZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCgogKiBSZWRpc3RyaWJ1 -dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlIGNvcHlyaWdodCBu -b3RpY2UsCiAgIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2Ns -YWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb24KICAgYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92 -aWRlZCB3aXRoIHRoZSBkaXN0cmlidXRpb24uCgogKiBOZWl0aGVyIHRoZSBuYW1lIEZhY2Vib29r -IG5vciB0aGUgbmFtZXMgb2YgaXRzIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0bwogICBlbmRv -cnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZCBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91 -dCBzcGVjaWZpYwogICBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgpUSElTIFNPRlRXQVJFIElT -IFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTICJBUyBJ -UyIgQU5ECkFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQg -Tk9UIExJTUlURUQgVE8sIFRIRSBJTVBMSUVECldBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZ -IEFORCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUKRElTQ0xBSU1FRC4gSU4g -Tk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVCBIT0xERVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJ -QUJMRSBGT1IKQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsIFNQRUNJQUwsIEVYRU1Q -TEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTCihJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRF -RCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsKTE9TUyBP -RiBVU0UsIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZF -UiBDQVVTRUQgQU5EIE9OCkFOWSBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRS -QUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUCihJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBP -VEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRSBPRiBUSElTClNPRlRX -QVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLgo= diff --git a/src/native/node-addon/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 b/src/native/node-addon/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 new file mode 100644 index 000000000..b143c953c --- /dev/null +++ b/src/native/node-addon/dependency-license-blobs/bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977.base64 @@ -0,0 +1,29 @@ +VGhlIGF1dG8tZ2VuZXJhdGVkIGJpbmRpbmdzIGFyZSB1bmRlciB0aGUgMy1jbGF1c2UgQlNEIGxp +Y2Vuc2U6CgpCU0QgTGljZW5zZQoKRm9yIFpzdGFuZGFyZCBzb2Z0d2FyZQoKQ29weXJpZ2h0IChj +KSBNZXRhIFBsYXRmb3JtcywgSW5jLiBhbmQgYWZmaWxpYXRlcy4gQWxsIHJpZ2h0cyByZXNlcnZl +ZC4KClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdp +dGggb3Igd2l0aG91dCBtb2RpZmljYXRpb24sCmFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0 +aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDoKCiAqIFJlZGlzdHJpYnV0aW9ucyBvZiBz +b3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgdGhpcwog +ICBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KCiAqIFJl +ZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29w +eXJpZ2h0IG5vdGljZSwKICAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dp +bmcgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbgogICBhbmQvb3Igb3RoZXIgbWF0ZXJp +YWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KCiAqIE5laXRoZXIgdGhlIG5hbWUg +RmFjZWJvb2ssIG5vciBNZXRhLCBub3IgdGhlIG5hbWVzIG9mIGl0cyBjb250cmlidXRvcnMgbWF5 +CiAgIGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbSB0 +aGlzIHNvZnR3YXJlIHdpdGhvdXQKICAgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9u +LgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5E +IENPTlRSSUJVVE9SUyAiQVMgSVMiIEFORApBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJ +RVMsIElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRApXQVJSQU5USUVT +IE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0Ug +QVJFCkRJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQgSE9MREVSIE9S +IENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SCkFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVO +VEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUwooSU5DTFVE +SU5HLCBCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMg +T1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJ +TlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTgpBTlkgVEhFT1JZIE9GIExJQUJJTElU +WSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAooSU5DTFVE +SU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRI +RSBVU0UgT0YgVEhJUwpTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElU +WSBPRiBTVUNIIERBTUFHRS4K diff --git a/src/native/node-addon/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 b/src/native/node-addon/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 new file mode 100644 index 000000000..693c36fe3 --- /dev/null +++ b/src/native/node-addon/dependency-license-blobs/ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60.base64 @@ -0,0 +1,27 @@ +QlNEIDMtQ2xhdXNlIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiwgQWxleGFuZHJlIEJ1cnkK +ClJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGgg +b3Igd2l0aG91dAptb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUg +Zm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlIG1ldDoKCjEuIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3Vy +Y2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSwgdGhpcwogICBs +aXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KCjIuIFJlZGlz +dHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJp +Z2h0IG5vdGljZSwKICAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcg +ZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbgogICBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxz +IHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KCjMuIE5laXRoZXIgdGhlIG5hbWUgb2Yg +dGhlIGNvcHlyaWdodCBob2xkZXIgbm9yIHRoZSBuYW1lcyBvZiBpdHMKICAgY29udHJpYnV0b3Jz +IG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20K +ICAgdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lv +bi4KClRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFO +RCBDT05UUklCVVRPUlMgIkFTIElTIgpBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5U +SUVTLCBJTkNMVURJTkcsIEJVVCBOT1QgTElNSVRFRCBUTywgVEhFCklNUExJRUQgV0FSUkFOVElF +UyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NF +IEFSRQpESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIEhPTERFUiBP +UiBDT05UUklCVVRPUlMgQkUgTElBQkxFCkZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURF +TlRBTCwgU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMCkRBTUFHRVMgKElOQ0xV +RElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RT +IE9SClNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwgREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1Mg +SU5URVJSVVBUSU9OKSBIT1dFVkVSCkNBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJ +VFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksCk9SIFRPUlQgKElOQ0xV +RElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBU +SEUgVVNFCk9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJ +VFkgT0YgU1VDSCBEQU1BR0UuCg== diff --git a/src/native/node-addon/dependency-licenses.json b/src/native/node-addon/dependency-licenses.json index 6e3f4ecdb..ed6d6b2e0 100644 --- a/src/native/node-addon/dependency-licenses.json +++ b/src/native/node-addon/dependency-licenses.json @@ -7,25 +7,25 @@ "linux-x64-gnu": { "cargoTarget": "x86_64-unknown-linux-gnu", "packages": [ - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "cfg-if@1.0.4", - "convert_case@0.11.0", + "convert_case@0.12.0", "cpufeatures@0.2.17", "crypto-common@0.1.7", "ctor@1.0.13", "digest@0.10.7", "filetime@0.2.29", "fs2@0.4.3", - "futures-channel@0.3.32", - "futures-core@0.3.32", - "futures-executor@0.3.32", - "futures-io@0.3.32", - "futures-macro@0.3.32", - "futures-sink@0.3.32", - "futures-task@0.3.32", - "futures-util@0.3.32", - "futures@0.3.32", + "futures-channel@0.3.34", + "futures-core@0.3.34", + "futures-executor@0.3.34", + "futures-io@0.3.34", + "futures-macro@0.3.34", + "futures-sink@0.3.34", + "futures-task@0.3.34", + "futures-util@0.3.34", + "futures@0.3.34", "generic-array@0.14.7", "getrandom@0.3.4", "itoa@1.0.18", @@ -34,10 +34,10 @@ "libloading@0.9.0", "linux-raw-sys@0.12.1", "memchr@2.8.1", - "napi-derive-backend@6.1.2", - "napi-derive@3.6.3", - "napi-sys@3.3.0", - "napi@3.12.2", + "napi-derive-backend@6.1.4", + "napi-derive@3.6.10", + "napi-sys@3.4.0", + "napi@3.14.0", "nohash-hasher@0.2.0", "pin-project-lite@0.2.17", "proc-macro2@1.0.106", @@ -52,6 +52,7 @@ "sha2@0.10.9", "slab@0.4.12", "syn@2.0.117", + "syn@3.0.5", "tar@0.4.46", "typenum@1.20.1", "unicode-ident@1.0.24", @@ -59,32 +60,32 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, "linux-arm64-gnu": { "cargoTarget": "aarch64-unknown-linux-gnu", "packages": [ - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "cfg-if@1.0.4", - "convert_case@0.11.0", + "convert_case@0.12.0", "cpufeatures@0.2.17", "crypto-common@0.1.7", "ctor@1.0.13", "digest@0.10.7", "filetime@0.2.29", "fs2@0.4.3", - "futures-channel@0.3.32", - "futures-core@0.3.32", - "futures-executor@0.3.32", - "futures-io@0.3.32", - "futures-macro@0.3.32", - "futures-sink@0.3.32", - "futures-task@0.3.32", - "futures-util@0.3.32", - "futures@0.3.32", + "futures-channel@0.3.34", + "futures-core@0.3.34", + "futures-executor@0.3.34", + "futures-io@0.3.34", + "futures-macro@0.3.34", + "futures-sink@0.3.34", + "futures-task@0.3.34", + "futures-util@0.3.34", + "futures@0.3.34", "generic-array@0.14.7", "getrandom@0.3.4", "itoa@1.0.18", @@ -93,10 +94,10 @@ "libloading@0.9.0", "linux-raw-sys@0.12.1", "memchr@2.8.1", - "napi-derive-backend@6.1.2", - "napi-derive@3.6.3", - "napi-sys@3.3.0", - "napi@3.12.2", + "napi-derive-backend@6.1.4", + "napi-derive@3.6.10", + "napi-sys@3.4.0", + "napi@3.14.0", "nohash-hasher@0.2.0", "pin-project-lite@0.2.17", "proc-macro2@1.0.106", @@ -111,6 +112,7 @@ "sha2@0.10.9", "slab@0.4.12", "syn@2.0.117", + "syn@3.0.5", "tar@0.4.46", "typenum@1.20.1", "unicode-ident@1.0.24", @@ -118,17 +120,17 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, "macos-arm64": { "cargoTarget": "aarch64-apple-darwin", "packages": [ - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "cfg-if@1.0.4", - "convert_case@0.11.0", + "convert_case@0.12.0", "cpufeatures@0.2.17", "crypto-common@0.1.7", "ctor@1.0.13", @@ -136,15 +138,15 @@ "errno@0.3.14", "filetime@0.2.29", "fs2@0.4.3", - "futures-channel@0.3.32", - "futures-core@0.3.32", - "futures-executor@0.3.32", - "futures-io@0.3.32", - "futures-macro@0.3.32", - "futures-sink@0.3.32", - "futures-task@0.3.32", - "futures-util@0.3.32", - "futures@0.3.32", + "futures-channel@0.3.34", + "futures-core@0.3.34", + "futures-executor@0.3.34", + "futures-io@0.3.34", + "futures-macro@0.3.34", + "futures-sink@0.3.34", + "futures-task@0.3.34", + "futures-util@0.3.34", + "futures@0.3.34", "generic-array@0.14.7", "getrandom@0.3.4", "itoa@1.0.18", @@ -152,10 +154,10 @@ "libloading@0.8.9", "libloading@0.9.0", "memchr@2.8.1", - "napi-derive-backend@6.1.2", - "napi-derive@3.6.3", - "napi-sys@3.3.0", - "napi@3.12.2", + "napi-derive-backend@6.1.4", + "napi-derive@3.6.10", + "napi-sys@3.4.0", + "napi@3.14.0", "nohash-hasher@0.2.0", "pin-project-lite@0.2.17", "proc-macro2@1.0.106", @@ -170,6 +172,7 @@ "sha2@0.10.9", "slab@0.4.12", "syn@2.0.117", + "syn@3.0.5", "tar@0.4.46", "typenum@1.20.1", "unicode-ident@1.0.24", @@ -177,42 +180,42 @@ "xattr@1.6.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] }, "windows-x64-msvc": { "cargoTarget": "x86_64-pc-windows-msvc", "packages": [ - "bitflags@2.12.1", + "bitflags@2.13.2", "block-buffer@0.10.4", "cfg-if@1.0.4", - "convert_case@0.11.0", + "convert_case@0.12.0", "cpufeatures@0.2.17", "crypto-common@0.1.7", "ctor@1.0.13", "digest@0.10.7", "filetime@0.2.29", "fs2@0.4.3", - "futures-channel@0.3.32", - "futures-core@0.3.32", - "futures-executor@0.3.32", - "futures-io@0.3.32", - "futures-macro@0.3.32", - "futures-sink@0.3.32", - "futures-task@0.3.32", - "futures-util@0.3.32", - "futures@0.3.32", + "futures-channel@0.3.34", + "futures-core@0.3.34", + "futures-executor@0.3.34", + "futures-io@0.3.34", + "futures-macro@0.3.34", + "futures-sink@0.3.34", + "futures-task@0.3.34", + "futures-util@0.3.34", + "futures@0.3.34", "generic-array@0.14.7", "getrandom@0.3.4", "itoa@1.0.18", "libloading@0.8.9", "libloading@0.9.0", "memchr@2.8.1", - "napi-derive-backend@6.1.2", - "napi-derive@3.6.3", - "napi-sys@3.3.0", - "napi@3.12.2", + "napi-derive-backend@6.1.4", + "napi-derive@3.6.10", + "napi-sys@3.4.0", + "napi@3.14.0", "nohash-hasher@0.2.0", "pin-project-lite@0.2.17", "proc-macro2@1.0.106", @@ -226,6 +229,7 @@ "sha2@0.10.9", "slab@0.4.12", "syn@2.0.117", + "syn@3.0.5", "tar@0.4.46", "typenum@1.20.1", "unicode-ident@1.0.24", @@ -234,7 +238,7 @@ "windows-link@0.2.1", "zmij@1.0.21", "zstd-safe@7.2.4", - "zstd-sys@2.0.16+zstd.1.5.7", + "zstd-sys@2.1.0+zstd.1.5.7", "zstd@0.13.3" ] } @@ -242,8 +246,8 @@ "packages": [ { "name": "bitflags", - "version": "2.12.1", - "checksum": "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a", + "version": "2.13.2", + "checksum": "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -302,8 +306,8 @@ }, { "name": "convert_case", - "version": "0.11.0", - "checksum": "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49", + "version": "0.12.0", + "checksum": "1af709f1f33454bf52eadfc8c78b3b9ef9cb26fb54d16dc9cd9a7299f899fd1b", "declaredLicense": "MIT", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -457,8 +461,8 @@ }, { "name": "futures-channel", - "version": "0.3.32", - "checksum": "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d", + "version": "0.3.34", + "checksum": "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -477,8 +481,8 @@ }, { "name": "futures-core", - "version": "0.3.32", - "checksum": "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d", + "version": "0.3.34", + "checksum": "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -497,8 +501,8 @@ }, { "name": "futures-executor", - "version": "0.3.32", - "checksum": "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d", + "version": "0.3.34", + "checksum": "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -517,8 +521,8 @@ }, { "name": "futures-io", - "version": "0.3.32", - "checksum": "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718", + "version": "0.3.34", + "checksum": "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -537,8 +541,8 @@ }, { "name": "futures-macro", - "version": "0.3.32", - "checksum": "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b", + "version": "0.3.34", + "checksum": "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -557,8 +561,8 @@ }, { "name": "futures-sink", - "version": "0.3.32", - "checksum": "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893", + "version": "0.3.34", + "checksum": "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -577,8 +581,8 @@ }, { "name": "futures-task", - "version": "0.3.32", - "checksum": "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393", + "version": "0.3.34", + "checksum": "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -597,8 +601,8 @@ }, { "name": "futures-util", - "version": "0.3.32", - "checksum": "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6", + "version": "0.3.34", + "checksum": "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -617,8 +621,8 @@ }, { "name": "futures", - "version": "0.3.32", - "checksum": "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d", + "version": "0.3.34", + "checksum": "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3", "declaredLicense": "MIT OR Apache-2.0", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -797,8 +801,8 @@ }, { "name": "napi-derive-backend", - "version": "6.1.2", - "checksum": "df4056ac7c18e4438ccf0edaed4340ca0d269278c8ec19284f7b23cb039fd0ae", + "version": "6.1.4", + "checksum": "fd6856fe1686867da90ed0dc292d7c0c87a695eb7c578b913360f33619ee01e3", "declaredLicense": "MIT", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -809,7 +813,7 @@ "bytes": 2138, "upstream": { "repository": "https://github.com/napi-rs/napi-rs", - "commit": "956e4525fea6a676ea3680b711382f167b899af9", + "commit": "38162bb0eb324ae24b402982bad3d4ef3f24c90a", "path": "LICENSE" } } @@ -817,8 +821,8 @@ }, { "name": "napi-derive", - "version": "3.6.3", - "checksum": "0fa55ea69990c90b888e9e77044410e304ce7f35de599dc6d0b5c1923d2e59af", + "version": "3.6.10", + "checksum": "d11174f7507d7f6cdd69874f541eff194e81c95652b775e69036bcf8d5b7a805", "declaredLicense": "MIT", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -829,7 +833,7 @@ "bytes": 2138, "upstream": { "repository": "https://github.com/napi-rs/napi-rs", - "commit": "956e4525fea6a676ea3680b711382f167b899af9", + "commit": "37109f133043aff379a38edfb8b1548e7e9309e5", "path": "LICENSE" } } @@ -837,8 +841,8 @@ }, { "name": "napi-sys", - "version": "3.3.0", - "checksum": "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a", + "version": "3.4.0", + "checksum": "e22a4f25c16a5c5411d987cd6fbd48313522ae1789ade2d6dd3efdc6d40a0fc8", "declaredLicense": "MIT", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -849,7 +853,7 @@ "bytes": 2138, "upstream": { "repository": "https://github.com/napi-rs/napi-rs", - "commit": "679eb79f5cf3c7c6b2850f4ab46092126f23dc5c", + "commit": "37109f133043aff379a38edfb8b1548e7e9309e5", "path": "LICENSE" } } @@ -857,8 +861,8 @@ }, { "name": "napi", - "version": "3.12.2", - "checksum": "58c5f4d5375213fdb7be2655e152386e82f026f9a5ba36a75556e11359aafe09", + "version": "3.14.0", + "checksum": "15301c22171e8397a7b17beb3c75bf486bf67ca3bdad8ea347835b770df61969", "declaredLicense": "MIT", "selectedLicense": "MIT", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], @@ -869,7 +873,7 @@ "bytes": 2138, "upstream": { "repository": "https://github.com/napi-rs/napi-rs", - "commit": "444bf29b8534216dd1cec4695a71e5996a173e87", + "commit": "37109f133043aff379a38edfb8b1548e7e9309e5", "path": "LICENSE" } } @@ -1160,6 +1164,26 @@ } ] }, + { + "name": "syn", + "version": "3.0.5", + "checksum": "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9", + "declaredLicense": "MIT OR Apache-2.0", + "selectedLicense": "MIT", + "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], + "licenseFiles": [ + { + "name": "LICENSE-APACHE", + "sha256": "62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a", + "bytes": 9723 + }, + { + "name": "LICENSE-MIT", + "sha256": "23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3", + "bytes": 1023 + } + ] + }, { "name": "tar", "version": "0.4.46", @@ -1357,31 +1381,21 @@ }, { "name": "zstd-sys", - "version": "2.0.16+zstd.1.5.7", - "checksum": "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748", - "declaredLicense": "MIT/Apache-2.0", - "selectedLicense": "MIT AND BSD-3-Clause", + "version": "2.1.0+zstd.1.5.7", + "checksum": "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0", + "declaredLicense": "BSD-3-Clause", + "selectedLicense": "BSD-3-Clause", "targets": ["linux-arm64-gnu", "linux-x64-gnu", "macos-arm64", "windows-x64-msvc"], "licenseFiles": [ { "name": "LICENSE", - "sha256": "a77b7cfeaf911ed410ffbe76f0cb2b24ad8a4d94e7ead5727e914425c416cc63", - "bytes": 18 - }, - { - "name": "LICENSE.Apache-2.0", - "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", - "bytes": 10174 + "sha256": "ce4868c8e4ebe47a7d4b3c93dbf6b802033e5ae2fa7638098173f80836daac60", + "bytes": 1501 }, { "name": "LICENSE.BSD-3-Clause", - "sha256": "48341f685c87304089aa099b23c386f8bacc519ef555aa7a13e239908907b3fd", - "bytes": 1595 - }, - { - "name": "LICENSE.Mit", - "sha256": "129e8edef29e9abcd2ebabe252f4ef1b1289cdca356bf0040284a2fbccfb96c8", - "bytes": 1080 + "sha256": "bf7017a73a95ea63bf3716b13ec2bcd79c92942356e3408c6fb66332bad6d977", + "bytes": 1614 }, { "name": "zstd/COPYING", diff --git a/src/native/node-addon/src/lib.rs b/src/native/node-addon/src/lib.rs index d69e74851..a27da36ad 100644 --- a/src/native/node-addon/src/lib.rs +++ b/src/native/node-addon/src/lib.rs @@ -439,7 +439,7 @@ pub fn restore(env: &Env, options: RestoreOptions) -> Result> { } fn js_bytes(env: &Env, bytes: &[u8]) -> Result { - // Reuse the current WASIX addon buffer contract, including napi 3.12.2 copy initialization. + // As in the WASIX addon, initialize the allocation returned by napi-rs. let mut output = Uint8ArraySlice::copy_from(env, bytes)?; unsafe { output.as_mut() }.copy_from_slice(bytes); output.into_typed_array(env) diff --git a/src/query/rust/src/lib.rs b/src/query/rust/src/lib.rs index 506f87dc5..0d5e5ab85 100644 --- a/src/query/rust/src/lib.rs +++ b/src/query/rust/src/lib.rs @@ -880,7 +880,9 @@ fn decode_text_bytea(value: ValueRef<'_>, raw: &[u8]) -> std::result::Result", "hex bytea has odd length"); } return hex - .chunks_exact(2) + .as_chunks::<2>() + .0 + .iter() .map(|pair| { let digit = |byte: u8| match byte { b'0'..=b'9' => Some(byte - b'0'), diff --git a/src/wasix/node-addon/Cargo.toml b/src/wasix/node-addon/Cargo.toml index 2b10611b0..14e7ac1f0 100644 --- a/src/wasix/node-addon/Cargo.toml +++ b/src/wasix/node-addon/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-napi" version = "0.2.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Node-API adapter for the Oliphaunt WASIX Rust runtime." repository = "https://github.com/f0rr0/oliphaunt" homepage = "https://oliphaunt.dev" @@ -97,8 +97,8 @@ extension-vector = ["extensions", "oliphaunt-wasix/extension-vector"] serde_json = "1" sha2 = "0.10" oliphaunt-pgwire-server = { path = "../pgwire-server" } -napi = { version = "=3.12.2", default-features = false, features = ["napi8"] } -napi-derive = { version = "=3.6.3", default-features = false, features = ["strict", "type-def"] } +napi = { version = "=3.14.0", default-features = false, features = ["napi8"] } +napi-derive = { version = "=3.6.10", default-features = false, features = ["strict", "type-def"] } liboliphaunt-wasix-portable = { version = "*", path = "../runtime/crates/assets" } oliphaunt-wasix = { version = "*", path = "../sdks/rust", features = ["__internal-napi"] } @@ -106,4 +106,4 @@ oliphaunt-wasix = { version = "*", path = "../sdks/rust", features = ["__interna rustix = { version = "=1.1.4", features = ["fs"] } [build-dependencies] -napi-build = "=2.4.1" +napi-build = "=2.6.0" diff --git a/src/wasix/node-addon/README.md b/src/wasix/node-addon/README.md index a98232c50..84e5e1bbc 100644 --- a/src/wasix/node-addon/README.md +++ b/src/wasix/node-addon/README.md @@ -127,7 +127,7 @@ The supported target set is intentionally closed: macOS arm64, Linux arm64 or x64 with glibc, and Windows x64 with MSVC. macOS x64, Linux musl, and Windows arm64 do not have carriers. The native builder detects its Linux libc and rejects musl or an unidentifiable libc before compiling a GNU carrier. The -Linux release addons are then compiled inside the pinned Rust 1.93.1 Debian +Linux release addons are then compiled inside the pinned Rust 1.99.0 Debian Bookworm image (glibc 2.36), with exact payload paths mounted read-only and the actual build run without network access. This keeps them below the published glibc 2.38 ceiling; release staging also validates their ELF shape and resolves diff --git a/src/wasix/node-addon/src/lib.rs b/src/wasix/node-addon/src/lib.rs index eb1f5004b..583fbb4f3 100644 --- a/src/wasix/node-addon/src/lib.rs +++ b/src/wasix/node-addon/src/lib.rs @@ -957,7 +957,7 @@ fn static_object(env: &Env, object: Object<'_>) -> Object<'static> { } fn v8_owned_bytes(env: &Env, bytes: &[u8]) -> Result { - // napi-rs 3.12.2 allocates here but does not initialize the new + // napi-rs allocates here but does not initialize the new // ArrayBuffer from `bytes`; fill the V8-owned allocation explicitly. let mut output = Uint8ArraySlice::copy_from(env, bytes)?; // SAFETY: the new ArrayBuffer is not observable by JavaScript until this diff --git a/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh b/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh index d6d2751f9..76b021f65 100755 --- a/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh +++ b/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh @@ -38,12 +38,12 @@ features="$3" [ "$target_triple" = "$rust_host" ] || fail "target $target_triple does not match native host $rust_host" [ "$features" = "release" ] || fail "unsupported WASIX Node-API feature set: $features" -# Official rust:1.93.1-slim-bookworm, pinned as one multi-architecture OCI +# Official rust:1.99.0-slim-bookworm, pinned as one multi-architecture OCI # index. Bookworm's glibc 2.36 is below the published glibc 2.38 ceiling. The # Fedora 39 consumer rehearsal still checks the completed addon under 2.38. -readonly image="rust@sha256:5b9332190bb3b9ece73b810cd1f1e9f06343b294ce184bcb067f0747d7d333ea" -readonly rust_release="1.93.1" -readonly rust_commit="01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf" +readonly image="rust@sha256:452176c0cefca88c0b3184ce85a4eb03e3d4fa05d2afb5366abcba853221019e" +readonly rust_release="1.99.0" +readonly rust_commit="b940084d7eb6a299eb4bfeb8e34901bc051e7ac4" readonly rust_toolchain="${rust_release}-${rust_host}" readonly expected_builder_glibc="glibc 2.36" readonly manifest="/workspace/src/wasix/node-addon/Cargo.toml" diff --git a/src/wasix/pgwire-server/Cargo.toml b/src/wasix/pgwire-server/Cargo.toml index fee3eb78f..43197871f 100644 --- a/src/wasix/pgwire-server/Cargo.toml +++ b/src/wasix/pgwire-server/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-pgwire-server" version = "0.2.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" license = "MIT" repository.workspace = true homepage.workspace = true diff --git a/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/build-support.rs b/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/build-support.rs index 38f078a56..e8ca99b64 100644 --- a/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/build-support.rs +++ b/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/build-support.rs @@ -188,7 +188,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/build-support.rs b/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/build-support.rs index 38f078a56..e8ca99b64 100644 --- a/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/build-support.rs +++ b/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/build-support.rs @@ -188,7 +188,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs b/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs index 38f078a56..e8ca99b64 100644 --- a/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs +++ b/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs @@ -188,7 +188,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/build-support.rs b/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/build-support.rs index 38f078a56..e8ca99b64 100644 --- a/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/build-support.rs +++ b/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/build-support.rs @@ -188,7 +188,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/runtime/crates/aot/aarch64-apple-darwin/build-support.rs b/src/wasix/runtime/crates/aot/aarch64-apple-darwin/build-support.rs index 7de9cfe54..7d317c193 100644 --- a/src/wasix/runtime/crates/aot/aarch64-apple-darwin/build-support.rs +++ b/src/wasix/runtime/crates/aot/aarch64-apple-darwin/build-support.rs @@ -182,7 +182,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/build-support.rs b/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/build-support.rs index 7de9cfe54..7d317c193 100644 --- a/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/build-support.rs +++ b/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/build-support.rs @@ -182,7 +182,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs b/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs index 7de9cfe54..7d317c193 100644 --- a/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs +++ b/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs @@ -182,7 +182,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/build-support.rs b/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/build-support.rs index 7de9cfe54..7d317c193 100644 --- a/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/build-support.rs +++ b/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/build-support.rs @@ -182,7 +182,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.2.1\",\"wasmer-wasix-version\":\"0.702.1\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ diff --git a/src/wasix/runtime/toolchain.toml b/src/wasix/runtime/toolchain.toml index 78ddde979..606c9b43a 100644 --- a/src/wasix/runtime/toolchain.toml +++ b/src/wasix/runtime/toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -wasmer = "7.2.1" -wasmer-wasix = "0.702.1" -webc = "12.0.0" +wasmer = "7.5.0" +wasmer-wasix = "0.705.0" +webc = "12.0.1" diff --git a/src/wasix/runtime/tools/xtask/Cargo.toml b/src/wasix/runtime/tools/xtask/Cargo.toml index 368e33340..ba2f1c978 100644 --- a/src/wasix/runtime/tools/xtask/Cargo.toml +++ b/src/wasix/runtime/tools/xtask/Cargo.toml @@ -2,7 +2,7 @@ name = "xtask" version = "0.0.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" license.workspace = true publish = false @@ -24,9 +24,9 @@ sha2 = "0.10" tar = "0.4" toml = "0.9" walkdir = "2" -wasmer = { version = "=7.2.1", default-features = false, features = [ +wasmer = { version = "=7.5.0", default-features = false, features = [ "sys", ], optional = true } -wasmer-types = { version = "=7.2.1", optional = true } -wasmparser = "0.250.0" +wasmer-types = { version = "=7.5.0", optional = true } +wasmparser = "0.261.0" zstd = "0.13" diff --git a/src/wasix/runtime/tools/xtask/src/aot_serializer.rs b/src/wasix/runtime/tools/xtask/src/aot_serializer.rs index 647407b9d..5849e7e5e 100644 --- a/src/wasix/runtime/tools/xtask/src/aot_serializer.rs +++ b/src/wasix/runtime/tools/xtask/src/aot_serializer.rs @@ -16,8 +16,8 @@ use zstd::stream::write::Encoder as ZstdEncoder; #[cfg(feature = "aot-serializer")] use crate::value_after; -// Wasmer 7.2.1's deterministic_id omits these codegen choices. Keep our -// artifact identity explicit; never label strict and nonvolatile code alike. +// This profile names the fixed codegen policy in our published manifests; +// Wasmer's deterministic_id independently identifies the compiler settings. pub(crate) const AOT_ENGINE_PROFILE: &str = "llvm-opta"; pub(crate) fn check_aot_codegen_environment() -> Result<()> { diff --git a/src/wasix/runtime/tools/xtask/src/asset_manifest.rs b/src/wasix/runtime/tools/xtask/src/asset_manifest.rs index ae29397c8..d7a065bb6 100644 --- a/src/wasix/runtime/tools/xtask/src/asset_manifest.rs +++ b/src/wasix/runtime/tools/xtask/src/asset_manifest.rs @@ -398,7 +398,7 @@ pub(super) fn read_wasm_link_metadata(path: &Path) -> Result {} + Dylink0Subsection::TargetArch(_) | Dylink0Subsection::Unknown { .. } => {} } } } diff --git a/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs b/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs index e8a3ac51c..a9da24168 100644 --- a/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs +++ b/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs @@ -2716,6 +2716,9 @@ mod tests { source_fingerprint: Option<&str>, postgres_version: Option<&str>, ) { + let toolchain = load_wasix_toolchain_manifest() + .expect("WASIX toolchain manifest") + .toolchain; let manifest = AotManifest { format_version: AOT_MANIFEST_FORMAT_VERSION, source_lane: source_lane.map(str::to_owned), @@ -2723,8 +2726,8 @@ mod tests { postgres_version: postgres_version.map(str::to_owned), target_triple: "aarch64-apple-darwin".to_owned(), engine: crate::aot_serializer::AOT_ENGINE_PROFILE.to_owned(), - wasmer_version: "7.2.1".to_owned(), - wasmer_wasix_version: "0.702.1".to_owned(), + wasmer_version: toolchain.wasmer, + wasmer_wasix_version: toolchain.wasmer_wasix, artifacts: vec![AotManifestArtifact { name: "runtime:oliphaunt".to_owned(), path: "oliphaunt.aot.zst".to_owned(), diff --git a/src/wasix/sdks/rust/Cargo.toml b/src/wasix/sdks/rust/Cargo.toml index 2aa993c3e..214676143 100644 --- a/src/wasix/sdks/rust/Cargo.toml +++ b/src/wasix/sdks/rust/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Embedded PostgreSQL 18 for Rust with synchronous and asynchronous APIs." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" @@ -102,7 +102,7 @@ liboliphaunt-wasix-portable = { version = "*", path = "../../runtime/crates/asse oliphaunt-wasix-tools = { version = "*", path = "../../postgres-tools/crates/tools", optional = true } oliphaunt-icu = { version = "*", path = "../../../database-resources/icu/cargo", optional = true } tokio = { version = "1", features = ["io-util", "rt-multi-thread", "sync"] } -wasmer = { version = "=7.2.1", default-features = false, features = [ +wasmer = { version = "=7.5.0", default-features = false, features = [ "sys", "headless", "compiler", @@ -112,21 +112,21 @@ wasmer = { version = "=7.2.1", default-features = false, features = [ # release family. Keep the complete family constrained here so a fresh # consumer cannot silently mix a later patch generation with the runtime and # AOT artifacts built against this exact toolchain. -wasmer-config = { version = "=0.702.1", default-features = false } -wasmer-journal = { version = "=0.702.1", default-features = false } -wasmer-package = { version = "=0.702.1", default-features = false } -wasmer-types = "=7.2.1" -wasmer-wasix = { version = "=0.702.1", default-features = false, features = [ +wasmer-config = { version = "=0.705.0", default-features = false } +wasmer-journal = { version = "=0.705.0", default-features = false } +wasmer-package = { version = "=0.705.0", default-features = false } +wasmer-types = "=7.5.0" +wasmer-wasix = { version = "=0.705.0", default-features = false, features = [ "sys-minimal", "sys-poll", "host-vnet", "time", ] } -wasmer-wasix-types = { version = "=0.702.1", default-features = false } -virtual-fs = { version = "=0.702.1", default-features = false } -virtual-mio = { version = "=0.702.1", default-features = false } -virtual-net = { version = "=0.702.1", default-features = false } -webc = "=12.0.0" +wasmer-wasix-types = { version = "=0.705.0", default-features = false } +virtual-fs = { version = "=0.705.0", default-features = false } +virtual-mio = { version = "=0.705.0", default-features = false } +virtual-net = { version = "=0.705.0", default-features = false } +webc = "=12.0.1" [target.'cfg(all(target_os = "macos", target_arch = "aarch64"))'.dependencies] liboliphaunt-wasix-aot-aarch64-apple-darwin = { version = "*", path = "../../runtime/crates/aot/aarch64-apple-darwin" } diff --git a/src/wasix/sdks/rust/README.md b/src/wasix/sdks/rust/README.md index 7922d9168..c03101969 100644 --- a/src/wasix/sdks/rust/README.md +++ b/src/wasix/sdks/rust/README.md @@ -2,6 +2,8 @@ Host WebAssembly PostgreSQL in a Rust application. The synchronous handle must be created, used, and dropped on one OS thread. Use `AsyncOliphaunt` for a cloneable `Send + Sync` owner. +Requires Rust 1.96 or newer. + ## Install ```sh diff --git a/src/wasix/sdks/rust/src/oliphaunt/aot.rs b/src/wasix/sdks/rust/src/oliphaunt/aot.rs index a73117026..7296b9c09 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/aot.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/aot.rs @@ -16,15 +16,15 @@ use super::assets; const RUNTIME_ARTIFACT: &str = "runtime:oliphaunt"; const EXPECTED_AOT_ENGINE: &str = "llvm-opta"; -const EXPECTED_WASMER_VERSION: &str = "7.2.1"; -const EXPECTED_WASMER_WASIX_VERSION: &str = "0.702.1"; +const EXPECTED_WASMER_VERSION: &str = "7.5.0"; +const EXPECTED_WASMER_WASIX_VERSION: &str = "0.705.0"; const AOT_ENGINE_ID: &str = concat!( "engine=", "llvm-opta", ";wasmer=", - "7.2.1", + "7.5.0", ";wasmer-wasix=", - "0.702.1", + "0.705.0", ";cpu=generic-baseline" ); const ZSTD_MAGIC: &[u8] = &[0x28, 0xb5, 0x2f, 0xfd]; diff --git a/src/wasix/sdks/rust/src/oliphaunt/base.rs b/src/wasix/sdks/rust/src/oliphaunt/base.rs index 623fcd44c..16aca7079 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/base.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/base.rs @@ -1113,7 +1113,7 @@ fn logical_tree_sha256(root: &Path) -> Result { digest.update(size.to_string().as_bytes()); digest.update([0]); digest.update(fs::read(&file).with_context(|| format!("read {}", file.display()))?); - digest.update([b'\n']); + digest.update(b"\n"); } Ok(format!("{:x}", digest.finalize())) } diff --git a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs index dbfdb8376..9b7c15620 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs @@ -1545,6 +1545,7 @@ fn split_initdb_binary_package( Ok(BinaryPackage { id: package_id.clone(), package_ids: vec![package_id.clone()], + webc_version: webc::Version::V3, when_cached: None, entrypoint_cmd: Some("initdb".to_owned()), hash: Default::default(), From 5957b7c6c3ad7992268a5a8aeef9e0f1966c5362 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Sun, 4 Oct 2026 10:45:31 +0000 Subject: [PATCH 2/4] build: standardize on the minimum shared Rust toolchain Use Rust 1.96.0 for the workspace and both baseline container builders, with a common 1.96 minimum in maintained and generated Cargo packages. Let private workspace tools inherit the shared minimum. --- Cargo.toml | 2 +- rust-toolchain.toml | 2 +- src/benchmarks/perf/runner/Cargo.toml | 3 +-- src/database-resources/icu/cargo/Cargo.toml | 2 +- src/database-resources/seeds/package-carriers.mts | 2 +- src/database-resources/seeds/wasix/Cargo.toml | 2 +- src/docs/maintainers/release.md | 12 ++++++------ .../tools/package-extension-cargo-facades.mts | 2 +- .../tools/package-extension-release-carriers.mts | 6 +++--- src/native/broker/Cargo.toml | 2 +- src/native/broker/crates/linux-arm64-gnu/Cargo.toml | 2 +- src/native/broker/crates/linux-x64-gnu/Cargo.toml | 2 +- src/native/broker/crates/macos-arm64/Cargo.toml | 2 +- src/native/broker/crates/windows-x64-msvc/Cargo.toml | 2 +- .../broker/tools/build-linux-broker-baseline.sh | 8 ++++---- src/native/postgres-tools/crates/tools/Cargo.toml | 2 +- src/native/sdks/rust/Cargo.toml | 2 +- .../sdks/rust/crates/oliphaunt-build/Cargo.toml | 2 +- src/wasix/node-addon/Cargo.toml | 2 +- src/wasix/node-addon/README.md | 2 +- .../tools/build-linux-wasix-napi-baseline.sh | 8 ++++---- .../crates/aot/aarch64-apple-darwin/Cargo.toml | 2 +- .../crates/aot/aarch64-unknown-linux-gnu/Cargo.toml | 2 +- .../crates/aot/x86_64-pc-windows-msvc/Cargo.toml | 2 +- .../crates/aot/x86_64-unknown-linux-gnu/Cargo.toml | 2 +- src/wasix/postgres-tools/crates/tools/Cargo.toml | 2 +- src/wasix/postmaster/executor/Cargo.toml | 2 +- .../crates/aot/aarch64-apple-darwin/Cargo.toml | 2 +- .../crates/aot/aarch64-unknown-linux-gnu/Cargo.toml | 2 +- .../crates/aot/x86_64-pc-windows-msvc/Cargo.toml | 2 +- .../crates/aot/x86_64-unknown-linux-gnu/Cargo.toml | 2 +- src/wasix/runtime/crates/assets/Cargo.toml | 2 +- .../package_liboliphaunt_wasix_cargo_artifacts.mts | 6 +++--- src/wasix/runtime/tools/xtask/Cargo.toml | 2 +- tools/packaging/native-cargo-payload.mts | 4 ++-- tools/release/public-consumer-smoke.test.mts | 2 +- 36 files changed, 52 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6d658821..36dee7f08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ resolver = "3" [workspace.package] edition = "2024" -rust-version = "1.93" +rust-version = "1.96" repository = "https://github.com/f0rr0/oliphaunt" homepage = "https://oliphaunt.dev" license = "MIT" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fd7a8005d..3091c5b3e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.99.0" +channel = "1.96.0" components = ["rustfmt", "clippy", "llvm-tools-preview"] profile = "minimal" diff --git a/src/benchmarks/perf/runner/Cargo.toml b/src/benchmarks/perf/runner/Cargo.toml index 57f0c0db1..acbaf4043 100644 --- a/src/benchmarks/perf/runner/Cargo.toml +++ b/src/benchmarks/perf/runner/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-perf" version = "0.0.0" edition = "2024" -rust-version = "1.93" +rust-version.workspace = true license.workspace = true publish = false default-run = "oliphaunt-perf" @@ -24,4 +24,3 @@ sqlx = { version = "0.8", default-features = false, features = [ tar = "0.4" tokio = { version = "1", features = ["rt-multi-thread"] } tokio-postgres = "0.7" - diff --git a/src/database-resources/icu/cargo/Cargo.toml b/src/database-resources/icu/cargo/Cargo.toml index 253d301f7..fa5cfb20d 100644 --- a/src/database-resources/icu/cargo/Cargo.toml +++ b/src/database-resources/icu/cargo/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-icu" version = "0.2.2" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Optional ICU data files for Oliphaunt runtimes." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/src/database-resources/seeds/package-carriers.mts b/src/database-resources/seeds/package-carriers.mts index 7cac2e9d3..9284883e1 100644 --- a/src/database-resources/seeds/package-carriers.mts +++ b/src/database-resources/seeds/package-carriers.mts @@ -186,7 +186,7 @@ export function packageSeedCarriers(argv = [], { assetDir, outputDir, sourceDir ); writeFileSync( path.join(stage, 'Cargo.toml'), - `[package]\nname = ${JSON.stringify(identity.cargo)}\nversion = ${JSON.stringify(version)}\nedition = "2024"\nrust-version = "1.93"\ndescription = ${JSON.stringify(npm.description)}\nlicense = ${JSON.stringify(license)}\nrepository = "https://github.com/f0rr0/oliphaunt"\ninclude = ["src/**", "seed.tar.zst", "manifest.json", "README.md", "LICENSE", "THIRD_PARTY*"]\n${identity.profile === 'icu' ? `\n[dependencies]\noliphaunt-icu = "=${version}"\n` : ''}\n[workspace]\n`, + `[package]\nname = ${JSON.stringify(identity.cargo)}\nversion = ${JSON.stringify(version)}\nedition = "2024"\nrust-version = "1.96"\ndescription = ${JSON.stringify(npm.description)}\nlicense = ${JSON.stringify(license)}\nrepository = "https://github.com/f0rr0/oliphaunt"\ninclude = ["src/**", "seed.tar.zst", "manifest.json", "README.md", "LICENSE", "THIRD_PARTY*"]\n${identity.profile === 'icu' ? `\n[dependencies]\noliphaunt-icu = "=${version}"\n` : ''}\n[workspace]\n`, ); const crate = packageGeneratedCargoSource( path.join(stage, 'Cargo.toml'), diff --git a/src/database-resources/seeds/wasix/Cargo.toml b/src/database-resources/seeds/wasix/Cargo.toml index 8ee6c4ff3..d35c86dde 100644 --- a/src/database-resources/seeds/wasix/Cargo.toml +++ b/src/database-resources/seeds/wasix/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-seed-producer" version = "0.0.0" edition = "2024" -rust-version = "1.96" +rust-version.workspace = true license.workspace = true publish = false diff --git a/src/docs/maintainers/release.md b/src/docs/maintainers/release.md index b733be98c..02e3e4597 100644 --- a/src/docs/maintainers/release.md +++ b/src/docs/maintainers/release.md @@ -726,12 +726,12 @@ Release qualification must inspect the binaries themselves: Mach-O requirements must satisfy these floors. Package labels, runner versions, and successful builds are not compatibility evidence by themselves. -Rust 1.93.1's Linux `std::process` implementation contains weak -`pidfd_getpid`/`pidfd_spawnp` references. Linking the broker directly on an -Ubuntu 24.04 runner binds those otherwise optional references to -`GLIBC_2.39`, which raises the load-time floor even when the fast path is never -used. Linux broker release assets are therefore linked from a clean target -directory in the exact, digest-pinned `rust:1.93.1-slim-bookworm` container. +Rust's Linux `std::process` implementation can reference optional newer glibc +symbols. Linking on a newer runner can bind those references at link time, +raising the load-time floor even when the fast path is never used. Linux +broker and WASIX addon release assets are therefore linked from clean target +directories in the same exact, digest-pinned `rust:1.96.0-slim-bookworm` +container, matching the repository Rust toolchain. After any bounded digest-pinned image acquisition, only Cargo's locked dependency-fetch phase may use the network inside the container; package code and build scripts run in a read-only, networkless, capability-free container. diff --git a/src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts b/src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts index fa045561a..fa79f0e95 100644 --- a/src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts +++ b/src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts @@ -186,7 +186,7 @@ export function writeFacadeSource(product, outputRoot, { dependencyPaths = {} } name = ${JSON.stringify(product)} version = ${JSON.stringify(version)} edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = ${JSON.stringify(`Target-selecting Cargo facade for ${sqlNames.length} Oliphaunt PostgreSQL extension member${sqlNames.length === 1 ? '' : 's'}.`)} readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts b/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts index 67ee51748..8f9c99fae 100644 --- a/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts +++ b/src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts @@ -2169,7 +2169,7 @@ function writeNativeExtensionCargoPartCrate( name = "${name}" version = "${version}" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo payload part ${String(index).padStart(3, '0')} for the ${subject} Oliphaunt native extension carrier on ${target}." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" @@ -2622,7 +2622,7 @@ export function writeNativeExtensionSplitAggregatorCrate( name = "${name}" version = "${version}" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo artifact crate for the ${subject} Oliphaunt native extension carrier on ${target}." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" @@ -2764,7 +2764,7 @@ Cargo artifact crate for the ${subject} Oliphaunt native extension carrier on \` name = "${name}" version = "${version}" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo artifact crate for the ${subject} Oliphaunt native extension carrier on ${target}." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/src/native/broker/Cargo.toml b/src/native/broker/Cargo.toml index 9c634802e..c712c6273 100644 --- a/src/native/broker/Cargo.toml +++ b/src/native/broker/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-broker" version = "0.3.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Oliphaunt broker helper process for process-isolated embedded PostgreSQL." readme = "README.md" repository.workspace = true diff --git a/src/native/broker/crates/linux-arm64-gnu/Cargo.toml b/src/native/broker/crates/linux-arm64-gnu/Cargo.toml index 5884fc099..44f477d09 100644 --- a/src/native/broker/crates/linux-arm64-gnu/Cargo.toml +++ b/src/native/broker/crates/linux-arm64-gnu/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-broker-linux-arm64-gnu" version = "0.3.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo artifact crate for the linux-arm64-gnu oliphaunt-broker helper binary." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/src/native/broker/crates/linux-x64-gnu/Cargo.toml b/src/native/broker/crates/linux-x64-gnu/Cargo.toml index 7002e6aae..1b14ab78f 100644 --- a/src/native/broker/crates/linux-x64-gnu/Cargo.toml +++ b/src/native/broker/crates/linux-x64-gnu/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-broker-linux-x64-gnu" version = "0.3.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo artifact crate for the linux-x64-gnu oliphaunt-broker helper binary." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/src/native/broker/crates/macos-arm64/Cargo.toml b/src/native/broker/crates/macos-arm64/Cargo.toml index 814864888..a8bf7efae 100644 --- a/src/native/broker/crates/macos-arm64/Cargo.toml +++ b/src/native/broker/crates/macos-arm64/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-broker-macos-arm64" version = "0.3.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo artifact crate for the macos-arm64 oliphaunt-broker helper binary." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/src/native/broker/crates/windows-x64-msvc/Cargo.toml b/src/native/broker/crates/windows-x64-msvc/Cargo.toml index cc7f0c172..ec6c998ea 100644 --- a/src/native/broker/crates/windows-x64-msvc/Cargo.toml +++ b/src/native/broker/crates/windows-x64-msvc/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-broker-windows-x64-msvc" version = "0.3.0" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo artifact crate for the windows-x64-msvc oliphaunt-broker helper binary." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/src/native/broker/tools/build-linux-broker-baseline.sh b/src/native/broker/tools/build-linux-broker-baseline.sh index c1845dae3..32451445b 100755 --- a/src/native/broker/tools/build-linux-broker-baseline.sh +++ b/src/native/broker/tools/build-linux-broker-baseline.sh @@ -28,13 +28,13 @@ case "$(uname -m)" in *) fail "unsupported Linux architecture $(uname -m)" ;; esac -# Official rust:1.93.1-slim-bookworm, pinned as one multi-architecture OCI index. +# Official rust:1.96.0-slim-bookworm, pinned as one multi-architecture OCI index. # Debian Bookworm links the broker below the repository-wide glibc 2.38 ceiling # while retaining the normal linux-*-gnu carrier ABI and the exact workspace # Rust release. Never replace this with a moving tag. -readonly image="rust@sha256:5b9332190bb3b9ece73b810cd1f1e9f06343b294ce184bcb067f0747d7d333ea" -readonly rust_release="1.93.1" -readonly rust_commit="01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf" +readonly image="rust@sha256:4732ca96fd086cb9be682050c3f0176288eebaac2b80aa2bcefccfaf198e1950" +readonly rust_release="1.96.0" +readonly rust_commit="ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96" readonly rust_toolchain="${rust_release}-${rust_host}" target_dir="$1" diff --git a/src/native/postgres-tools/crates/tools/Cargo.toml b/src/native/postgres-tools/crates/tools/Cargo.toml index e6bb0f47e..9f2043425 100644 --- a/src/native/postgres-tools/crates/tools/Cargo.toml +++ b/src/native/postgres-tools/crates/tools/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-tools" version = "0.2.2" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Target-selecting Cargo facade for Oliphaunt native PostgreSQL client tool artifacts." readme = "README.md" repository.workspace = true diff --git a/src/native/sdks/rust/Cargo.toml b/src/native/sdks/rust/Cargo.toml index 6522aa00d..775f3cfaf 100644 --- a/src/native/sdks/rust/Cargo.toml +++ b/src/native/sdks/rust/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Native-first Rust SDK surface for embedded PostgreSQL through liboliphaunt." readme = "README.md" repository.workspace = true diff --git a/src/native/sdks/rust/crates/oliphaunt-build/Cargo.toml b/src/native/sdks/rust/crates/oliphaunt-build/Cargo.toml index e6a85455d..97b3f8f0e 100644 --- a/src/native/sdks/rust/crates/oliphaunt-build/Cargo.toml +++ b/src/native/sdks/rust/crates/oliphaunt-build/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-build" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo build-script integration for staging Oliphaunt runtime artifacts." readme = "README.md" repository.workspace = true diff --git a/src/wasix/node-addon/Cargo.toml b/src/wasix/node-addon/Cargo.toml index 14e7ac1f0..39e77c03e 100644 --- a/src/wasix/node-addon/Cargo.toml +++ b/src/wasix/node-addon/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-napi" version = "0.2.0" edition = "2024" -rust-version = "1.96" +rust-version.workspace = true description = "Node-API adapter for the Oliphaunt WASIX Rust runtime." repository = "https://github.com/f0rr0/oliphaunt" homepage = "https://oliphaunt.dev" diff --git a/src/wasix/node-addon/README.md b/src/wasix/node-addon/README.md index 84e5e1bbc..5b73ec4b3 100644 --- a/src/wasix/node-addon/README.md +++ b/src/wasix/node-addon/README.md @@ -127,7 +127,7 @@ The supported target set is intentionally closed: macOS arm64, Linux arm64 or x64 with glibc, and Windows x64 with MSVC. macOS x64, Linux musl, and Windows arm64 do not have carriers. The native builder detects its Linux libc and rejects musl or an unidentifiable libc before compiling a GNU carrier. The -Linux release addons are then compiled inside the pinned Rust 1.99.0 Debian +Linux release addons are then compiled inside the pinned Rust 1.96.0 Debian Bookworm image (glibc 2.36), with exact payload paths mounted read-only and the actual build run without network access. This keeps them below the published glibc 2.38 ceiling; release staging also validates their ELF shape and resolves diff --git a/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh b/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh index 76b021f65..7837d1f2d 100755 --- a/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh +++ b/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh @@ -38,12 +38,12 @@ features="$3" [ "$target_triple" = "$rust_host" ] || fail "target $target_triple does not match native host $rust_host" [ "$features" = "release" ] || fail "unsupported WASIX Node-API feature set: $features" -# Official rust:1.99.0-slim-bookworm, pinned as one multi-architecture OCI +# Official rust:1.96.0-slim-bookworm, pinned as one multi-architecture OCI # index. Bookworm's glibc 2.36 is below the published glibc 2.38 ceiling. The # Fedora 39 consumer rehearsal still checks the completed addon under 2.38. -readonly image="rust@sha256:452176c0cefca88c0b3184ce85a4eb03e3d4fa05d2afb5366abcba853221019e" -readonly rust_release="1.99.0" -readonly rust_commit="b940084d7eb6a299eb4bfeb8e34901bc051e7ac4" +readonly image="rust@sha256:4732ca96fd086cb9be682050c3f0176288eebaac2b80aa2bcefccfaf198e1950" +readonly rust_release="1.96.0" +readonly rust_commit="ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96" readonly rust_toolchain="${rust_release}-${rust_host}" readonly expected_builder_glibc="glibc 2.36" readonly manifest="/workspace/src/wasix/node-addon/Cargo.toml" diff --git a/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/Cargo.toml b/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/Cargo.toml index f8237b4d4..77853a2c7 100644 --- a/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/Cargo.toml +++ b/src/wasix/postgres-tools/crates/aot/aarch64-apple-darwin/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-tools-aot-aarch64-apple-darwin" version = "0.2.3" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT pg_dump and psql artifacts for oliphaunt-wasix on aarch64-apple-darwin" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml b/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml index a51b84d45..96755d771 100644 --- a/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml +++ b/src/wasix/postgres-tools/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-tools-aot-aarch64-unknown-linux-gnu" version = "0.2.3" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT pg_dump and psql artifacts for oliphaunt-wasix on aarch64-unknown-linux-gnu" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/Cargo.toml b/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/Cargo.toml index b0ccde6ca..e5c54e0b9 100644 --- a/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/Cargo.toml +++ b/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-tools-aot-x86_64-pc-windows-msvc" version = "0.2.3" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT pg_dump and psql artifacts for oliphaunt-wasix on x86_64-pc-windows-msvc" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml b/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml index 1dae0a4c5..2059b8515 100644 --- a/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml +++ b/src/wasix/postgres-tools/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-tools-aot-x86_64-unknown-linux-gnu" version = "0.2.3" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT pg_dump and psql artifacts for oliphaunt-wasix on x86_64-unknown-linux-gnu" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/postgres-tools/crates/tools/Cargo.toml b/src/wasix/postgres-tools/crates/tools/Cargo.toml index 68f09b666..9b8b8aeda 100644 --- a/src/wasix/postgres-tools/crates/tools/Cargo.toml +++ b/src/wasix/postgres-tools/crates/tools/Cargo.toml @@ -2,7 +2,7 @@ name = "oliphaunt-wasix-tools" version = "0.2.3" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "WASIX pg_dump and psql assets for oliphaunt-wasix" repository = "https://github.com/f0rr0/oliphaunt" homepage = "https://oliphaunt.dev" diff --git a/src/wasix/postmaster/executor/Cargo.toml b/src/wasix/postmaster/executor/Cargo.toml index 2862a6a1c..7fdd7a89e 100644 --- a/src/wasix/postmaster/executor/Cargo.toml +++ b/src/wasix/postmaster/executor/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" homepage = "https://oliphaunt.dev" license = "MIT" repository = "https://github.com/f0rr0/oliphaunt" -rust-version = "1.93" +rust-version = "1.96" version = "7.2.0-alpha.2" [[bin]] diff --git a/src/wasix/runtime/crates/aot/aarch64-apple-darwin/Cargo.toml b/src/wasix/runtime/crates/aot/aarch64-apple-darwin/Cargo.toml index ba2183c39..3f91325d6 100644 --- a/src/wasix/runtime/crates/aot/aarch64-apple-darwin/Cargo.toml +++ b/src/wasix/runtime/crates/aot/aarch64-apple-darwin/Cargo.toml @@ -2,7 +2,7 @@ name = "liboliphaunt-wasix-aot-aarch64-apple-darwin" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT runtime artifacts for oliphaunt-wasix on aarch64-apple-darwin" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml b/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml index ce749f16a..8addc44d5 100644 --- a/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml +++ b/src/wasix/runtime/crates/aot/aarch64-unknown-linux-gnu/Cargo.toml @@ -2,7 +2,7 @@ name = "liboliphaunt-wasix-aot-aarch64-unknown-linux-gnu" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT runtime artifacts for oliphaunt-wasix on aarch64-unknown-linux-gnu" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/Cargo.toml b/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/Cargo.toml index c2b82915f..1c090e23f 100644 --- a/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/Cargo.toml +++ b/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/Cargo.toml @@ -2,7 +2,7 @@ name = "liboliphaunt-wasix-aot-x86_64-pc-windows-msvc" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT runtime artifacts for oliphaunt-wasix on x86_64-pc-windows-msvc" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml b/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml index ff3f79048..be81ecf98 100644 --- a/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml +++ b/src/wasix/runtime/crates/aot/x86_64-unknown-linux-gnu/Cargo.toml @@ -2,7 +2,7 @@ name = "liboliphaunt-wasix-aot-x86_64-unknown-linux-gnu" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Wasmer AOT runtime artifacts for oliphaunt-wasix on x86_64-unknown-linux-gnu" repository = "https://github.com/f0rr0/oliphaunt" license = "MIT AND PostgreSQL AND Unicode-3.0" diff --git a/src/wasix/runtime/crates/assets/Cargo.toml b/src/wasix/runtime/crates/assets/Cargo.toml index 871c3fc06..4a9bb82cf 100644 --- a/src/wasix/runtime/crates/assets/Cargo.toml +++ b/src/wasix/runtime/crates/assets/Cargo.toml @@ -2,7 +2,7 @@ name = "liboliphaunt-wasix-portable" version = "0.3.1" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Portable WASIX runtime assets for oliphaunt-wasix" repository = "https://github.com/f0rr0/oliphaunt" homepage = "https://oliphaunt.dev" diff --git a/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts b/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts index ea7a9fcc0..1df7e1a00 100755 --- a/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts +++ b/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts @@ -722,7 +722,7 @@ function writeExtensionPayloadPartSources({ `name = ${JSON.stringify(part.name)}`, `version = ${JSON.stringify(version)}`, 'edition = "2024"', - 'rust-version = "1.93"', + 'rust-version = "1.96"', `description = ${JSON.stringify(`Cargo payload part for the ${subject} on ${target}`)}`, 'repository = "https://github.com/f0rr0/oliphaunt"', 'homepage = "https://oliphaunt.dev"', @@ -1262,7 +1262,7 @@ function writeExtensionCargoSource(spec, sourceRoot, partBytes) { `name = "${spec.name}"`, `version = "${spec.version}"`, 'edition = "2024"', - 'rust-version = "1.93"', + 'rust-version = "1.96"', `description = "Oliphaunt WASIX artifact package for the ${subject}"`, 'repository = "https://github.com/f0rr0/oliphaunt"', 'homepage = "https://oliphaunt.dev"', @@ -1421,7 +1421,7 @@ function writeExtensionAotCargoSource(spec, sourceRoot, partBytes) { `name = "${spec.name}"`, `version = "${spec.version}"`, 'edition = "2024"', - 'rust-version = "1.93"', + 'rust-version = "1.96"', `description = "Oliphaunt WASIX AOT artifact package for the ${subject} on ${spec.target}"`, 'repository = "https://github.com/f0rr0/oliphaunt"', 'homepage = "https://oliphaunt.dev"', diff --git a/src/wasix/runtime/tools/xtask/Cargo.toml b/src/wasix/runtime/tools/xtask/Cargo.toml index ba2f1c978..dbdde65d1 100644 --- a/src/wasix/runtime/tools/xtask/Cargo.toml +++ b/src/wasix/runtime/tools/xtask/Cargo.toml @@ -2,7 +2,7 @@ name = "xtask" version = "0.0.0" edition = "2024" -rust-version = "1.96" +rust-version.workspace = true license.workspace = true publish = false diff --git a/tools/packaging/native-cargo-payload.mts b/tools/packaging/native-cargo-payload.mts index b80a1d9d4..8f9dfed4c 100644 --- a/tools/packaging/native-cargo-payload.mts +++ b/tools/packaging/native-cargo-payload.mts @@ -325,7 +325,7 @@ function writePartCrate( name = "${name}" version = "${version}" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo payload part ${String(index).padStart(3, '0')} for the ${targetId} ${artifactLabel}." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" @@ -415,7 +415,7 @@ function writeAggregatorCrate( name = "${name}" version = "${version}" edition = "2024" -rust-version = "1.93" +rust-version = "1.96" description = "Cargo artifact crate for the ${target.target} ${artifactLabel}." readme = "README.md" repository = "https://github.com/f0rr0/oliphaunt" diff --git a/tools/release/public-consumer-smoke.test.mts b/tools/release/public-consumer-smoke.test.mts index f4913a113..d252308a7 100644 --- a/tools/release/public-consumer-smoke.test.mts +++ b/tools/release/public-consumer-smoke.test.mts @@ -645,7 +645,7 @@ test('Cargo consumer toolchain context fails closed on unpinned or unavailable i ), /must pin an exact stable Rust toolchain/u, ); - writeFileSync(path.join(root, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.93.1"\n'); + writeFileSync(path.join(root, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.96.0"\n'); assert.throws( () => publicCargoEnvironment( From f7389afeccc9642a5d513112f60ab6847fcad53d Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Sun, 4 Oct 2026 17:26:03 +0000 Subject: [PATCH 3/4] feat(wasix): upgrade browser host to Wasmer SDK 0.19 --- .../maintainers/runtime-resource-budgets.md | 4 +- src/docs/maintainers/wasix-usage.md | 24 + src/wasix/browser-host/Cargo.lock | 4338 +++++++++++++++++ .../browser-host/adapter/caller_realm.rs | 50 + src/wasix/browser-host/adapter/clock.rs | 289 ++ .../browser-host/adapter/fs/directory.rs | 612 +++ src/wasix/browser-host/adapter/fs/mod.rs | 5 + .../browser-host/adapter/fs/sync_bridge.rs | 787 +++ src/wasix/browser-host/adapter/instance.rs | 268 + src/wasix/browser-host/adapter/mod.rs | 22 + src/wasix/browser-host/adapter/options.rs | 273 ++ .../browser-host/adapter/postgres_direct.rs | 1570 ++++++ src/wasix/browser-host/adapter/run.rs | 65 + src/wasix/browser-host/adapter/runtime.rs | 11 + src/wasix/browser-host/adapter/streams.rs | 344 ++ src/wasix/browser-host/adapter/tool_direct.rs | 592 +++ src/wasix/browser-host/adapter/utils.rs | 167 + src/wasix/browser-host/build-provenance.mts | 23 +- src/wasix/browser-host/build-sdk.sh | 41 +- src/wasix/browser-host/bundle-sdk.mts | 34 + src/wasix/browser-host/clock.test.mts | 202 +- src/wasix/browser-host/index.mts | 25 + src/wasix/browser-host/moon.yml | 2 +- ...smer-js-run-configured-wasix-process.patch | 120 - ...wasix-add-0702-compatibility-imports.patch | 111 - ...r-js-install-browser-runtime-devices.patch | 42 - .../0005-wasmer-js-use-object-wasm-init.patch | 25 - ...er-js-reuse-precompiled-wasix-module.patch | 427 -- .../0007-wasmer-js-run-oliphaunt-direct.patch | 760 --- ...-wasix-instantiate-main-module-async.patch | 274 -- .../0010-wasmer-js-refresh-npm-lock.patch | 86 - ...asix-deny-single-backend-guest-spawn.patch | 338 -- ...js-remove-retired-wasm32-wasi-target.patch | 18 - ...smer-wasix-fast-single-backend-clock.patch | 445 -- ...-wasmer-js-track-directory-mutations.patch | 408 -- ...wasmer-js-add-sync-filesystem-bridge.patch | 780 --- ...16-wasmer-wasix-expose-direct-memory.patch | 36 - ...asmer-js-direct-pgwire-memory-bridge.patch | 344 -- .../0018-wasmer-js-bound-direct-stderr.patch | 257 - ...smer-parse-exception-reference-types.patch | 22 - ...asix-preserve-posix-close-durability.patch | 46 - .../0021-wasmer-js-stream-direct-pgwire.patch | 1311 ----- ...-js-prepare-trusted-embedded-session.patch | 57 - ...4-virtual-fs-propagate-random-errors.patch | 32 - ...r-js-fail-closed-direct-guest-phases.patch | 1017 ---- ...-isolate-thread-local-handle-borrows.patch | 176 - ...4-wasmer-js-preserve-seek-end-errors.patch | 116 - ...er-wasix-preserve-shared-seek-offset.patch | 80 - ...0037-virtual-mio-retain-inline-wakes.patch | 88 - ...r-js-build-oliphaunt-browser-adapter.patch | 1253 +++++ ...r-instantiate-browser-modules-async.patch} | 117 +- ...042-wasmer-wasix-browser-host-policy.patch | 629 +++ ...ual-fs-preserve-io-errors-and-wakes.patch} | 36 +- ...-preserve-handle-and-seek-lifetimes.patch} | 172 +- ...-wasix-correct-browser-clock-domains.patch | 88 + src/wasix/browser-host/prepare-sdk.mts | 40 + src/wasix/browser-host/rust-toolchain.toml | 6 + src/wasix/browser-host/source.toml | 79 +- src/wasix/sdks/ts/src/host/index.d.mts | 3 + src/wasix/sdks/ts/tools/stage-host.mts | 8 +- 60 files changed, 11914 insertions(+), 7681 deletions(-) create mode 100644 src/wasix/browser-host/Cargo.lock create mode 100644 src/wasix/browser-host/adapter/caller_realm.rs create mode 100644 src/wasix/browser-host/adapter/clock.rs create mode 100644 src/wasix/browser-host/adapter/fs/directory.rs create mode 100644 src/wasix/browser-host/adapter/fs/mod.rs create mode 100644 src/wasix/browser-host/adapter/fs/sync_bridge.rs create mode 100644 src/wasix/browser-host/adapter/instance.rs create mode 100644 src/wasix/browser-host/adapter/mod.rs create mode 100644 src/wasix/browser-host/adapter/options.rs create mode 100644 src/wasix/browser-host/adapter/postgres_direct.rs create mode 100644 src/wasix/browser-host/adapter/run.rs create mode 100644 src/wasix/browser-host/adapter/runtime.rs create mode 100644 src/wasix/browser-host/adapter/streams.rs create mode 100644 src/wasix/browser-host/adapter/tool_direct.rs create mode 100644 src/wasix/browser-host/adapter/utils.rs create mode 100644 src/wasix/browser-host/bundle-sdk.mts create mode 100644 src/wasix/browser-host/index.mts delete mode 100644 src/wasix/browser-host/patches/0001-wasmer-js-run-configured-wasix-process.patch delete mode 100644 src/wasix/browser-host/patches/0002-wasmer-wasix-add-0702-compatibility-imports.patch delete mode 100644 src/wasix/browser-host/patches/0003-wasmer-js-install-browser-runtime-devices.patch delete mode 100644 src/wasix/browser-host/patches/0005-wasmer-js-use-object-wasm-init.patch delete mode 100644 src/wasix/browser-host/patches/0006-wasmer-js-reuse-precompiled-wasix-module.patch delete mode 100644 src/wasix/browser-host/patches/0007-wasmer-js-run-oliphaunt-direct.patch delete mode 100644 src/wasix/browser-host/patches/0009-wasmer-wasix-instantiate-main-module-async.patch delete mode 100644 src/wasix/browser-host/patches/0010-wasmer-js-refresh-npm-lock.patch delete mode 100644 src/wasix/browser-host/patches/0011-wasmer-wasix-deny-single-backend-guest-spawn.patch delete mode 100644 src/wasix/browser-host/patches/0012-wasmer-js-remove-retired-wasm32-wasi-target.patch delete mode 100644 src/wasix/browser-host/patches/0013-wasmer-wasix-fast-single-backend-clock.patch delete mode 100644 src/wasix/browser-host/patches/0014-wasmer-js-track-directory-mutations.patch delete mode 100644 src/wasix/browser-host/patches/0015-wasmer-js-add-sync-filesystem-bridge.patch delete mode 100644 src/wasix/browser-host/patches/0016-wasmer-wasix-expose-direct-memory.patch delete mode 100644 src/wasix/browser-host/patches/0017-wasmer-js-direct-pgwire-memory-bridge.patch delete mode 100644 src/wasix/browser-host/patches/0018-wasmer-js-bound-direct-stderr.patch delete mode 100644 src/wasix/browser-host/patches/0019-wasmer-parse-exception-reference-types.patch delete mode 100644 src/wasix/browser-host/patches/0020-wasmer-wasix-preserve-posix-close-durability.patch delete mode 100644 src/wasix/browser-host/patches/0021-wasmer-js-stream-direct-pgwire.patch delete mode 100644 src/wasix/browser-host/patches/0023-wasmer-js-prepare-trusted-embedded-session.patch delete mode 100644 src/wasix/browser-host/patches/0024-virtual-fs-propagate-random-errors.patch delete mode 100644 src/wasix/browser-host/patches/0025-wasmer-js-fail-closed-direct-guest-phases.patch delete mode 100644 src/wasix/browser-host/patches/0027-wasmer-wasix-isolate-thread-local-handle-borrows.patch delete mode 100644 src/wasix/browser-host/patches/0034-wasmer-js-preserve-seek-end-errors.patch delete mode 100644 src/wasix/browser-host/patches/0035-wasmer-wasix-preserve-shared-seek-offset.patch delete mode 100644 src/wasix/browser-host/patches/0037-virtual-mio-retain-inline-wakes.patch create mode 100644 src/wasix/browser-host/patches/0040-wasmer-js-build-oliphaunt-browser-adapter.patch rename src/wasix/browser-host/patches/{0008-wasmer-instantiate-js-modules-async.patch => 0041-wasmer-instantiate-browser-modules-async.patch} (53%) create mode 100644 src/wasix/browser-host/patches/0042-wasmer-wasix-browser-host-policy.patch rename src/wasix/browser-host/patches/{0036-virtual-fs-poll-contended-pipe-reads.patch => 0043-wasmer-virtual-fs-preserve-io-errors-and-wakes.patch} (74%) rename src/wasix/browser-host/patches/{0033-wasmer-wasix-poll-ready-asyncify-work.patch => 0044-wasmer-wasix-preserve-handle-and-seek-lifetimes.patch} (58%) create mode 100644 src/wasix/browser-host/patches/0045-wasmer-wasix-correct-browser-clock-domains.patch create mode 100644 src/wasix/browser-host/prepare-sdk.mts create mode 100644 src/wasix/browser-host/rust-toolchain.toml diff --git a/src/docs/maintainers/runtime-resource-budgets.md b/src/docs/maintainers/runtime-resource-budgets.md index 505731cb3..00bd67f9e 100644 --- a/src/docs/maintainers/runtime-resource-budgets.md +++ b/src/docs/maintainers/runtime-resource-budgets.md @@ -260,8 +260,8 @@ separate. Rust `postgres_mod.rs::DIAGNOSTIC_TAIL_BYTES` retains the latest 8 KiB from each split-initdb stdout and stderr stream. The backend separately retains 16 KiB -of stderr in `instantiate_wasix_module`; browser patch -`0018-wasmer-js-bound-direct-stderr.patch::STDERR_LIMIT_BYTES` also uses 16 KiB. +of stderr in `instantiate_wasix_module`; browser adapter +`src/wasix/browser-host/adapter/postgres_direct.rs::STDERR_LIMIT_BYTES` also uses 16 KiB. These matching backend values are not generated from the initdb constant. `STARTUP_OUTCOME_MAX_PROTOCOL_BYTES` allows up to 1 MiB of startup-error protocol payload and must match the guest diff --git a/src/docs/maintainers/wasix-usage.md b/src/docs/maintainers/wasix-usage.md index 4b33512c2..27eee2a33 100644 --- a/src/docs/maintainers/wasix-usage.md +++ b/src/docs/maintainers/wasix-usage.md @@ -87,6 +87,30 @@ caller-realm native placement, or `@oliphaunt/wasix-ts/worker` for a separate JavaScript Worker realm on every runtime. Browser execution requires cross-origin isolation. +The browser host builds the engine and worker scheduler from the pinned +Wasmer SDK source in `src/wasix/browser-host/source.toml`. Its repo-owned +`adapter/` supplies our direct PostgreSQL and frontend-tool interfaces, not +Wasmer's PostgreSQL example or registry packages. Shared-memory host workers +require the browser-only nightly pinned in that project's `rust-toolchain.toml`; +normal Rust builds retain the workspace toolchain. Browser host updates do not +change Rust/Node-API or Postmaster engines. + +Browser PostgreSQL installs its fast clock through Wasmer's import hook; tools +use ordinary WASIX clocks. Fast reads return through WASIX after 16 observed +milliseconds or 1,024 reads per clock domain to service pending work. A guest +clock-setting call switches all readers sharing that database's memory to +ordinary WASIX reads before entering the setter, including readers in extensions +loaded later. Importing a setter alone does not disable fast reads. The switch +lasts until that guest is destroyed, even when the setter returns an error; +Wasmer remains the sole owner of virtual clock adjustments. No public flag is +needed. This optimization is restricted to the enforced single execution realm. + +The pinned SDK has a +[Modified MIT license](https://github.com/wasmerio/wasmer-sdk/blob/wasmer-sdk-js-v0.19.0/LICENSE), +including a prominent Wasmer UI attribution requirement for commercial +products above one million monthly active users or US$1 million monthly revenue; +it must not be described as plain MIT. + Memory is the default. Persistent providers are selective imports: - `storage/indexed-db` and `storage/opfs` in browsers; diff --git a/src/wasix/browser-host/Cargo.lock b/src/wasix/browser-host/Cargo.lock new file mode 100644 index 000000000..b8f36da97 --- /dev/null +++ b/src/wasix/browser-host/Cargo.lock @@ -0,0 +1,4338 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli 0.32.3", +] + +[[package]] +name = "addr2line" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e567177890eb1617b1f774005b66b26b2377afd138a2ca37aae7d8f0c81429d4" +dependencies = [ + "cpp_demangle 0.5.1", + "fallible-iterator", + "gimli 0.34.0", + "memmap2 0.9.11", + "object 0.40.0", + "rustc-demangle", + "smallvec", + "typed-arena", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line 0.25.1", + "cfg-if", + "libc", + "miniz_oxide 0.8.9", + "object 0.37.3", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", + "rayon-core", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bumpalo-herd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a57952053bc5530247762011239f17c49e82376130cdcd03da0dc49ab8bd6c3" +dependencies = [ + "bumpalo", +] + +[[package]] +name = "bus" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7118d0221d84fada881b657c2ddb7cd55108db79c8764c9ee212c0c259b783" +dependencies = [ + "crossbeam-channel", + "num_cpus", + "parking_lot_core", +] + +[[package]] +name = "bytecheck" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26333eeac754f0ad8a6bcd0eb0ac012156302e4e16b852b72ee399aea4f12c29" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "bytesize" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "colosseum" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370c83b49aedf022ee27942e8ae1d9de1cf40dc9653ee6550e4455d08f6406f9" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cooked-waker" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "corosensei" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6886a0c0f263965933c438626e7179139a62b978a33aa18281cbf0cd5a975f34" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "scopeguard", + "windows-sys 0.59.0", +] + +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpp_demangle" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-skiplist" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.1", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumset" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc5801fd11762e24d1e420d01d2ac518f2a2ca4329d4fbb6639f2412b6204e0" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gimli" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1033caf0b349c518623b5396bfb2cf0bddf44f0306d543a250e5743297aafd10" +dependencies = [ + "fnv", + "hashbrown 0.17.1", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "iprange" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37209be0ad225457e63814401415e748e2453a5297f9b637338f5fb8afa4ec00" +dependencies = [ + "ipnet", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libtest-mimic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", +] + +[[package]] +name = "libunwind" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6639b70a7ce854b79c70d7e83f16b5dc0137cc914f3d7d03803b513ecc67ac" + +[[package]] +name = "libwild" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f7edaa8ff7a14a1a9915d5ff85c65b24426d81edc0362635a687c70ce7c1ccc" +dependencies = [ + "anyhow", + "atomic-take", + "bitflags 2.13.1", + "blake3", + "bumpalo-herd", + "bytesize", + "cc", + "colored", + "colosseum", + "crossbeam-queue", + "crossbeam-utils", + "derive_more", + "flate2", + "foldhash 0.2.0", + "gimli 0.34.0", + "glob", + "hashbrown 0.17.1", + "hex", + "indexmap", + "itertools 0.15.0", + "jobserver", + "leb128", + "libc", + "linker-layout", + "linker-trace", + "linker-utils", + "memchr", + "memmap2 0.9.11", + "nix", + "object 0.40.0", + "perf-event", + "perfetto-recorder", + "rayon", + "serde", + "serde_yaml", + "sha2", + "sharded-offset-map", + "sharded-vec-writer", + "smallvec", + "strum", + "symbolic-common", + "symbolic-demangle", + "thread_local", + "tracing", + "tracing-subscriber", + "uuid", + "wasm-encoder 0.255.0", + "wasmparser 0.255.0", + "winnow", + "zerocopy", + "zlib-rs", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984fb35d06508d1e69fc91050cceba9c0b748f983e6739fa2c7a9237154c52c8" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "linker-layout" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b117d6790188a855ea548db622f1b9d3ae8fc7df7bc66f296defd78f7c2e10" +dependencies = [ + "anyhow", + "postcard", + "serde", +] + +[[package]] +name = "linker-trace" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728b8a0f9a835c0debd6ae954531169bc76cb430fae909a701303b907e5b518f" +dependencies = [ + "anyhow", + "postcard", + "serde", +] + +[[package]] +name = "linker-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198c6aa21803baa1398e7fb169adadba421164c27ee7946b3a7dcc57d40b1f7e" +dependencies = [ + "anyhow", + "leb128", + "object 0.40.0", + "paste", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lz4_flex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbdfe44b1bd960b68170b417450a628c43f7cf56bb3c5317e61cb230ee7f226" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + +[[package]] +name = "macho-unwind-info" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4bdc8b0ce69932332cf76d24af69c3a155242af95c226b2ab6c2e371ed1149" +dependencies = [ + "thiserror", + "zerocopy", + "zerocopy-derive", +] + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "minicov" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3aa3aa12b448ac225b3102217d1ac5cc717908f02722926524b0599c933c7a0" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "more-asserts" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" + +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd229a0361b9d0d4396176e02d65897f487eebeab7caa6d443855ee152ca0b9c" +dependencies = [ + "crc32fast", + "flate2", + "hashbrown 0.17.1", + "indexmap", + "memchr", + "ruzstd", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "perf-event" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d6393d9238342159080d79b78cb59c67399a8e7ecfa5d410bd614169e4e823" +dependencies = [ + "libc", + "perf-event-open-sys", +] + +[[package]] +name = "perf-event-open-sys" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c44fb1c7651a45a3652c4afc6e754e40b3d6e6556f1487e2b230bfc4f33c2a8" +dependencies = [ + "libc", +] + +[[package]] +name = "perfetto-recorder" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee50f00666bf0c681a805a241afcf131ca3b9da28e79c49d3563be86099e002f" +dependencies = [ + "libc", + "nix", + "prost", + "rand", + "windows-sys 0.61.2", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "phf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ptr_meta" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743da816b98c921cdbe8628ef7381b76f25ecf4da599fc80aca90eae7ef70cc0" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags 1.3.2", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +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 = "rancor" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b534442d0fcdb55d66f373d9cac6d33b6293a2335bc2136dbd06ce0e87d2572" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rangemap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "region" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "430988f137a23121407b362bf10b09df302ed5092ffeb9af90a549edcc849115" +dependencies = [ + "bitflags 2.13.1", + "libc", + "mach2 0.4.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "replace_with" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" + +[[package]] +name = "rkyv" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9776093b7ca170454ab1406954f7b7d97a57c51dc6c0642957fb2ef25c2d399" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.17.1", + "indexmap", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ruzstd" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a252f5e20f038fe7b4ea53e073e65398d652c864cc162fc77c56c2f13717b888" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "saffron" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fb9a628596fc7590eb7edbf7b0613287be78df107f5f97b118aad59fb2eea9" +dependencies = [ + "chrono", + "nom 5.1.3", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "indexmap", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.4", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-offset-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b71ad2c1fdc235dc70668aff10a50040624596781d443a3389b1203d18e6b7" +dependencies = [ + "sharded-vec-writer", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "sharded-vec-writer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b990b2e423f971a9afaea1fdf46ab3e42197d2577e8ef89f2266c1c26a0731a6" + +[[package]] +name = "shared-buffer" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" +dependencies = [ + "bytes", + "memmap2 0.6.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smoltcp" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f73d40463bba65efc9adc6370b56df76d563cc46e2482bba58351b4afb7535e" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt 0.3.100", + "heapless", + "managed", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "symbolic-common" +version = "13.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfa0014ff4e423a1bb8881453c7a29181eb14fe1dbc8e0197ebfa081903f3ed" +dependencies = [ + "debugid", + "memmap2 0.9.11", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "13.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a213276280a1a737ff5fb89ae2a70ba51d1d4608e74736beb92f257f125f1aa" +dependencies = [ + "cpp_demangle 0.4.5", + "rustc-demangle", + "symbolic-common", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "twox-hash" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5283634e518fe9e82c7b20520bb4bc209009fd16c82077c802f8111ecbb0117a" + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtual-fs" +version = "0.704.0" +dependencies = [ + "async-trait", + "bytes", + "dashmap", + "derive_more", + "dunce", + "futures", + "getrandom 0.4.3", + "pin-project-lite", + "replace_with", + "shared-buffer", + "slab", + "thiserror", + "tokio", + "tracing", + "virtual-mio", + "wasmer-package", + "web-time", + "webc", +] + +[[package]] +name = "virtual-mio" +version = "0.704.0" +dependencies = [ + "async-trait", + "bytes", + "futures", + "parking", + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "virtual-net" +version = "0.704.0" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.23.1", + "bincode", + "bytecheck", + "bytes", + "derive_more", + "futures-util", + "ipnet", + "iprange", + "pin-project-lite", + "rkyv", + "serde", + "smoltcp", + "thiserror", + "tokio", + "tracing", + "virtual-mio", +] + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "wai-bindgen-gen-core" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa3dc41b510811122b3088197234c27e08fcad63ef936306dd8e11e2803876c" +dependencies = [ + "anyhow", + "wai-parser", +] + +[[package]] +name = "wai-bindgen-gen-rust" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc05e8380515c4337c40ef03b2ff233e391315b178a320de8640703d522efe" +dependencies = [ + "heck 0.3.3", + "wai-bindgen-gen-core", +] + +[[package]] +name = "wai-bindgen-gen-rust-wasm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f35ce5e74086fac87f3a7bd50f643f00fe3559adb75c88521ecaa01c8a6199" +dependencies = [ + "heck 0.3.3", + "wai-bindgen-gen-core", + "wai-bindgen-gen-rust", +] + +[[package]] +name = "wai-bindgen-rust" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5601c6f448c063e83a5e931b8fefcdf7e01ada424ad42372c948d2e3d67741" +dependencies = [ + "bitflags 1.3.2", + "wai-bindgen-rust-impl", +] + +[[package]] +name = "wai-bindgen-rust-impl" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeb5c1170246de8425a3e123e7ef260dc05ba2b522a1d369fe2315376efea4" +dependencies = [ + "proc-macro2", + "syn 1.0.109", + "wai-bindgen-gen-core", + "wai-bindgen-gen-rust-wasm", +] + +[[package]] +name = "wai-parser" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd0acb6d70885ea0c343749019ba74f015f64a9d30542e66db69b49b7e28186" +dependencies = [ + "anyhow", + "id-arena", + "pulldown-cmark", + "unicode-normalization", + "unicode-xid", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0d555ca874445df8d314f94f5c948a4e74e5418f332c89f660a3d8310a96f4" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94eb68555b95bcea5e8cf4abe280b529049479fa995bfc23734af96a6aedc120" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31d56021e873866c968588ed85ccdf56db5c426e44afdb4618c39895104b920" + +[[package]] +name = "wasm-encoder" +version = "0.255.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b524283fb5df62eec102ed0574838961bdd7ba5ac9c50d38e2756c51c971a42" +dependencies = [ + "leb128fmt", +] + +[[package]] +name = "wasm-encoder" +version = "0.257.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8ad9f0a39050867bda22e6486c316e2e52d20a42154d5bc433934bf738d085" +dependencies = [ + "leb128fmt", + "wasmparser 0.257.1", +] + +[[package]] +name = "wasmer" +version = "7.4.0" +dependencies = [ + "bindgen", + "bytes", + "cmake", + "corosensei", + "crossbeam-skiplist", + "dashmap", + "derive_more", + "futures", + "indexmap", + "itertools 0.15.0", + "js-sys", + "more-asserts", + "paste", + "rkyv", + "serde", + "serde-wasm-bindgen", + "shared-buffer", + "tar", + "target-lexicon", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasmer-compiler", + "wasmer-derive", + "wasmer-types", + "wasmparser 0.257.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmer-compiler" +version = "7.4.0" +dependencies = [ + "addr2line 0.27.1", + "backtrace", + "bytes", + "crossbeam-channel", + "enum-iterator", + "enumset", + "gimli 0.34.0", + "itertools 0.15.0", + "leb128", + "libc", + "libwild", + "macho-unwind-info", + "memmap2 0.9.11", + "more-asserts", + "object 0.40.0", + "phf", + "rangemap", + "rayon", + "region", + "rkyv", + "self_cell", + "shared-buffer", + "smallvec", + "target-lexicon", + "tempfile", + "thiserror", + "wasmer-types", + "wasmer-vm", + "which", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmer-config" +version = "0.704.0" +dependencies = [ + "anyhow", + "bytesize", + "ciborium", + "derive_builder", + "hex", + "indexmap", + "saffron", + "schemars", + "semver", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "toml", + "url", +] + +[[package]] +name = "wasmer-derive" +version = "7.4.0" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "wasmer-journal" +version = "0.704.0" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.23.1", + "bincode", + "bytecheck", + "bytes", + "derive_more", + "lz4_flex", + "num_enum", + "rkyv", + "serde", + "serde_json", + "thiserror", + "tracing", + "virtual-fs", + "virtual-net", + "wasmer", + "wasmer-config", + "wasmer-wasix-types", +] + +[[package]] +name = "wasmer-package" +version = "0.704.0" +dependencies = [ + "bytes", + "libc", + "thiserror", + "wasmer-types", + "webc", +] + +[[package]] +name = "wasmer-sdk-js" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "console_error_panic_hook", + "crossbeam-skiplist", + "derivative", + "futures", + "http", + "httparse", + "instant", + "js-sys", + "once_cell", + "serde", + "serde-wasm-bindgen", + "serde_bytes", + "tokio", + "tracing", + "virtual-fs", + "virtual-mio", + "virtual-net", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "wasmer", + "wasmer-package", + "wasmer-types", + "wasmer-wasix", + "wasmer-wasix-types", + "web-sys", + "webc", +] + +[[package]] +name = "wasmer-types" +version = "7.4.0" +dependencies = [ + "bytecheck", + "crc32fast", + "enum-iterator", + "enumset", + "getrandom 0.4.3", + "hex", + "indexmap", + "itertools 0.15.0", + "more-asserts", + "rkyv", + "serde", + "sha2", + "target-lexicon", + "thiserror", + "wasmparser 0.257.1", +] + +[[package]] +name = "wasmer-vm" +version = "7.4.0" +dependencies = [ + "backtrace", + "bytesize", + "cc", + "corosensei", + "crossbeam-queue", + "dashmap", + "enum-iterator", + "fnv", + "gimli 0.34.0", + "indexmap", + "itertools 0.15.0", + "libc", + "libunwind", + "mach2 0.6.0", + "more-asserts", + "parking_lot", + "region", + "rustversion", + "scopeguard", + "thiserror", + "wasmer-types", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmer-wasix" +version = "0.704.0" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.23.1", + "bincode", + "blake3", + "bus", + "bytecheck", + "bytes", + "chrono", + "cooked-waker", + "crossbeam-channel", + "dashmap", + "derive_more", + "flate2", + "fnv", + "fs_extra", + "futures", + "getrandom 0.3.4", + "getrandom 0.4.3", + "heapless", + "hex", + "http", + "itertools 0.15.0", + "js-sys", + "libc", + "libtest-mimic", + "linked_hash_set", + "lz4_flex", + "num_enum", + "once_cell", + "petgraph", + "pin-project", + "pin-utils", + "rand", + "rkyv", + "semver", + "serde", + "serde_derive", + "serde_json", + "serde_yaml", + "sha2", + "shared-buffer", + "tempfile", + "terminal_size", + "termios", + "thiserror", + "tokio", + "tokio-stream", + "toml", + "tracing", + "url", + "urlencoding", + "virtual-fs", + "virtual-mio", + "virtual-net", + "waker-fn", + "walkdir", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-encoder 0.257.1", + "wasmer", + "wasmer-config", + "wasmer-journal", + "wasmer-package", + "wasmer-types", + "wasmer-wasix-types", + "wasmparser 0.257.1", + "web-sys", + "web-time", + "webc", + "windows-sys 0.61.2", + "xxhash-rust", + "zstd", +] + +[[package]] +name = "wasmer-wasix-types" +version = "0.704.0" +dependencies = [ + "anyhow", + "bitflags 2.13.1", + "byteorder", + "num_enum", + "serde", + "time", + "tracing", + "wai-bindgen-gen-core", + "wai-bindgen-gen-rust", + "wai-bindgen-gen-rust-wasm", + "wai-bindgen-rust", + "wai-parser", + "wasmer", + "wasmer-derive", + "wasmer-types", +] + +[[package]] +name = "wasmparser" +version = "0.255.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b" +dependencies = [ + "bitflags 2.13.1", + "hashbrown 0.17.1", + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.257.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92fc335fb6d48f46bda1d8b26b69e28320c15ac3272208333833d6e217e2b4a" +dependencies = [ + "bitflags 2.13.1", + "indexmap", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webc" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715cbfae9eb87236aedca786d0f094e4b513ae5c1bc328a81760d1f9b19f187e" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bytes", + "cfg-if", + "ciborium", + "document-features", + "ignore", + "indexmap", + "libc", + "once_cell", + "serde", + "serde_json", + "sha2", + "shared-buffer", + "thiserror", + "url", +] + +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[patch.unused]] +name = "wasmer-c-api-imports" +version = "7.4.0" diff --git a/src/wasix/browser-host/adapter/caller_realm.rs b/src/wasix/browser-host/adapter/caller_realm.rs new file mode 100644 index 000000000..c534c443a --- /dev/null +++ b/src/wasix/browser-host/adapter/caller_realm.rs @@ -0,0 +1,50 @@ +use std::{future::Future, pin::Pin, time::Duration}; + +use futures::future::BoxFuture; +use wasmer_wasix::{VirtualTaskManager, WasiThreadError, runtime::task_manager::TaskWasm}; + +/// A deliberately single-realm task manager for synchronous guest calls. +/// +/// PostgreSQL's direct protocol pump never yields control back to JavaScript +/// while a guest export is running. Timers therefore have to complete during +/// their first poll, while operations that require another WASM execution +/// context fail explicitly instead of being queued behind the blocked realm. +#[derive(Debug, Default)] +pub(crate) struct CallerRealmTaskManager; + +#[async_trait::async_trait] +impl VirtualTaskManager for CallerRealmTaskManager { + fn sleep_now( + &self, + duration: Duration, + ) -> Pin + Send + Sync + 'static>> { + let deadline = instant::Instant::now() + duration; + Box::pin(async move { + while instant::Instant::now() < deadline { + std::hint::spin_loop(); + } + }) + } + + fn task_shared( + &self, + _task: Box BoxFuture<'static, ()> + Send + 'static>, + ) -> Result<(), WasiThreadError> { + Err(WasiThreadError::Unsupported) + } + + fn task_wasm(&self, _task: TaskWasm) -> Result<(), WasiThreadError> { + Err(WasiThreadError::Unsupported) + } + + fn task_dedicated( + &self, + _task: Box, + ) -> Result<(), WasiThreadError> { + Err(WasiThreadError::Unsupported) + } + + fn thread_parallelism(&self) -> Result { + Ok(1) + } +} diff --git a/src/wasix/browser-host/adapter/clock.rs b/src/wasix/browser-host/adapter/clock.rs new file mode 100644 index 000000000..f478af84f --- /dev/null +++ b/src/wasix/browser-host/adapter/clock.rs @@ -0,0 +1,289 @@ +//! Fast browser clocks for the sealed, single-realm embedded PostgreSQL host. +//! WASIX remains the authority for clock adjustments and pending work. +use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; +use wasmer::{Extern, Function, Imports, Module, StoreMut, js::AsJs}; +use wasmer_wasix::runtime::{InstantiationHook, InstantiationState}; + +#[wasm_bindgen(inline_js = r#" +const clockStates = new WeakMap(); +function clockState(memory) { + let state = clockStates.get(memory); + if (state === undefined) { + state = { canonical: false }; + clockStates.set(memory, state); + } + return state; +} +export function oliphauntDirectClockImport(memory, fallback) { + const state = clockState(memory); + const MAX_CANONICAL_INTERVAL_MILLIS = 16; + // Coarsened or stalled clock sources must not suppress + // canonical checkpoints forever. + const MAX_DIRECT_READS_BETWEEN_FALLBACKS = 1024; + const MAX_CANONICAL_NANOSECONDS = 9223372036854775807n; + let lastRealtimeFallbackMillis; + let lastMonotonicFallbackMillis; + let realtimeDirectReadsSinceFallback = 0; + let monotonicDirectReadsSinceFallback = 0; + let monotonicAnchor; + let buffer = memory.buffer; + let view = new DataView(buffer); + + function sampleRealtimeMillis() { + const millis = Date.now(); + return Number.isFinite(millis) && millis >= 0 ? millis : undefined; + } + + function samplePerformanceMillis() { + const performanceObject = globalThis.performance; + if (performanceObject === undefined || typeof performanceObject.now !== "function") { + return undefined; + } + const millis = performanceObject.now(); + return Number.isFinite(millis) && millis >= 0 ? millis : undefined; + } + + function refreshView(timePointer) { + let pointer; + if (typeof timePointer === "bigint") { + if (timePointer < 0n || timePointer > BigInt(Number.MAX_SAFE_INTEGER)) { + return undefined; + } + pointer = Number(timePointer); + } else if (typeof timePointer === "number" && Number.isInteger(timePointer)) { + pointer = timePointer >>> 0; + } else { + return undefined; + } + + if (buffer !== memory.buffer) { + buffer = memory.buffer; + view = new DataView(buffer); + } + return pointer <= buffer.byteLength - 8 ? pointer : undefined; + } + + function callFallback(clockId, precision, timePointer) { + const errno = fallback(clockId, precision, timePointer); + if (clockId === 0) { + realtimeDirectReadsSinceFallback = 0; + lastRealtimeFallbackMillis = + errno === 0 ? sampleRealtimeMillis() : undefined; + } else if (clockId === 1) { + monotonicDirectReadsSinceFallback = 0; + const sampledMonotonicMillis = + errno === 0 ? samplePerformanceMillis() : undefined; + lastMonotonicFallbackMillis = sampledMonotonicMillis; + const pointer = errno === 0 ? refreshView(timePointer) : undefined; + monotonicAnchor = + sampledMonotonicMillis !== undefined && pointer !== undefined + ? { + millis: sampledMonotonicMillis, + nanoseconds: view.getBigUint64(pointer, true), + } + : undefined; + } + return errno; + } + + return function clock_time_get(clockId, precision, timePointer) { + if (state.canonical) return fallback(clockId, precision, timePointer); + let nanoseconds; + if (clockId === 0) { + const realtimeMillis = sampleRealtimeMillis(); + if (realtimeMillis === undefined || + lastRealtimeFallbackMillis === undefined || + realtimeMillis < lastRealtimeFallbackMillis || + realtimeDirectReadsSinceFallback >= MAX_DIRECT_READS_BETWEEN_FALLBACKS || + realtimeMillis - lastRealtimeFallbackMillis >= MAX_CANONICAL_INTERVAL_MILLIS) { + return callFallback(clockId, precision, timePointer); + } + nanoseconds = BigInt(Math.trunc(realtimeMillis)) * 1000000n; + } else if (clockId === 1) { + const monotonicMillis = samplePerformanceMillis(); + if (monotonicMillis === undefined || + lastMonotonicFallbackMillis === undefined || + monotonicMillis < lastMonotonicFallbackMillis || + monotonicDirectReadsSinceFallback >= MAX_DIRECT_READS_BETWEEN_FALLBACKS || + monotonicMillis - lastMonotonicFallbackMillis >= MAX_CANONICAL_INTERVAL_MILLIS || + monotonicAnchor === undefined || + monotonicMillis < monotonicAnchor.millis) { + return callFallback(clockId, precision, timePointer); + } + nanoseconds = monotonicAnchor.nanoseconds + + BigInt(Math.trunc((monotonicMillis - monotonicAnchor.millis) * 1000000)); + } else { + return callFallback(clockId, precision, timePointer); + } + + if (nanoseconds > MAX_CANONICAL_NANOSECONDS) { + return callFallback(clockId, precision, timePointer); + } + + const pointer = refreshView(timePointer); + if (pointer === undefined) { + return callFallback(clockId, precision, timePointer); + } + view.setBigUint64(pointer, nanoseconds, true); + if (clockId === 0) { + realtimeDirectReadsSinceFallback += 1; + } else { + monotonicDirectReadsSinceFallback += 1; + } + return 0; + }; +} + +export function oliphauntClockSetImport(memory, fallback) { + const state = clockState(memory); + return function clock_time_set(clockId, time) { + // Disable every reader before entering WASIX: pending work can reenter + // another module. Even a failed attempt conservatively stays canonical. + state.canonical = true; + return fallback(clockId, time); + }; +} +"#)] +extern "C" { + #[wasm_bindgen(js_name = oliphauntDirectClockImport)] + fn direct_clock(memory: &JsValue, fallback: &JsValue) -> js_sys::Function; + #[wasm_bindgen(js_name = oliphauntClockSetImport)] + fn clock_set(memory: &JsValue, fallback: &JsValue) -> js_sys::Function; +} + +/// Registered only by the direct PostgreSQL constructor, never command/tools +/// runtimes, journaling or guest-controlled threads. Functions remain local to +/// their own stores; only a mode bit is shared by modules using the same memory. +#[derive(Debug)] +pub(crate) struct DirectClockHook; + +impl InstantiationHook for DirectClockHook { + fn prepare_imports( + &self, + module: &Module, + store: &mut StoreMut, + imports: &mut Imports, + ) -> anyhow::Result { + let Some(Extern::Memory(memory)) = imports.get_export("env", "memory") else { + // Modules without an imported memory retain ordinary WASIX clocks. + return Ok(InstantiationState::empty()); + }; + let raw_memory = memory.as_jsvalue(store); + for import in module.imports().filter(|import| { + matches!( + import.module(), + "wasi_snapshot_preview1" | "wasi_unstable" | "wasix_32v1" | "wasix_64v1" + ) && matches!(import.name(), "clock_time_get" | "clock_time_set") + }) { + let Some(Extern::Function(fallback)) = + imports.get_export(import.module(), import.name()) + else { + continue; + }; + let function_type = fallback.ty(store); + let raw_fallback = fallback.as_jsvalue(store); + let wrapper = if import.name() == "clock_time_set" { + clock_set(&raw_memory, &raw_fallback) + } else { + direct_clock(&raw_memory, &raw_fallback) + }; + let function = Function::from_jsvalue(store, &function_type, wrapper.as_ref()) + .map_err(|error| { + anyhow::anyhow!("install {}.{}: {error:?}", import.module(), import.name()) + })?; + imports.define(import.module(), import.name(), function); + } + Ok(InstantiationState::empty()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use wasmer::{Memory, Store}; + use wasmer_wasix::{Runtime, WasiEnvBuilder}; + + // Import-only module: shared env.memory; get(i32,i64,i32)->i32 and + // set(i32,i64)->i32, re-exported along with the memory. No guest compiler. + fn clock_module() -> Vec { + fn string(bytes: &mut Vec, value: &str) { + bytes.push(value.len() as u8); + bytes.extend_from_slice(value.as_bytes()); + } + fn section(bytes: &mut Vec, id: u8, contents: &[u8]) { + bytes.extend_from_slice(&[id, contents.len() as u8]); + bytes.extend_from_slice(contents); + } + let mut bytes = b"\0asm\x01\0\0\0".to_vec(); + section( + &mut bytes, + 1, + &[ + 2, 0x60, 3, 0x7f, 0x7e, 0x7f, 1, 0x7f, 0x60, 2, 0x7f, 0x7e, 1, 0x7f, + ], + ); + let mut imports = vec![3]; + string(&mut imports, "env"); + string(&mut imports, "memory"); + imports.extend_from_slice(&[2, 3, 16, 16]); + for (namespace, name, ty) in [ + ("wasi_snapshot_preview1", "clock_time_get", 0), + ("wasix_32v1", "clock_time_set", 1), + ] { + string(&mut imports, namespace); + string(&mut imports, name); + imports.extend_from_slice(&[0, ty]); + } + section(&mut bytes, 2, &imports); + let mut exports = vec![3]; + for (name, kind, index) in [("memory", 2, 0), ("get", 0, 0), ("set", 0, 1)] { + string(&mut exports, name); + exports.extend_from_slice(&[kind, index]); + } + section(&mut bytes, 7, &exports); + bytes + } + + fn read_time(memory: &Memory, store: &Store) -> u64 { + let mut bytes = [0; 8]; + memory.view(store).read(0, &mut bytes).unwrap(); + u64::from_le_bytes(bytes) + } + + #[wasm_bindgen_test::wasm_bindgen_test] + async fn real_wasix_clock_adjustments_remain_visible() { + let mut runtime = super::super::runtime::runtime(Arc::new( + super::super::caller_realm::CallerRealmTaskManager, + )) + .unwrap(); + runtime.with_instantiation_hook(DirectClockHook); + let runtime = Arc::new(runtime); + let mut store = Store::new(runtime.engine()); + let module = Module::new(&store, clock_module()).unwrap(); + let (instance, _env) = WasiEnvBuilder::new("clock-test") + .runtime(runtime) + .instantiate_async(module, &mut store) + .await + .unwrap(); + let get = instance + .exports + .get_typed_function::<(u32, u64, u32), u32>(&store, "get") + .unwrap(); + let set = instance + .exports + .get_typed_function::<(u32, u64), u32>(&store, "set") + .unwrap(); + let memory = instance.exports.get_memory("memory").unwrap(); + for clock_id in [0, 1] { + assert_eq!(get.call(&mut store, clock_id, 0, 0).unwrap(), 0); + let target = read_time(memory, &store) + 5_000_000_000; + assert_eq!(set.call(&mut store, clock_id, target).unwrap(), 0); + for _ in 0..8 { + assert_eq!(get.call(&mut store, clock_id, 0, 0).unwrap(), 0); + let observed = read_time(memory, &store); + assert!(observed >= target && observed < target + 1_000_000_000); + } + } + } +} diff --git a/src/wasix/browser-host/adapter/fs/directory.rs b/src/wasix/browser-host/adapter/fs/directory.rs new file mode 100644 index 000000000..2ce0c4af9 --- /dev/null +++ b/src/wasix/browser-host/adapter/fs/directory.rs @@ -0,0 +1,612 @@ +use std::{ + collections::BTreeSet, + path::{Path, PathBuf}, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context as TaskContext, Poll}, +}; + +use anyhow::Context; +use js_sys::{Array, Reflect}; +use tracing::Instrument; +use virtual_fs::{ + AsyncRead, AsyncReadExt, AsyncSeek, AsyncWrite, AsyncWriteExt, FileSystem, FileType, ReadBuf, + VirtualFile, +}; +use virtual_mio::block_on; +use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen}; + +use super::super::{StringOrBytes, utils::Error}; + +use super::SyncBridgeFileSystem; + +/// A directory that can be mounted inside a WASIX instance. +#[derive(Debug, Clone)] +#[wasm_bindgen] +pub struct Directory { + fs: Arc, + changes: Arc>>, + track_changes: bool, +} + +#[wasm_bindgen] +impl Directory { + #[wasm_bindgen(js_name = "__getClassname")] + pub fn js_classname(&self) -> String { + "Directory".to_owned() + } + + /// Mounts share the filesystem and journal without consuming the JS handle. + #[wasm_bindgen(js_name = "__cloneForMount")] + pub fn clone_for_mount(&self) -> Directory { + self.clone() + } + /// Create a new {@link Directory}. + #[wasm_bindgen(constructor)] + pub fn new(init: Option) -> Result { + match init { + Some(init) => { + let fs = init.initialize()?; + Ok(Directory::from_filesystem(fs)) + } + None => Ok(Directory::default()), + } + } + + /// Create a directory whose filesystem is served synchronously by a + /// caller-realm JavaScript backend. Oliphaunt uses this only from its + /// single-backend worker, where OPFS synchronous access handles are legal. + #[wasm_bindgen(js_name = "createSync")] + pub fn create_sync(backend: JsValue, capacity: usize) -> Result { + Ok(Directory::from_untracked_filesystem(Arc::new( + SyncBridgeFileSystem::new(backend, capacity)?, + ))) + } + + /// Read the contents of a directory. + #[wasm_bindgen(js_name = "readDir")] + pub async fn read_dir(&self, mut path: String) -> Result { + if !path.starts_with('/') { + path.insert(0, '/'); + } + + let contents = js_sys::Array::new(); + + let ty = JsValue::from_str("type"); + let file = JsValue::from_str("file"); + let dir = JsValue::from_str("dir"); + let unknown = JsValue::from_str("unknown"); + let name = JsValue::from_str("name"); + + for entry in FileSystem::read_dir(self, path.as_ref())? { + let entry = entry?; + + let entry_name = entry.file_name().to_string_lossy().to_string(); + let entry_type = match entry.file_type() { + Ok(FileType { dir: true, .. }) => &dir, + Ok(FileType { file: true, .. }) => &file, + _ => &unknown, + }; + + let dir_entry = js_sys::Object::new(); + Reflect::set(&dir_entry, &name, &JsValue::from(entry_name)).map_err(Error::js)?; + Reflect::set(&dir_entry, &ty, entry_type).map_err(Error::js)?; + + contents.push(&dir_entry); + } + + Ok(contents.unchecked_into()) + } + + /// Write to a file. + /// + /// If a string is provided, it is encoded as UTF-8. + #[wasm_bindgen(js_name = "writeFile")] + pub async fn write_file(&self, mut path: String, contents: StringOrBytes) -> Result<(), Error> { + if !path.starts_with('/') { + path.insert(0, '/'); + } + + let mut f = self + .new_open_options() + .write(true) + .create(true) + .open(&path)?; + + let contents = contents.as_bytes(); + f.write_all(&contents).await?; + + Ok(()) + } + + /// Read the contents of a file from this directory. + /// + /// Note that the path is relative to the directory's root. + #[wasm_bindgen(js_name = "readFile")] + pub async fn read_file(&self, path: String) -> Result { + let buffer = self._read_file(path).await?; + Ok(js_sys::Uint8Array::from(&buffer[..])) + } + + /// Read the contents of a file from this directory as a UTF-8 string. + /// + /// Note that the path is relative to the directory's root. + #[wasm_bindgen(js_name = "readTextFile")] + pub async fn read_text_file(&self, path: String) -> Result { + let buffer = self._read_file(path).await?; + let string = String::from_utf8(buffer)?; + Ok(string.into()) + } + + /// Create a directory. + #[wasm_bindgen(js_name = "createDir")] + pub async fn create_dir(&self, mut path: String) -> Result<(), Error> { + if !path.starts_with('/') { + path.insert(0, '/'); + } + + FileSystem::create_dir(self, path.as_ref())?; + + Ok(()) + } + + /// Remove a directory. + #[wasm_bindgen(js_name = "removeDir")] + pub async fn remove_dir(&self, mut path: String) -> Result<(), Error> { + if !path.starts_with('/') { + path.insert(0, '/'); + } + + FileSystem::remove_dir(self, path.as_ref())?; + + Ok(()) + } + + /// Remove a file. + #[wasm_bindgen(js_name = "removeFile")] + pub async fn remove_file(&self, mut path: String) -> Result<(), Error> { + if !path.starts_with('/') { + path.insert(0, '/'); + } + + FileSystem::remove_file(self, path.as_ref())?; + + Ok(()) + } + + /// List paths changed through this directory since the last acknowledgement. + /// + /// The paths are relative to the directory root. A consumer treats each + /// path's current state as authoritative, so the same compact journal + /// represents file writes, creates, removes, and directory renames. + #[wasm_bindgen(js_name = "changedPaths")] + pub fn changed_paths(&self) -> Result { + let changes = self + .changes + .lock() + .map_err(|_| anyhow::anyhow!("directory change journal is poisoned"))?; + let values = Array::new(); + for path in changes.iter() { + values.push(&JsValue::from(path)); + } + Ok(values) + } + + /// Discard the current change journal without changing directory contents. + #[wasm_bindgen(js_name = "clearChanges")] + pub fn clear_changes(&self) -> Result<(), Error> { + self.changes + .lock() + .map_err(|_| anyhow::anyhow!("directory change journal is poisoned"))? + .clear(); + Ok(()) + } + + /// Inspect one path without parsing exception text to distinguish a + /// missing entry from a genuine storage failure. + #[wasm_bindgen(js_name = "entryType")] + pub fn entry_type(&self, mut path: String) -> Result { + if !path.starts_with('/') { + path.insert(0, '/'); + } + match self.fs.metadata(Path::new(&path)) { + Ok(metadata) if metadata.is_dir() => Ok("dir".to_owned()), + Ok(metadata) if metadata.is_file() => Ok("file".to_owned()), + Ok(_) => Ok("unknown".to_owned()), + Err(virtual_fs::FsError::EntryNotFound) => Ok("missing".to_owned()), + Err(error) => Err(error.into()), + } + } +} + +impl Directory { + fn from_filesystem(fs: Arc) -> Self { + Self::from_filesystem_with_tracking(fs, true) + } + + fn from_untracked_filesystem(fs: Arc) -> Self { + Self::from_filesystem_with_tracking(fs, false) + } + + fn from_filesystem_with_tracking(fs: Arc, track_changes: bool) -> Self { + Self { + fs, + changes: Arc::new(Mutex::new(BTreeSet::new())), + track_changes, + } + } + + fn record_change(&self, path: &Path) { + if self.track_changes { + record_change(&self.changes, path); + } + } + + async fn _read_file(&self, mut path: String) -> Result, Error> { + if !path.starts_with('/') { + path.insert(0, '/'); + } + + let mut f = self.new_open_options().read(true).open(&path)?; + let mut buffer = Vec::with_capacity(f.size() as usize); + f.read_to_end(&mut buffer).await?; + + Ok(buffer) + } +} + +impl Default for Directory { + fn default() -> Self { + Directory::from_filesystem(Arc::new(virtual_fs::mem_fs::FileSystem::default())) + } +} + +impl FileSystem for Directory { + #[tracing::instrument(level = "trace", skip(self))] + fn read_dir(&self, path: &std::path::Path) -> virtual_fs::Result { + self.fs.read_dir(path) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn create_dir(&self, path: &std::path::Path) -> virtual_fs::Result<()> { + self.fs.create_dir(path)?; + self.record_change(path); + Ok(()) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn remove_dir(&self, path: &std::path::Path) -> virtual_fs::Result<()> { + self.fs.remove_dir(path)?; + self.record_change(path); + Ok(()) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn rename<'a>( + &'a self, + from: &'a std::path::Path, + to: &'a std::path::Path, + ) -> futures::future::BoxFuture<'a, virtual_fs::Result<()>> { + if !self.track_changes { + return Box::pin(self.fs.rename(from, to).in_current_span()); + } + let operation = self.fs.rename(from, to).in_current_span(); + let changes = self.changes.clone(); + let from = from.to_path_buf(); + let to = to.to_path_buf(); + Box::pin(async move { + operation.await?; + record_change(&changes, &from); + record_change(&changes, &to); + Ok(()) + }) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn metadata(&self, path: &std::path::Path) -> virtual_fs::Result { + self.fs.metadata(path) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn remove_file(&self, path: &std::path::Path) -> virtual_fs::Result<()> { + self.fs.remove_file(path)?; + self.record_change(path); + Ok(()) + } + + fn new_open_options(&self) -> virtual_fs::OpenOptions<'_> { + virtual_fs::OpenOptions::new(self) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn readlink(&self, path: &Path) -> virtual_fs::Result { + self.fs.readlink(path) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result { + self.fs.symlink_metadata(path) + } +} + +impl virtual_fs::FileOpener for Directory { + #[tracing::instrument(level = "trace", skip(self))] + fn open( + &self, + path: &std::path::Path, + conf: &virtual_fs::OpenOptionsConfig, + ) -> virtual_fs::Result> { + if !self.track_changes { + return self.fs.new_open_options().options(conf.clone()).open(path); + } + + let existed = if conf.create && !conf.truncate && !conf.create_new { + self.fs.metadata(path).is_ok() + } else { + true + }; + let file = self + .fs + .new_open_options() + .options(conf.clone()) + .open(path)?; + if conf.write || conf.append || conf.truncate || conf.create || conf.create_new { + if conf.truncate || conf.create_new || (conf.create && !existed) { + self.record_change(path); + } + Ok(Box::new(ChangeTrackingFile { + file, + path: path.to_path_buf(), + changes: self.changes.clone(), + })) + } else { + Ok(file) + } + } +} + +#[derive(Debug)] +struct ChangeTrackingFile { + file: Box, + path: PathBuf, + changes: Arc>>, +} + +impl ChangeTrackingFile { + fn record_change(&self) { + record_change(&self.changes, &self.path); + } +} + +impl VirtualFile for ChangeTrackingFile { + fn last_accessed(&self) -> u64 { + self.file.last_accessed() + } + + fn last_modified(&self) -> u64 { + self.file.last_modified() + } + + fn created_time(&self) -> u64 { + self.file.created_time() + } + + fn set_times(&mut self, atime: Option, mtime: Option) -> virtual_fs::Result<()> { + self.file.set_times(atime, mtime) + } + + fn size(&self) -> u64 { + self.file.size() + } + + fn set_len(&mut self, new_size: u64) -> virtual_fs::Result<()> { + self.file.set_len(new_size)?; + self.record_change(); + Ok(()) + } + + fn unlink(&mut self) -> virtual_fs::Result<()> { + self.file.unlink()?; + self.record_change(); + Ok(()) + } + + fn is_open(&self) -> bool { + self.file.is_open() + } + + fn get_special_fd(&self) -> Option { + self.file.get_special_fd() + } + + fn write_from_mmap(&mut self, offset: u64, len: u64) -> std::io::Result<()> { + self.file.write_from_mmap(offset, len)?; + self.record_change(); + Ok(()) + } + + fn poll_read_ready( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + ) -> Poll> { + Pin::new(&mut *self.file).poll_read_ready(cx) + } + + fn poll_write_ready( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + ) -> Poll> { + Pin::new(&mut *self.file).poll_write_ready(cx) + } +} + +impl AsyncRead for ChangeTrackingFile { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buffer: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut *self.file).poll_read(cx, buffer) + } +} + +impl AsyncWrite for ChangeTrackingFile { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buffer: &[u8], + ) -> Poll> { + let result = Pin::new(&mut *self.file).poll_write(cx, buffer); + if matches!(result, Poll::Ready(Ok(written)) if written > 0) { + self.record_change(); + } + result + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut *self.file).poll_flush(cx) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + ) -> Poll> { + Pin::new(&mut *self.file).poll_shutdown(cx) + } +} + +impl AsyncSeek for ChangeTrackingFile { + fn start_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> { + Pin::new(&mut *self.file).start_seek(position) + } + + fn poll_complete( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + ) -> Poll> { + Pin::new(&mut *self.file).poll_complete(cx) + } +} + +fn record_change(changes: &Mutex>, path: &Path) { + let mut relative = PathBuf::new(); + for component in path.components() { + match component { + std::path::Component::Prefix(_) | std::path::Component::RootDir => {} + std::path::Component::CurDir => {} + std::path::Component::ParentDir => { + relative.pop(); + } + std::path::Component::Normal(part) => relative.push(part), + } + } + if let Ok(mut journal) = changes.lock() { + journal.insert(relative.to_string_lossy().replace('\\', "/")); + } +} + +#[wasm_bindgen(typescript_custom_section)] +const DIRENTRY_TYPE_DEF: &'static str = r#" +/** + * An entry in a {@link Directory}. + */ +export type DirEntry = { + /** + * What type of entry is this? + */ + type: "file" | "dir" | "unknown"; + /** + * What is the item's name? (the last component in the path) + */ + name: string; +}; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "DirEntry[]")] + pub type ListOfDirEntry; +} + +#[wasm_bindgen(typescript_custom_section)] +const DIRECTORY_INIT_TYPE_DEF: &'static str = r#" +/** + * A mapping from file paths to their contents that can be used to initialize + * a {@link Directory}. + */ +export type DirectoryInit = Record; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "DirectoryInit", extends = js_sys::Object)] + #[derive(Debug, Clone, PartialEq)] + pub type DirectoryInit; +} + +impl DirectoryInit { + fn initialize(&self) -> Result, Error> { + if let Some(record) = self.dyn_ref::() { + let fs = in_memory_filesystem(record)?; + Ok(Arc::new(fs)) + } else { + unreachable!() + } + } +} + +/// Construct an in-memory [`FileSystem`] based on an object mapping paths to +/// their contents (`Record`). +fn in_memory_filesystem(record: &js_sys::Object) -> Result { + let fs = virtual_fs::mem_fs::FileSystem::default(); + + for (key, contents) in super::super::utils::object_entries(record)? { + let mut path = String::from(key); + if !path.starts_with('/') { + path.insert(0, '/'); + } + let path = PathBuf::from(path); + + let contents: StringOrBytes = contents.unchecked_into(); + let contents = contents.as_bytes(); + + if let Some(parent) = path.parent() { + create_dir_all(&fs, parent)?; + } + + tracing::trace!( + path=%path.display(), + file.length=contents.len(), + "Adding file to directory", + ); + block_on(async { + let mut f = fs + .new_open_options() + .write(true) + .create_new(true) + .open(&path)?; + f.write_all(&contents).await?; + f.flush().await + }) + .with_context(|| format!("Unable to write to \"{}\"", path.display()))?; + } + + Ok(fs) +} + +#[tracing::instrument(level = "trace", skip(fs))] +fn create_dir_all(fs: &dyn FileSystem, path: &Path) -> Result<(), anyhow::Error> { + let ancestors: Vec<&Path> = path.ancestors().collect(); + + for ancestor in ancestors.into_iter().rev() { + if fs.read_dir(ancestor).is_ok() { + continue; + } + + fs.create_dir(ancestor).with_context(|| { + format!("Unable to create the \"{}\" directory", ancestor.display()) + })?; + } + + Ok(()) +} diff --git a/src/wasix/browser-host/adapter/fs/mod.rs b/src/wasix/browser-host/adapter/fs/mod.rs new file mode 100644 index 000000000..058a65744 --- /dev/null +++ b/src/wasix/browser-host/adapter/fs/mod.rs @@ -0,0 +1,5 @@ +mod directory; +mod sync_bridge; + +pub use self::directory::{Directory, DirectoryInit}; +pub(crate) use self::sync_bridge::SyncBridgeFileSystem; diff --git a/src/wasix/browser-host/adapter/fs/sync_bridge.rs b/src/wasix/browser-host/adapter/fs/sync_bridge.rs new file mode 100644 index 000000000..ef05b1f40 --- /dev/null +++ b/src/wasix/browser-host/adapter/fs/sync_bridge.rs @@ -0,0 +1,787 @@ +use std::{ + cell::RefCell, + collections::HashMap, + fmt, + io::{self, SeekFrom}, + path::{Path, PathBuf}, + pin::Pin, + rc::Rc, + sync::{ + Arc, Mutex, + atomic::{AtomicU32, Ordering}, + }, + task::{Context, Poll}, +}; + +use futures::future::BoxFuture; +use js_sys::{Array, Function, Uint8Array}; +use virtual_fs::{ + AsyncRead, AsyncSeek, AsyncWrite, DirEntry, FileOpener, FileSystem, FileType, FsError, + Metadata, OpenOptions, OpenOptionsConfig, ReadBuf, ReadDir, VirtualFile, +}; +use wasm_bindgen::{JsCast, JsValue}; + +const OP_METADATA: i32 = 1; +const OP_READ_DIR: i32 = 2; +const OP_CREATE_DIR: i32 = 3; +const OP_REMOVE_DIR: i32 = 4; +const OP_RENAME: i32 = 5; +const OP_REMOVE_FILE: i32 = 6; +const OP_OPEN: i32 = 7; +const OP_CLOSE: i32 = 8; +const OP_READ: i32 = 9; +const OP_WRITE: i32 = 10; +const OP_FLUSH: i32 = 11; +const OP_TRUNCATE: i32 = 12; +const OP_UNLINK: i32 = 13; +const OP_FILE_SIZE: i32 = 14; +const READ_DIR_PAGE_CAPACITY: usize = 64 * 1024; + +const RESULT_OK: i32 = 0; +const RESULT_NOT_FOUND: i32 = 1; +const RESULT_EXISTS: i32 = 2; +const RESULT_NOT_DIR: i32 = 3; +const RESULT_NOT_FILE: i32 = 4; +const RESULT_DIR_NOT_EMPTY: i32 = 5; +const RESULT_PERMISSION: i32 = 6; +const RESULT_INVALID: i32 = 7; +const RESULT_STORAGE_FULL: i32 = 8; +const RESULT_UNSUPPORTED: i32 = 9; +const RESULT_TIMEOUT: i32 = 10; + +const TYPE_FILE: u64 = 1; +const TYPE_DIRECTORY: u64 = 2; + +const FLAG_READ: i32 = 1 << 0; +const FLAG_WRITE: i32 = 1 << 1; +const FLAG_CREATE_NEW: i32 = 1 << 2; +const FLAG_CREATE: i32 = 1 << 3; +const FLAG_APPEND: i32 = 1 << 4; +const FLAG_TRUNCATE: i32 = 1 << 5; + +static NEXT_BACKEND_ID: AtomicU32 = AtomicU32::new(1); + +thread_local! { + /// JavaScript references must remain in the realm that created them. + /// Rust filesystem objects carry only the corresponding numeric key. + static REALM_BACKENDS: RefCell>> = + RefCell::new(HashMap::new()); +} + +struct RealmBackend { + backend: JsValue, + request: Function, +} + +/// A synchronous virtual filesystem delegated to a JavaScript object in the +/// caller realm. The backend owns OPFS access handles while Rust owns the +/// syscall-facing filesystem. Calls are deliberately single-flight because +/// Oliphaunt's PostgreSQL runtime is single-backend. +pub(crate) struct SyncBridgeFileSystem { + backend: Arc, +} + +impl fmt::Debug for SyncBridgeFileSystem { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("SyncBridgeFileSystem") + .finish_non_exhaustive() + } +} + +impl SyncBridgeFileSystem { + pub(crate) fn new(backend: JsValue, capacity: usize) -> virtual_fs::Result { + Ok(Self { + backend: Arc::new(Backend::new(backend, capacity)?), + }) + } + + fn metadata_inner(&self, path: &Path) -> virtual_fs::Result { + let path = normalize_path(path)?; + let response = self.backend.request(OP_METADATA, &path, &[], 0, 0, 0)?; + metadata(response.value0, response.value1) + } +} + +impl FileSystem for SyncBridgeFileSystem { + fn readlink(&self, _path: &Path) -> virtual_fs::Result { + Err(FsError::Unsupported) + } + + fn read_dir(&self, path: &Path) -> virtual_fs::Result { + let path = normalize_path(path)?; + let mut cursor = 0; + let mut entries = Vec::new(); + loop { + let response = self + .backend + .request(OP_READ_DIR, &path, &[], cursor, 0, 0)?; + entries.extend(decode_directory_entries(&path, &response.bytes)?); + match response.value1 { + 1 => break, + 0 if response.value0 > cursor => cursor = response.value0, + _ => return Err(FsError::InvalidData), + } + } + Ok(ReadDir::new(entries)) + } + + fn create_dir(&self, path: &Path) -> virtual_fs::Result<()> { + let path = normalize_path(path)?; + self.backend.request(OP_CREATE_DIR, &path, &[], 0, 0, 0)?; + Ok(()) + } + + fn remove_dir(&self, path: &Path) -> virtual_fs::Result<()> { + let path = normalize_path(path)?; + self.backend.request(OP_REMOVE_DIR, &path, &[], 0, 0, 0)?; + Ok(()) + } + + fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, virtual_fs::Result<()>> { + let result = (|| { + let from = normalize_path(from)?; + let to = normalize_path(to)?; + self.backend + .request(OP_RENAME, &from, to.as_bytes(), 0, 0, 0)?; + Ok(()) + })(); + Box::pin(async move { result }) + } + + fn metadata(&self, path: &Path) -> virtual_fs::Result { + self.metadata_inner(path) + } + + fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result { + self.metadata_inner(path) + } + + fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> { + let path = normalize_path(path)?; + self.backend.request(OP_REMOVE_FILE, &path, &[], 0, 0, 0)?; + Ok(()) + } + + fn new_open_options(&self) -> OpenOptions<'_> { + OpenOptions::new(self) + } +} + +impl FileOpener for SyncBridgeFileSystem { + fn open( + &self, + path: &Path, + configuration: &OpenOptionsConfig, + ) -> virtual_fs::Result> { + let path = normalize_path(path)?; + let flags = (if configuration.read { FLAG_READ } else { 0 }) + | (if configuration.write { FLAG_WRITE } else { 0 }) + | (if configuration.create_new { + FLAG_CREATE_NEW + } else { + 0 + }) + | (if configuration.create { FLAG_CREATE } else { 0 }) + | (if configuration.append { FLAG_APPEND } else { 0 }) + | (if configuration.truncate { + FLAG_TRUNCATE + } else { + 0 + }); + let response = self.backend.request(OP_OPEN, &path, &[], 0, 0, flags)?; + let cursor = if configuration.append { + response.value1 + } else { + 0 + }; + Ok(Box::new(SyncBridgeFile { + backend: self.backend.clone(), + descriptor: u32::try_from(response.value0).map_err(|_| FsError::InvalidFd)?, + readable: configuration.read, + writable: configuration.write || configuration.append, + cursor, + })) + } +} + +struct Backend { + id: u32, + origin_thread: u32, + capacity: usize, + gate: Mutex<()>, +} + +impl fmt::Debug for Backend { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("Backend") + .field("id", &self.id) + .field("origin_thread", &self.origin_thread) + .field("capacity", &self.capacity()) + .finish() + } +} + +impl Backend { + fn new(backend: JsValue, capacity: usize) -> virtual_fs::Result { + if capacity < 8 * 1024 || capacity > 4 * 1024 * 1024 { + return Err(FsError::InvalidInput); + } + let request = js_sys::Reflect::get(&backend, &JsValue::from_str("request")) + .map_err(|_| FsError::InvalidInput)? + .dyn_into::() + .map_err(|_| FsError::InvalidInput)?; + let origin_thread = wasmer::js::current_thread_id(); + let id = REALM_BACKENDS + .try_with(|backends| { + let mut backends = backends.try_borrow_mut().map_err(|_| FsError::Lock)?; + let id = loop { + let candidate = NEXT_BACKEND_ID.fetch_add(1, Ordering::Relaxed); + if candidate != 0 && !backends.contains_key(&candidate) { + break candidate; + } + }; + backends.insert(id, Rc::new(RealmBackend { backend, request })); + Ok::(id) + }) + .map_err(|_| FsError::Lock)??; + Ok(Self { + id, + origin_thread, + capacity, + gate: Mutex::new(()), + }) + } + + fn capacity(&self) -> usize { + self.capacity + } + + fn realm_backend(&self) -> virtual_fs::Result> { + if wasmer::js::current_thread_id() != self.origin_thread { + return Err(FsError::Lock); + } + REALM_BACKENDS + .try_with(|backends| { + let backends = backends.try_borrow().map_err(|_| FsError::Lock)?; + backends + .get(&self.id) + .map(Rc::clone) + .ok_or(FsError::InvalidFd) + }) + .map_err(|_| FsError::Lock)? + } + + fn request( + &self, + opcode: i32, + path: &str, + payload: &[u8], + arg0: u64, + arg1: u64, + flags: i32, + ) -> virtual_fs::Result { + let response_capacity = match opcode { + OP_READ_DIR => self.capacity().min(READ_DIR_PAGE_CAPACITY), + _ => 0, + }; + let mut bytes = vec![0; response_capacity]; + let response = self.request_into(opcode, path, payload, &mut bytes, arg0, arg1, flags)?; + bytes.truncate(response.length); + Ok(Response { + bytes, + value0: response.value0, + value1: response.value1, + }) + } + + fn read(&self, descriptor: u32, offset: u64, output: &mut [u8]) -> virtual_fs::Result { + let response = self.request_into( + OP_READ, + "", + &[], + output, + descriptor.into(), + offset, + i32::try_from(output.len()).map_err(|_| FsError::StorageFull)?, + )?; + Ok(response.length) + } + + #[allow(clippy::too_many_arguments)] + fn request_into( + &self, + opcode: i32, + path: &str, + payload: &[u8], + output: &mut [u8], + arg0: u64, + arg1: u64, + flags: i32, + ) -> virtual_fs::Result { + // A JavaScript callback can synchronously reenter this filesystem. + // Never wait for a lock that the current call already holds. + let _guard = self.gate.try_lock().map_err(|_| FsError::Lock)?; + if payload.len() > self.capacity() || output.len() > self.capacity() { + return Err(FsError::StorageFull); + } + if !payload.is_empty() && !output.is_empty() { + return Err(FsError::InvalidInput); + } + let realm_backend = self.realm_backend()?; + let transfer_length = payload.len().max(output.len()); + let transfer = Uint8Array::new_with_length( + u32::try_from(transfer_length).map_err(|_| FsError::StorageFull)?, + ); + if !payload.is_empty() { + transfer.copy_from(payload); + } + let arguments = Array::new(); + arguments.push(&JsValue::from(opcode)); + arguments.push(&JsValue::from_str(path)); + // Each callback receives an exact-sized JavaScript-owned allocation. + // It may synchronously mutate, retain, or transfer the bytes without + // aliasing Rust memory or observing storage from another request. + arguments.push(transfer.as_ref()); + arguments.push(&JsValue::from_f64(arg0 as f64)); + arguments.push(&JsValue::from_f64(arg1 as f64)); + arguments.push(&JsValue::from(flags)); + let raw = realm_backend + .request + .apply(&realm_backend.backend, &arguments) + .map_err(|_| FsError::IOError)?; + let values = Array::from(&raw); + if values.length() != 4 { + return Err(FsError::InvalidData); + } + let result = js_i32(&values.get(0))?; + let response_len = js_usize(&values.get(1))?; + let value0 = js_u64(&values.get(2))?; + let value1 = js_u64(&values.get(3))?; + decode_result(result)?; + if response_len > output.len() { + return Err(FsError::InvalidData); + } + if response_len > 0 { + // A callback may transfer the allocation after performing a + // side-effecting operation. Detachment is only an error when a + // successful response still has bytes that Rust must copy. + if transfer.length() as usize != transfer_length { + return Err(FsError::InvalidData); + } + transfer + .subarray( + 0, + u32::try_from(response_len).map_err(|_| FsError::InvalidData)?, + ) + .copy_to(&mut output[..response_len]); + } + Ok(ResponseHead { + length: response_len, + value0, + value1, + }) + } +} + +impl Drop for Backend { + fn drop(&mut self) { + // A filesystem may be released on a different worker because the + // virtual-fs traits require Send + Sync. Never touch foreign-realm JS + // references; in that exceptional case the realm-local entry leaks. + if wasmer::js::current_thread_id() != self.origin_thread { + return; + } + let _ = REALM_BACKENDS.try_with(|backends| { + if let Ok(mut backends) = backends.try_borrow_mut() { + backends.remove(&self.id); + } + }); + } +} + +struct ResponseHead { + length: usize, + value0: u64, + value1: u64, +} + +struct Response { + bytes: Vec, + value0: u64, + value1: u64, +} + +struct SyncBridgeFile { + backend: Arc, + descriptor: u32, + readable: bool, + writable: bool, + cursor: u64, +} + +impl fmt::Debug for SyncBridgeFile { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("SyncBridgeFile") + .field("descriptor", &self.descriptor) + .field("cursor", &self.cursor) + .finish_non_exhaustive() + } +} + +impl SyncBridgeFile { + fn seek_from_end_sync(&mut self, offset: i64) -> io::Result { + let size = self + .backend + .request(OP_FILE_SIZE, "", &[], self.descriptor.into(), 0, 0) + .map(|response| response.value0); + update_cursor_from_end(&mut self.cursor, size, offset) + } +} + +impl Drop for SyncBridgeFile { + fn drop(&mut self) { + let _ = self + .backend + .request(OP_CLOSE, "", &[], self.descriptor.into(), 0, 0); + } +} + +impl VirtualFile for SyncBridgeFile { + fn last_accessed(&self) -> u64 { + 0 + } + + fn last_modified(&self) -> u64 { + 0 + } + + fn created_time(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + self.backend + .request(OP_FILE_SIZE, "", &[], self.descriptor.into(), 0, 0) + .map(|response| response.value0) + .unwrap_or(0) + } + + fn set_len(&mut self, new_size: u64) -> virtual_fs::Result<()> { + if !self.writable { + return Err(FsError::PermissionDenied); + } + self.backend + .request(OP_TRUNCATE, "", &[], self.descriptor.into(), new_size, 0)?; + self.cursor = self.cursor.min(new_size); + Ok(()) + } + + fn unlink(&mut self) -> virtual_fs::Result<()> { + self.backend + .request(OP_UNLINK, "", &[], self.descriptor.into(), 0, 0)?; + Ok(()) + } + + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(self.backend.capacity())) + } + + fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(self.backend.capacity())) + } +} + +impl AsyncRead for SyncBridgeFile { + fn poll_read( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buffer: &mut ReadBuf<'_>, + ) -> Poll> { + if !self.readable { + return Poll::Ready(Err(FsError::PermissionDenied.into())); + } + let requested = buffer.remaining().min(self.backend.capacity()); + if requested == 0 { + return Poll::Ready(Ok(())); + } + let read = { + let output = buffer.initialize_unfilled_to(requested); + match self.backend.read(self.descriptor, self.cursor, output) { + Ok(read) => read, + Err(error) => return Poll::Ready(Err(error.into())), + } + }; + if read > requested { + return Poll::Ready(Err(FsError::InvalidData.into())); + } + buffer.advance(read); + self.cursor = self.cursor.saturating_add(read as u64); + Poll::Ready(Ok(())) + } +} + +impl AsyncWrite for SyncBridgeFile { + fn poll_write( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buffer: &[u8], + ) -> Poll> { + if !self.writable { + return Poll::Ready(Err(FsError::PermissionDenied.into())); + } + let length = buffer.len().min(self.backend.capacity()); + if length == 0 { + return Poll::Ready(Ok(0)); + } + let response = match self.backend.request( + OP_WRITE, + "", + &buffer[..length], + self.descriptor.into(), + self.cursor, + 0, + ) { + Ok(response) => response, + Err(error) => return Poll::Ready(Err(error.into())), + }; + let written = match usize::try_from(response.value0) { + Ok(written) if written <= length => written, + _ => return Poll::Ready(Err(FsError::InvalidData.into())), + }; + self.cursor = response.value1; + Poll::Ready(Ok(written)) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let result = self + .backend + .request(OP_FLUSH, "", &[], self.descriptor.into(), 0, 0) + .map(|_| ()) + .map_err(Into::into); + Poll::Ready(result) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncSeek for SyncBridgeFile { + fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { + let next = match position { + SeekFrom::Start(offset) => offset, + SeekFrom::Current(delta) => checked_offset(self.cursor, delta)?, + SeekFrom::End(delta) => return self.seek_from_end_sync(delta).map(|_| ()), + }; + self.cursor = next; + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(self.cursor)) + } +} + +fn update_cursor_from_end( + cursor: &mut u64, + size: virtual_fs::Result, + offset: i64, +) -> io::Result { + let next = checked_offset(size.map_err(io::Error::from)?, offset)?; + *cursor = next; + Ok(next) +} + +fn checked_offset(base: u64, delta: i64) -> io::Result { + if delta >= 0 { + base.checked_add(delta as u64) + } else { + base.checked_sub(delta.unsigned_abs()) + } + .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) +} + +#[cfg(test)] +mod seek_tests { + use super::*; + use wasm_bindgen_test::wasm_bindgen_test; + + #[wasm_bindgen_test] + fn seek_from_end_preserves_bridge_arithmetic_and_cursor() { + let mut cursor = 3; + assert_eq!(update_cursor_from_end(&mut cursor, Ok(6), 5).unwrap(), 11); + assert_eq!(cursor, 11); + assert_eq!(update_cursor_from_end(&mut cursor, Ok(6), -5).unwrap(), 1); + assert_eq!(cursor, 1); + } + + #[wasm_bindgen_test] + fn seek_from_end_preserves_cursor_on_arithmetic_error() { + let mut cursor = 3; + let error = update_cursor_from_end(&mut cursor, Ok(6), -7).unwrap_err(); + assert_eq!(error.kind(), io::ErrorKind::InvalidInput); + assert_eq!(cursor, 3); + } + + #[wasm_bindgen_test] + fn seek_from_end_propagates_provider_error_without_moving_cursor() { + let backend = js_sys::Object::new(); + let request = Function::new_with_args( + "opcode, _path, _payload, _arg0, _arg1, _flags", + r#" + switch (opcode) { + case 8: return [0, 0, 0, 0]; + case 14: return [6, 0, 0, 0]; + default: throw new Error(`unexpected filesystem opcode ${opcode}`); + } + "#, + ); + js_sys::Reflect::set(&backend, &JsValue::from_str("request"), request.as_ref()).unwrap(); + let mut file = SyncBridgeFile { + backend: Arc::new(Backend::new(backend.into(), 8 * 1024).unwrap()), + descriptor: 7, + readable: true, + writable: false, + cursor: 3, + }; + + let error = Pin::new(&mut file) + .start_seek(SeekFrom::End(0)) + .unwrap_err(); + assert_eq!(error.kind(), io::ErrorKind::PermissionDenied); + assert_eq!(file.cursor, 3); + } +} + +fn normalize_path(path: &Path) -> virtual_fs::Result { + let mut parts = Vec::new(); + for component in path.components() { + match component { + std::path::Component::Prefix(_) | std::path::Component::RootDir => {} + std::path::Component::CurDir => {} + std::path::Component::ParentDir => { + parts.pop().ok_or(FsError::InvalidInput)?; + } + std::path::Component::Normal(value) => { + let value = value.to_str().ok_or(FsError::InvalidInput)?; + if value.is_empty() || value.contains('\0') { + return Err(FsError::InvalidInput); + } + parts.push(value); + } + } + } + Ok(parts.join("/")) +} + +fn metadata(kind: u64, size: u64) -> virtual_fs::Result { + let ft = match kind { + TYPE_FILE => FileType::new_file(), + TYPE_DIRECTORY => FileType::new_dir(), + _ => return Err(FsError::InvalidData), + }; + Ok(Metadata { + ft, + len: size, + ..Default::default() + }) +} + +fn decode_directory_entries(parent: &str, bytes: &[u8]) -> virtual_fs::Result> { + let mut cursor = 0; + let count = read_u32(bytes, &mut cursor)? as usize; + let mut entries = Vec::with_capacity(count); + for _ in 0..count { + let kind = u64::from(read_u8(bytes, &mut cursor)?); + let length = read_u32(bytes, &mut cursor)? as usize; + let end = cursor.checked_add(length).ok_or(FsError::InvalidData)?; + let name = std::str::from_utf8(bytes.get(cursor..end).ok_or(FsError::InvalidData)?) + .map_err(|_| FsError::InvalidData)?; + if name.is_empty() || name.contains('/') || name.contains('\0') { + return Err(FsError::InvalidData); + } + cursor = end; + let path = if parent.is_empty() { + PathBuf::from("/").join(name) + } else { + PathBuf::from("/").join(parent).join(name) + }; + entries.push(DirEntry { + path, + metadata: metadata(kind, 0), + }); + } + if cursor != bytes.len() { + return Err(FsError::InvalidData); + } + Ok(entries) +} + +fn read_u8(bytes: &[u8], cursor: &mut usize) -> virtual_fs::Result { + let value = *bytes.get(*cursor).ok_or(FsError::InvalidData)?; + *cursor += 1; + Ok(value) +} + +fn read_u32(bytes: &[u8], cursor: &mut usize) -> virtual_fs::Result { + let end = cursor.checked_add(4).ok_or(FsError::InvalidData)?; + let raw: [u8; 4] = bytes + .get(*cursor..end) + .ok_or(FsError::InvalidData)? + .try_into() + .map_err(|_| FsError::InvalidData)?; + *cursor = end; + Ok(u32::from_le_bytes(raw)) +} + +fn decode_result(result: i32) -> virtual_fs::Result<()> { + let error = match result { + RESULT_OK => return Ok(()), + RESULT_NOT_FOUND => FsError::EntryNotFound, + RESULT_EXISTS => FsError::AlreadyExists, + RESULT_NOT_DIR => FsError::BaseNotDirectory, + RESULT_NOT_FILE => FsError::NotAFile, + RESULT_DIR_NOT_EMPTY => FsError::DirectoryNotEmpty, + RESULT_PERMISSION => FsError::PermissionDenied, + RESULT_INVALID => FsError::InvalidInput, + RESULT_STORAGE_FULL => FsError::StorageFull, + RESULT_UNSUPPORTED => FsError::Unsupported, + RESULT_TIMEOUT => FsError::TimedOut, + _ => FsError::IOError, + }; + Err(error) +} + +fn js_number(value: &JsValue) -> virtual_fs::Result { + value + .as_f64() + .filter(|value| value.is_finite() && *value >= 0.0 && value.fract() == 0.0) + .ok_or(FsError::InvalidData) +} + +fn js_i32(value: &JsValue) -> virtual_fs::Result { + let value = value.as_f64().ok_or(FsError::InvalidData)?; + if !value.is_finite() || value.fract() != 0.0 { + return Err(FsError::InvalidData); + } + i32::try_from(value as i64).map_err(|_| FsError::InvalidData) +} + +fn js_usize(value: &JsValue) -> virtual_fs::Result { + let value = js_number(value)?; + if value > usize::MAX as f64 { + return Err(FsError::InvalidData); + } + Ok(value as usize) +} + +fn js_u64(value: &JsValue) -> virtual_fs::Result { + let value = js_number(value)?; + if value > 9_007_199_254_740_991.0 { + return Err(FsError::InvalidData); + } + Ok(value as u64) +} diff --git a/src/wasix/browser-host/adapter/instance.rs b/src/wasix/browser-host/adapter/instance.rs new file mode 100644 index 000000000..61c08ff05 --- /dev/null +++ b/src/wasix/browser-host/adapter/instance.rs @@ -0,0 +1,268 @@ +use futures::{Stream, StreamExt, TryFutureExt, channel::oneshot::Receiver}; +use js_sys::Uint8Array; +use wasm_bindgen::{JsCast, JsValue, closure::Closure, prelude::wasm_bindgen}; +use wasmer_wasix::WasiRuntimeError; + +use super::utils::Error; + +/// A handle connected to a running WASIX program. +#[derive(Debug)] +#[wasm_bindgen] +pub struct Instance { + /// The standard input stream, if one wasn't provided when starting the + /// instance. + #[wasm_bindgen(getter_with_clone, readonly)] + pub stdin: Option, + /// The WASI program's standard output. + #[wasm_bindgen(getter_with_clone, readonly)] + pub stdout: web_sys::ReadableStream, + /// The WASI program's standard error. + #[wasm_bindgen(getter_with_clone, readonly)] + pub stderr: web_sys::ReadableStream, + pub(crate) exit: Receiver, + pub(crate) pool: std::sync::Arc, +} + +#[wasm_bindgen] +impl Instance { + /// Wait for the process to exit. + #[wasm_bindgen(js_name = "wait")] + pub async fn js_wait(self) -> Result { + let output = self.wait().await?; + Ok(output.into()) + } +} + +impl Instance { + #[tracing::instrument(skip_all)] + async fn wait(self) -> Result { + let Instance { + stdin, + stdout, + stderr, + exit, + pool, + } = self; + + if let Some(stdin) = stdin { + if stdin.locked() { + // The caller has already acquired a writer so it's their + // responsibility to close the stream. + } else { + match wasm_bindgen_futures::JsFuture::from(stdin.close()).await { + Ok(_) => { + tracing::debug!("Closed stdin"); + } + + Err(e) if e.has_type::() => { + tracing::debug!("Stdin was already closed by the user"); + } + Err(e) => { + return Err(Error::js(e)); + } + } + } + } + + tracing::debug!("Getting stdout"); + let mut stdout_buffer = Vec::new(); + let stdout_done = copy_to_buffer(super::streams::read_to_end(stdout), &mut stdout_buffer); + tracing::debug!("Getting stderr"); + let mut stderr_buffer = Vec::new(); + let stderr_done = copy_to_buffer(super::streams::read_to_end(stderr), &mut stderr_buffer); + + // Note: this relies on the underlying instance closing stdout and + // stderr when it exits. Failing to do this will block forever. + let completed = futures::try_join!(stdout_done, stderr_done, exit.map_err(Error::from)); + let drained = pool.close_and_wait().await; + let (_, _, ExitCondition(code)) = completed?; + drained?; + + let output = Output { + code, + ok: code == 0, + stdout: stdout_buffer, + stderr: stderr_buffer, + }; + + Ok(output) + } +} + +async fn copy_to_buffer( + stream: impl Stream, Error>>, + buffer: &mut Vec, +) -> Result<(), Error> { + futures::pin_mut!(stream); + while let Some(chunk) = stream.next().await { + buffer.extend(chunk?); + } + + Ok(()) +} + +#[derive(Debug)] +pub(crate) struct ExitCondition(i32); + +impl ExitCondition { + pub(crate) fn from_result(result: Result<(), anyhow::Error>) -> Self { + let err = match result { + Ok(_) => return ExitCondition(0), + Err(e) => e, + }; + + // looks like some sort of error occurred. + let error_code = err + .chain() + .find_map(|e| e.downcast_ref::()) + .and_then(|runtime_error| runtime_error.as_exit_code()); + + match error_code { + Some(code) => ExitCondition(code.raw()), + None => { + tracing::debug!(error = &*err, "Process exited unexpectedly"); + ExitCondition(1) + } + } + } +} + +#[derive(Debug, Clone, PartialEq)] +struct Output { + code: i32, + ok: bool, + stdout: Vec, + stderr: Vec, +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Output")] + pub type JsOutput; +} + +impl From for JsOutput { + fn from(value: Output) -> Self { + let Output { + code, + ok, + stdout, + stderr, + } = value; + + let output = js_sys::Object::new(); + let _ = js_sys::Reflect::set(&output, &JsValue::from_str("code"), &JsValue::from(code)); + let _ = js_sys::Reflect::set(&output, &JsValue::from_str("ok"), &JsValue::from(ok)); + let _ = js_sys::Reflect::set( + &output, + &JsValue::from_str("stdoutBytes"), + &Uint8Array::from(stdout.as_slice()), + ); + js_sys::Object::define_property( + &output, + &JsValue::from_str("stdout"), + &lazily_decoded_string_property(stdout), + ); + let _ = js_sys::Reflect::set( + &output, + &JsValue::from_str("stderrBytes"), + &Uint8Array::from(stderr.as_slice()), + ); + js_sys::Object::define_property( + &output, + &JsValue::from_str("stderr"), + &lazily_decoded_string_property(stderr), + ); + + output.unchecked_into() + } +} + +fn lazily_decoded_string_property(binary: Vec) -> js_sys::Object { + let contents: once_cell::unsync::Lazy = + once_cell::unsync::Lazy::new(move || { + let utf8 = String::from_utf8_lossy(&binary); + js_sys::JsString::from(utf8.as_ref()) + }); + + let property = js_sys::Object::new(); + let getter: Closure js_sys::JsString> = + Closure::new(move || js_sys::JsString::clone(&contents)); + js_sys::Reflect::set(&property, &JsValue::from("get"), &getter.into_js_value()).unwrap(); + js_sys::Reflect::set(&property, &JsValue::from("enumerable"), &JsValue::TRUE).unwrap(); + + property +} + +#[wasm_bindgen(typescript_custom_section)] +const OUTPUT_TYPE_DEFINITION: &'static str = r#" +export type Output = { + /* The program's exit code. */ + code: number; + /* Did the program exit successfully? */ + ok: boolean; + /* The contents of the program's stdout stream. */ + stdoutBytes: Uint8Array; + /* The program's stdout stream, decoded as UTF-8. */ + readonly stdout: string; + /* The contents of the program's stderr stream. */ + stderrBytes: Uint8Array; + /* The program's stderr stream, decoded as UTF-8. */ + readonly stderr: string; +} +"#; + +#[cfg(test)] +mod tests { + use futures::channel::oneshot; + use virtual_fs::AsyncReadExt; + use virtual_fs::AsyncWriteExt; + use wasm_bindgen_test::wasm_bindgen_test; + + use super::super::streams; + use super::*; + + #[wasm_bindgen_test] + async fn read_stdout_and_stderr_when_waiting_for_completion() { + let (mut stdin, stdin_stream) = streams::input_pipe(); + let (mut stdout, stdout_stream) = streams::output_pipe(); + let (mut stderr, stderr_stream) = streams::output_pipe(); + let (sender, exit) = oneshot::channel(); + let instance = Instance { + stdin: Some(stdin_stream), + stdout: stdout_stream, + stderr: stderr_stream, + exit, + pool: std::sync::Arc::new(crate::tasks::ThreadPool::new(None)), + }; + dbg!(&instance); + + // First, let's pretend to be a WASIX process doing stuff in the background + stdout.write_all(b"stdout").await.unwrap(); + stdout.flush().await.unwrap(); + stderr.write_all(b"stderr").await.unwrap(); + stderr.flush().await.unwrap(); + + // Now, we pretend the WASIX process exited + stdout.close(); + stderr.close(); + sender.send(ExitCondition(42)).unwrap(); + + // and wait for the result + let output = instance.wait().await.unwrap(); + + assert_eq!( + output, + Output { + code: 42, + ok: false, + stdout: b"stdout".to_vec(), + stderr: b"stderr".to_vec() + } + ); + // Reading from stdin should now result in an EOF because it's closed + let mut buffer = Vec::new(); + let bytes_read = stdin.read_to_end(&mut buffer).await.unwrap(); + assert_eq!(bytes_read, 0); + } +} diff --git a/src/wasix/browser-host/adapter/mod.rs b/src/wasix/browser-host/adapter/mod.rs new file mode 100644 index 000000000..0d4239aa4 --- /dev/null +++ b/src/wasix/browser-host/adapter/mod.rs @@ -0,0 +1,22 @@ +//! Browser-only adapter for Oliphaunt's embedded PostgreSQL and frontend tools. +//! The SDK supplies WASIX and its worker scheduler; SQL never goes through a +//! command runner or a virtual socket. +mod caller_realm; +mod clock; +mod fs; +mod instance; +mod options; +mod postgres_direct; +mod protocol_contract; +mod run; +mod runtime; +mod streams; +mod tool_direct; +mod utils; +pub use fs::{Directory, DirectoryInit}; +pub use instance::Instance; +pub use options::RunOptions; +pub use postgres_direct::{OliphauntDirectInstance, instantiate_oliphaunt_direct}; +pub use run::run_wasix; +pub use tool_direct::{OliphauntPreparedTool, prepare_oliphaunt_tool, run_oliphaunt_tool_direct}; +pub use utils::StringOrBytes; diff --git a/src/wasix/browser-host/adapter/options.rs b/src/wasix/browser-host/adapter/options.rs new file mode 100644 index 000000000..8cf52a78a --- /dev/null +++ b/src/wasix/browser-host/adapter/options.rs @@ -0,0 +1,273 @@ +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::Context; +use js_sys::Array; +use virtual_fs::{TmpFileSystem, random_file::RandomFile}; +use wasm_bindgen::{JsCast, JsValue, convert::TryFromJsValue, prelude::wasm_bindgen}; +use wasmer_wasix::{ + WasiEnvBuilder, + capabilities::{WasiFdClosePolicy, WasiGuestExecutionMode, WasiHostPolicy}, +}; + +use super::{Directory, DirectoryInit, StringOrBytes, utils::Error}; + +const OLIPHAUNT_PROTOCOL_DEVICE: &str = "/dev/oliphaunt-pgwire"; +#[wasm_bindgen(typescript_custom_section)] +const TYPE_DEFINITIONS: &str = r#" +type CommonOptions = { args?: string[]; env?: Record; stdin?: string | Uint8Array; mount?: Record; cwd?: string; }; +export type RunOptions = CommonOptions & { program?: string; moduleBytes?: Uint8Array; }; +"#; +#[wasm_bindgen] +extern "C" { + type OptionalDirectories; + #[wasm_bindgen(typescript_type = "CommonOptions", extends = js_sys::Object)] + pub type CommonOptions; + + #[wasm_bindgen(method, getter)] + fn cwd(this: &CommonOptions) -> Option; + + #[wasm_bindgen(method, getter)] + fn args(this: &CommonOptions) -> Option; + + #[wasm_bindgen(method, getter)] + fn env(this: &CommonOptions) -> JsValue; + + #[wasm_bindgen(method, getter)] + fn stdin(this: &CommonOptions) -> Option; + + #[wasm_bindgen(method, getter)] + fn mount(this: &CommonOptions) -> OptionalDirectories; +} + +impl CommonOptions { + pub(crate) fn parse_args(&self) -> Result, Error> { + match self.args() { + Some(args) => super::utils::js_string_array(args), + None => Ok(Vec::new()), + } + } + + pub(crate) fn parse_cwd(&self) -> Result, Error> { + match self.cwd() { + Some(cwd) => Ok(Some(cwd)), + None => Ok(None), + } + } + + pub(crate) fn parse_env(&self) -> Result, Error> { + match self.env().dyn_ref() { + Some(env) => { + let vars = super::utils::js_record_of_strings(env)?; + Ok(vars.into_iter().collect()) + } + None => Ok(BTreeMap::new()), + } + } + + pub(crate) fn read_stdin(&self) -> Option> { + self.stdin().map(|s| s.as_bytes()) + } + + pub(crate) fn mounted_directories(&self) -> Result, Error> { + let Ok(obj) = self.mount().dyn_into::() else { + return Ok(Vec::new()); + }; + + let entries: BTreeMap = super::utils::object_entries(&obj)?; + let mut mounted_directories = Vec::new(); + + for (key, value) in &entries { + let key = String::from(key.clone()); + + // Note: the value is a `Directory | DirectoryInit` + + let clone = js_sys::Reflect::get(value, &JsValue::from_str("__cloneForMount")) + .ok() + .and_then(|method| method.dyn_into::().ok()) + .map(|method| method.call0(value)) + .transpose() + .map_err(Error::js)?; + let directory = clone.and_then(|clone| Directory::try_from_js_value(clone).ok()); + let value = if let Some(dir) = directory { + dir + } else if value.is_object() { + // looks like we were given parameters for initializing a + // Directory and need to call the constructor ourselves + let init: &DirectoryInit = value.unchecked_ref(); + Directory::new(Some(init.clone()))? + } else { + unreachable!(); + }; + mounted_directories.push((key, value)); + } + + Ok(mounted_directories) + } +} + +impl Default for CommonOptions { + fn default() -> Self { + // Note: all fields are optional, so it's fine to use an empty object. + CommonOptions { + obj: js_sys::Object::new(), + } + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "RunOptions", extends = CommonOptions)] + #[derive(Default)] + pub type RunOptions; + + #[wasm_bindgen(method, getter)] + pub(crate) fn program(this: &RunOptions) -> JsValue; + + #[wasm_bindgen(method, getter, js_name = moduleBytes)] + pub(crate) fn module_bytes(this: &RunOptions) -> Option; + +} + +impl RunOptions { + pub(crate) fn configure_direct_builder( + &self, + builder: &mut WasiEnvBuilder, + stdin: Box, + stdout: Box, + stderr: Box, + ) -> Result<(), Error> { + self.configure_common_builder(builder, None)?; + builder.set_host_policy(WasiHostPolicy::new( + WasiGuestExecutionMode::SingleProgram, + WasiFdClosePolicy::WritesCompleteSynchronously, + )); + + builder.set_stdin(stdin); + builder.set_stdout(stdout); + builder.set_stderr(stderr); + + Ok(()) + } + + /// Propagate any provided options to the [`WasiEnvBuilder`], returning + /// streams that can be used for stdin/stdout/stderr. + pub(crate) fn configure_builder( + &self, + builder: &mut WasiEnvBuilder, + ) -> Result< + ( + Option, + web_sys::ReadableStream, + web_sys::ReadableStream, + ), + Error, + > { + self.configure_common_builder(builder, None)?; + Ok(self.configure_stdio(builder)) + } + + pub(crate) fn configure_tool_direct_builder( + &self, + builder: &mut WasiEnvBuilder, + protocol: Box, + stdout: Box, + stderr: Box, + ) -> Result<(), Error> { + self.configure_common_builder(builder, Some(protocol))?; + builder.set_host_policy(WasiHostPolicy::new( + WasiGuestExecutionMode::SingleProgram, + WasiFdClosePolicy::WritesCompleteSynchronously, + )); + builder.add_env("OLIPHAUNT_DIRECT_PGWIRE", OLIPHAUNT_PROTOCOL_DEVICE); + match self.read_stdin() { + Some(input) => builder.set_stdin(Box::new(virtual_fs::StaticFile::new(input))), + None => builder.set_stdin(Box::::default()), + }; + builder.set_stdout(stdout); + builder.set_stderr(stderr); + + Ok(()) + } + + fn configure_stdio( + &self, + builder: &mut WasiEnvBuilder, + ) -> ( + Option, + web_sys::ReadableStream, + web_sys::ReadableStream, + ) { + let stdin = match self.read_stdin() { + Some(stdin) => { + builder.set_stdin(Box::new(virtual_fs::StaticFile::new(stdin))); + None + } + None => { + let (file, stream) = super::streams::input_pipe(); + builder.set_stdin(Box::new(file)); + Some(stream) + } + }; + let (stdout_file, stdout) = super::streams::output_pipe(); + builder.set_stdout(Box::new(stdout_file)); + let (stderr_file, stderr) = super::streams::output_pipe(); + builder.set_stderr(Box::new(stderr_file)); + (stdin, stdout, stderr) + } + + fn configure_common_builder( + &self, + builder: &mut WasiEnvBuilder, + protocol_file: Option>, + ) -> Result<(), Error> { + for arg in self.parse_args()? { + builder.add_arg(arg); + } + + for (key, value) in self.parse_env()? { + builder.add_env(key, value); + } + + if let Some(cwd) = self.parse_cwd()? { + builder.set_current_dir(cwd); + } + let fs = self.filesystem()?; + if let Some(protocol_file) = protocol_file { + fs.new_open_options_ext() + .insert_device_file(PathBuf::from(OLIPHAUNT_PROTOCOL_DEVICE), protocol_file) + .with_context(|| format!("Unable to create {OLIPHAUNT_PROTOCOL_DEVICE}"))?; + } + builder.set_fs(Arc::new(fs) as Arc); + builder.add_preopen_dir("/")?; + + Ok(()) + } + + pub(crate) fn filesystem(&self) -> Result { + let root = TmpFileSystem::new(); + for path in ["/dev", "/dev/shm"] { + virtual_fs::FileSystem::create_dir(&root, Path::new(path)) + .with_context(|| format!("Unable to create \"{path}\""))?; + } + root.new_open_options_ext() + .insert_device_file(PathBuf::from("/dev/urandom"), Box::::default()) + .context("Unable to create /dev/urandom")?; + + for (dest, fs) in self.mounted_directories()? { + tracing::trace!(%dest, ?fs, "Mounting directory"); + + let fs = Arc::new(fs) as Arc; + root.new_open_options_ext() + .mount(dest.as_str().into(), &fs, "/".into()) + .with_context(|| format!("Unable to mount to \"{dest}\""))?; + } + + tracing::trace!(?root, "Initialized the filesystem"); + + Ok(root) + } +} diff --git a/src/wasix/browser-host/adapter/postgres_direct.rs b/src/wasix/browser-host/adapter/postgres_direct.rs new file mode 100644 index 000000000..b4727e6d7 --- /dev/null +++ b/src/wasix/browser-host/adapter/postgres_direct.rs @@ -0,0 +1,1570 @@ +use std::{ + cell::RefCell, + collections::VecDeque, + io::{self, SeekFrom}, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context as TaskContext, Poll}, +}; + +use anyhow::{Context, ensure}; +use js_sys::{Function, Uint8Array, WebAssembly}; +use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; +use virtual_fs::VirtualFile; +use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen}; +use wasmer::{Instance as WasmerInstance, Store, TypedFunction, Value, WasmTypeList}; +use wasmer_wasix::{ + Runtime as _, WasiEnvBuilder, WasiError, WasiFunctionEnv, oliphaunt_direct_memory, +}; + +use super::{RunOptions, caller_realm::CallerRealmTaskManager, runtime::Runtime, utils::Error}; + +const DEFAULT_PROGRAM_NAME: &str = "/bin/postgres"; +const OLIPHAUNT_EXIT_STARTUP_REJECTED: i32 = 98; +const OLIPHAUNT_EXIT_ALIVE: i32 = 99; +const STDERR_LIMIT_BYTES: usize = 16 * 1024; +use super::protocol_contract::PROTOCOL_BUFFERED_INPUT_STREAMED_OUTPUT; +use super::protocol_contract::{ + PROTOCOL_BUFFERED, PROTOCOL_CALLBACK_CHUNK_BYTES as PROTOCOL_CHUNK_BYTES, PROTOCOL_HYBRID, +}; +const PROCESS_STARTUP_OK: i32 = 0; +const PROCESS_STARTUP_ERROR: i32 = -1; +const STARTUP_OUTCOME_VERSION: u32 = 1; +const STARTUP_OUTCOME_DESCRIPTOR_BYTES: u32 = 32; +const STARTUP_OUTCOME_PENDING: u32 = 0; +const STARTUP_OUTCOME_REJECTED: u32 = 1; + +thread_local! { + static PROTOCOL_CALLBACK: RefCell> = const { RefCell::new(None) }; + static PROTOCOL_INPUT_CALLBACK: RefCell> = const { RefCell::new(None) }; +} + +#[derive(Clone, Debug, Default)] +struct ProtocolStdin; + +impl ProtocolStdin { + fn begin(&self, callback: Function) -> anyhow::Result<()> { + PROTOCOL_INPUT_CALLBACK.with(|active| { + let mut active = active.borrow_mut(); + ensure!( + active.is_none(), + "protocol input callback is already active" + ); + *active = Some(callback); + Ok(()) + }) + } + + fn finish(&self) { + PROTOCOL_INPUT_CALLBACK.with(|active| *active.borrow_mut() = None); + } +} + +impl AsyncRead for ProtocolStdin { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + buffer: &mut ReadBuf<'_>, + ) -> Poll> { + let callback = match PROTOCOL_INPUT_CALLBACK.with(|active| active.borrow().clone()) { + Some(callback) => callback, + None => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "protocol input callback is inactive", + ))); + } + }; + let requested = buffer.remaining().min(PROTOCOL_CHUNK_BYTES); + if requested == 0 { + return Poll::Ready(Ok(())); + } + let value = match callback.call1(&JsValue::UNDEFINED, &JsValue::from(requested as u32)) { + Ok(value) => value, + Err(error) => { + return Poll::Ready(Err(io::Error::other(format!( + "protocol input callback failed: {error:?}" + )))); + } + }; + let input = match value.dyn_into::() { + Ok(input) => input, + Err(_) => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "protocol input callback must return a Uint8Array", + ))); + } + }; + let length = input.length() as usize; + if length > requested { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "protocol input callback exceeded the requested byte count", + ))); + } + if length > 0 { + input.copy_to(buffer.initialize_unfilled_to(length)); + buffer.advance(length); + } + Poll::Ready(Ok(())) + } +} + +impl AsyncWrite for ProtocolStdin { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + _input: &[u8], + ) -> Poll> { + Poll::Ready(Err(io::Error::new( + io::ErrorKind::Unsupported, + "protocol stdin is read-only", + ))) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncSeek for ProtocolStdin { + fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } +} + +impl VirtualFile for ProtocolStdin { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } + fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { + Ok(()) + } + fn unlink(&mut self) -> virtual_fs::Result<()> { + Ok(()) + } + fn get_special_fd(&self) -> Option { + Some(0) + } + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) + } + fn poll_write_ready( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + ) -> Poll> { + Poll::Ready(Ok(0)) + } +} + +#[derive(Clone, Debug)] +enum ProtocolStdoutFailure { + Callback(String), + LockPoisoned, +} + +impl ProtocolStdoutFailure { + fn message(&self) -> String { + match self { + Self::Callback(failure) => format!("protocol stream callback failed: {failure}"), + Self::LockPoisoned => "protocol stream state lock was poisoned".to_string(), + } + } + + fn into_anyhow(self) -> anyhow::Error { + anyhow::anyhow!(self.message()) + } + + fn into_io(self) -> io::Error { + io::Error::other(self.message()) + } +} + +#[derive(Debug, Default)] +struct ProtocolStdoutState { + failure: Option, +} + +impl ProtocolStdoutState { + fn poison_lock(&mut self) -> ProtocolStdoutFailure { + self.failure = Some(ProtocolStdoutFailure::LockPoisoned); + ProtocolStdoutFailure::LockPoisoned + } + + fn record_callback_failure(&mut self, failure: String) -> ProtocolStdoutFailure { + match &self.failure { + Some(failure) => failure.clone(), + None => { + let failure = ProtocolStdoutFailure::Callback(failure); + self.failure = Some(failure.clone()); + failure + } + } + } +} + +#[derive(Clone, Debug, Default)] +struct ProtocolStdout { + state: Arc>, +} + +impl ProtocolStdout { + fn begin(&self, callback: Function) -> anyhow::Result<()> { + PROTOCOL_CALLBACK.with(|active| { + ensure!( + active.borrow().is_none(), + "protocol stream callback is already active" + ); + Ok(()) + })?; + self.with_state(|state| state.failure = None) + .map_err(ProtocolStdoutFailure::into_anyhow)?; + PROTOCOL_CALLBACK.with(|active| *active.borrow_mut() = Some(callback)); + Ok(()) + } + + fn finish(&self) -> anyhow::Result<()> { + PROTOCOL_CALLBACK.with(|active| *active.borrow_mut() = None); + let failure = match self.with_state(|state| state.failure.clone()) { + Ok(failure) => failure, + Err(failure) => Some(failure), + }; + match failure { + Some(failure) => Err(failure.into_anyhow()), + None => Ok(()), + } + } + + fn with_state( + &self, + operation: impl FnOnce(&mut ProtocolStdoutState) -> T, + ) -> Result { + match self.state.lock() { + Ok(mut state) => Ok(operation(&mut state)), + Err(poisoned) => { + let mut state = poisoned.into_inner(); + Err(state.poison_lock()) + } + } + } + + fn deliver(&self, input: &[u8]) -> io::Result<()> { + let failure = match self.with_state(|state| state.failure.clone()) { + Ok(failure) => failure, + Err(failure) => Some(failure), + }; + if let Some(failure) = failure { + return Err(failure.into_io()); + } + let callback = PROTOCOL_CALLBACK + .with(|active| active.borrow().clone()) + .ok_or_else(|| { + io::Error::new(io::ErrorKind::BrokenPipe, "protocol callback is inactive") + })?; + for input in input.chunks(PROTOCOL_CHUNK_BYTES) { + let chunk = Uint8Array::from(input); + if let Err(error) = callback.call1(&JsValue::UNDEFINED, &chunk) { + let failure = match self + .with_state(|state| state.record_callback_failure(format!("{error:?}"))) + { + Ok(failure) | Err(failure) => failure, + }; + return Err(failure.into_io()); + } + } + Ok(()) + } +} + +impl AsyncWrite for ProtocolStdout { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + input: &[u8], + ) -> Poll> { + match self.deliver(input) { + Ok(()) => Poll::Ready(Ok(input.len())), + Err(error) => Poll::Ready(Err(error)), + } + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncRead for ProtocolStdout { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + _buffer: &mut ReadBuf<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncSeek for ProtocolStdout { + fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } +} + +impl VirtualFile for ProtocolStdout { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } + fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { + Ok(()) + } + fn unlink(&mut self) -> virtual_fs::Result<()> { + Ok(()) + } + fn get_special_fd(&self) -> Option { + Some(1) + } + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } + fn poll_write_ready( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + ) -> Poll> { + Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) + } +} + +#[derive(Clone, Debug, Default)] +struct BoundedStderr { + bytes: Arc>>, +} + +impl BoundedStderr { + fn append(&self, input: &[u8]) { + let mut bytes = self.bytes.lock().expect("bounded stderr lock poisoned"); + if input.len() >= STDERR_LIMIT_BYTES { + bytes.clear(); + bytes.extend(&input[input.len() - STDERR_LIMIT_BYTES..]); + return; + } + let overflow = bytes + .len() + .saturating_add(input.len()) + .saturating_sub(STDERR_LIMIT_BYTES); + bytes.drain(..overflow); + bytes.extend(input); + } + + fn attach(&self, error: anyhow::Error) -> anyhow::Error { + let mut bytes = self.bytes.lock().expect("bounded stderr lock poisoned"); + if bytes.is_empty() { + return error; + } + let stderr = String::from_utf8_lossy(bytes.make_contiguous()); + error.context(format!("WASIX stderr (last 16 KiB):\n{stderr}")) + } +} + +impl AsyncWrite for BoundedStderr { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + input: &[u8], + ) -> Poll> { + self.append(input); + Poll::Ready(Ok(input.len())) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncRead for BoundedStderr { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + _buffer: &mut ReadBuf<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncSeek for BoundedStderr { + fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } +} + +impl VirtualFile for BoundedStderr { + fn last_accessed(&self) -> u64 { + 0 + } + + fn last_modified(&self) -> u64 { + 0 + } + + fn created_time(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + self.bytes + .lock() + .expect("bounded stderr lock poisoned") + .len() as u64 + } + + fn set_len(&mut self, new_size: u64) -> virtual_fs::Result<()> { + let target = usize::try_from(new_size) + .unwrap_or(usize::MAX) + .min(STDERR_LIMIT_BYTES); + let mut bytes = self.bytes.lock().expect("bounded stderr lock poisoned"); + while bytes.len() > target { + bytes.pop_front(); + } + bytes.resize(target, 0); + Ok(()) + } + + fn unlink(&mut self) -> virtual_fs::Result<()> { + self.bytes + .lock() + .expect("bounded stderr lock poisoned") + .clear(); + Ok(()) + } + + fn get_special_fd(&self) -> Option { + Some(2) + } + + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } + + fn poll_write_ready( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + ) -> Poll> { + Poll::Ready(Ok(8192)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MainLoopOutcome { + Processed, + Recovered, + InputEnded, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DirectInstanceState { + Open, + StartupPhase, + GuestPhase, + StartupRejected, + Closing, + Poisoned, + Closed, +} + +enum BackendStartOutcome { + Alive, + Rejected(Vec), +} + +enum DirectStartupOutcome { + Processed { output: Uint8Array, status: i32 }, + Rejected(Vec), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct StartupOutcomeDescriptor { + kind: u32, + data_pointer: u64, + data_length: u64, +} + +impl MainLoopOutcome { + fn from_i32(value: i32) -> Option { + match value { + 0 => Some(Self::Processed), + 1 => Some(Self::Recovered), + 2 => Some(Self::InputEnded), + _ => None, + } + } +} + +#[wasm_bindgen(inline_js = r#" +export function oliphauntCopyToGuest(memory, pointer, input) { + new Uint8Array(memory.buffer, pointer, input.byteLength).set(input); +} + +export function oliphauntCopyFromGuest(memory, pointer, length) { + // slice() deliberately returns owned protocol bytes. A view would become + // invalid or mutable as soon as PostgreSQL reuses or grows guest memory. + return new Uint8Array(memory.buffer, pointer, length).slice(); +} + +"#)] +extern "C" { + #[wasm_bindgen(catch, js_name = oliphauntCopyToGuest)] + fn oliphaunt_copy_to_guest( + memory: &JsValue, + pointer: u32, + input: &Uint8Array, + ) -> Result<(), JsValue>; + #[wasm_bindgen(catch, js_name = oliphauntCopyFromGuest)] + fn oliphaunt_copy_from_guest( + memory: &JsValue, + pointer: u32, + length: u32, + ) -> Result; +} +/// Instantiate the integrated Oliphaunt/PostgreSQL guest in this JS realm. +/// +/// The returned driver is synchronous by design: every guest export runs on +/// the caller's stack, and this code never constructs a Worker or ThreadPool. +#[wasm_bindgen(js_name = instantiateOliphauntDirect)] +pub async fn instantiate_oliphaunt_direct( + module: WebAssembly::Module, + module_bytes: Uint8Array, + options: RunOptions, +) -> Result { + let stderr = BoundedStderr::default(); + let protocol_stdin = ProtocolStdin; + let protocol_stdout = ProtocolStdout::default(); + OliphauntDirectInstance::instantiate( + module, + module_bytes, + options, + protocol_stdin, + protocol_stdout, + stderr.clone(), + ) + .await + .map_err(|error| Error::from(stderr.attach(error.into_anyhow()))) +} + +#[wasm_bindgen] +pub struct OliphauntDirectInstance { + // Runtime and instance must outlive every function handle and the Store. + _runtime: Arc, + store: Store, + _instance: WasmerInstance, + _env: WasiFunctionEnv, + protocol_stdin: ProtocolStdin, + protocol_stdout: ProtocolStdout, + stderr: BoundedStderr, + guest_memory: JsValue, + input_reset: TypedFunction<(), i32>, + input_reserve: TypedFunction, + input_commit: TypedFunction, + input_available: TypedFunction<(), i32>, + output_reset: TypedFunction<(), i32>, + output_len: TypedFunction<(), i32>, + output_data: TypedFunction<(), i32>, + set_protocol_transport: TypedFunction, + set_active: TypedFunction, + prepare_trusted_embedded_session: TypedFunction<(), i32>, + startup_outcome: TypedFunction<(), i32>, + wasi_start: TypedFunction<(), ()>, + start_oliphaunt: TypedFunction<(), ()>, + run_atexit_funcs: Option>, + get_port: TypedFunction<(), i32>, + process_startup: TypedFunction<(i32, i32, i32), i32>, + send_conn_data: TypedFunction<(), ()>, + pq_flush: TypedFunction<(), i32>, + pq_buffer_remaining_data: TypedFunction<(), i32>, + main_loop: TypedFunction<(), i32>, + send_ready: TypedFunction<(), ()>, + backend_started: bool, + protocol_started: bool, + state: DirectInstanceState, +} + +#[wasm_bindgen] +impl OliphauntDirectInstance { + /// Start PostgreSQL and process one frontend startup packet. + #[wasm_bindgen(js_name = startup)] + pub fn startup(&mut self, packet: Uint8Array) -> Result { + match self.startup_inner(&packet) { + Ok(DirectStartupOutcome::Processed { output, .. }) => Ok(output), + Ok(DirectStartupOutcome::Rejected(protocol)) => Err(self.startup_rejection_error( + anyhow::anyhow!("_start rejected Oliphaunt single-user backend"), + protocol, + )), + Err(error) if self.state == DirectInstanceState::StartupPhase => { + self.poison_guest_phase(); + Err(Error::from(self.stderr.attach(error))) + } + Err(error) => Err(Error::from(self.stderr.attach(error))), + } + } + + /// Execute raw PostgreSQL frontend-protocol bytes synchronously. + #[wasm_bindgen(js_name = execProtocolRaw)] + pub fn exec_protocol_raw(&mut self, input: Uint8Array) -> Result { + if let Err(error) = self.ensure_protocol_started() { + return Err(Error::from(self.stderr.attach(error))); + } + let result = self.run_guest_phase("buffered protocol exchange", |direct| { + let previous = direct + .set_protocol_transport + .call(&mut direct.store, PROTOCOL_BUFFERED) + .context("enable buffered protocol transport")?; + ensure!( + previous == PROTOCOL_BUFFERED, + "enable buffered protocol transport returned previous mode {previous}, expected {PROTOCOL_BUFFERED}" + ); + direct.exec_protocol_raw_inner(&input, false) + }); + match result { + Ok(output) => Ok(output), + Err(error) => Err(Error::from(self.stderr.attach(error))), + } + } + + /// Execute raw PostgreSQL protocol bytes and synchronously deliver owned chunks. + #[wasm_bindgen(js_name = execProtocolStream)] + pub fn exec_protocol_stream( + &mut self, + input: Uint8Array, + on_chunk: Function, + ) -> Result { + if let Err(error) = self.ensure_protocol_started() { + return Err(Error::from(self.stderr.attach(error))); + } + let result = self.run_guest_phase("streaming protocol exchange", |direct| { + direct.exec_protocol_stream_inner(&input, on_chunk) + }); + match result { + Ok(()) => Ok(0), + Err(error) => Err(Error::from(self.stderr.attach(error))), + } + } + + /// Execute a protocol exchange with synchronous full-duplex callbacks. + #[wasm_bindgen(js_name = execProtocolDuplex)] + pub fn exec_protocol_duplex( + &mut self, + input: Uint8Array, + on_read: Function, + on_chunk: Function, + ) -> Result<(), Error> { + if let Err(error) = self.ensure_protocol_started() { + return Err(Error::from(self.stderr.attach(error))); + } + let result = self.run_guest_phase("duplex protocol exchange", |direct| { + direct.exec_protocol_duplex_inner(&input, on_read, on_chunk) + }); + match result { + Ok(()) => Ok(()), + Err(error) => Err(Error::from(self.stderr.attach(error))), + } + } + + /// Shut down the embedded lifecycle. This does not consume the JS object. + pub fn close(&mut self) -> Result<(), Error> { + match self.close_inner() { + Ok(()) => Ok(()), + Err(error) => Err(Error::from(self.stderr.attach(error))), + } + } +} + +impl OliphauntDirectInstance { + fn exec_protocol_duplex_inner( + &mut self, + payload: &Uint8Array, + on_read: Function, + on_chunk: Function, + ) -> anyhow::Result<()> { + self.protocol_stdin.begin(on_read)?; + let result = + self.exec_protocol_callback_inner(payload, on_chunk.clone(), PROTOCOL_HYBRID, true); + self.protocol_stdin.finish(); + let output = result?; + let length = output.length() as usize; + for start in (0..length).step_by(PROTOCOL_CHUNK_BYTES) { + let end = start.saturating_add(PROTOCOL_CHUNK_BYTES).min(length); + let chunk = output.slice(start as u32, end as u32); + on_chunk + .call1(&JsValue::UNDEFINED, &chunk) + .map_err(|error| anyhow::anyhow!("protocol stream callback failed: {error:?}"))?; + } + Ok(()) + } + + fn exec_protocol_stream_inner( + &mut self, + payload: &Uint8Array, + on_chunk: Function, + ) -> anyhow::Result<()> { + let output = self.exec_protocol_callback_inner( + payload, + on_chunk, + PROTOCOL_BUFFERED_INPUT_STREAMED_OUTPUT, + false, + )?; + ensure!( + output.length() == 0, + "buffered-input/streamed-output transport left {} buffered protocol bytes", + output.length() + ); + Ok(()) + } + + fn exec_protocol_callback_inner( + &mut self, + payload: &Uint8Array, + on_chunk: Function, + transport: i32, + streaming_input: bool, + ) -> anyhow::Result { + self.protocol_stdout.begin(on_chunk.clone())?; + let previous = match self + .set_protocol_transport + .call(&mut self.store, transport) + .with_context(|| format!("enable protocol transport mode {transport}")) + { + Ok(previous) if previous == PROTOCOL_BUFFERED => previous, + Ok(previous) => { + let _ = self.protocol_stdout.finish(); + anyhow::bail!( + "enable protocol transport mode {transport} returned previous mode {previous}, expected {PROTOCOL_BUFFERED}" + ); + } + Err(error) => { + let _ = self.protocol_stdout.finish(); + return Err(error); + } + }; + let execution = self.exec_protocol_raw_inner(payload, streaming_input); + let restore = if execution.is_err() { + // Once any guest-facing step fails, guest state is unknown. The + // outer phase boundary will poison the instance; do not re-enter + // the guest merely to restore the transport selector. + Ok(()) + } else { + self.set_protocol_transport + .call(&mut self.store, previous) + .with_context(|| format!("restore protocol transport from mode {transport}")) + .and_then(|replaced| { + ensure!( + replaced == transport, + "restore protocol transport replaced mode {replaced}, expected {transport}" + ); + Ok(()) + }) + }; + let callback = self.protocol_stdout.finish(); + + callback?; + let output = execution?; + restore?; + Ok(output) + } + + fn startup_inner(&mut self, packet: &Uint8Array) -> anyhow::Result { + match self.state { + DirectInstanceState::Open => {} + DirectInstanceState::StartupRejected => { + anyhow::bail!( + "Oliphaunt direct startup was already rejected; the direct instance cannot be reused" + ) + } + _ => self.ensure_open()?, + } + ensure!( + !self.protocol_started, + "Oliphaunt direct startup already completed" + ); + self.state = DirectInstanceState::StartupPhase; + let outcome = self.startup_guest_inner(packet)?; + match outcome { + DirectStartupOutcome::Processed { status, .. } => { + self.state = if status == PROCESS_STARTUP_ERROR { + DirectInstanceState::StartupRejected + } else { + DirectInstanceState::Open + }; + } + DirectStartupOutcome::Rejected(_) => self.poison_guest_phase(), + } + Ok(outcome) + } + + fn startup_guest_inner(&mut self, packet: &Uint8Array) -> anyhow::Result { + match self.start_backend()? { + BackendStartOutcome::Alive => {} + BackendStartOutcome::Rejected(protocol) => { + return Ok(DirectStartupOutcome::Rejected(protocol)); + } + } + self.reset_io()?; + self.push_input_js(packet)?; + + let port = self + .get_port + .call(&mut self.store) + .context("oliphaunt_wasix_get_proc_port")?; + ensure!(port > 0, "oliphaunt_wasix_get_proc_port returned null"); + let status = self + .process_startup + .call(&mut self.store, port, 1, 1) + .context("ProcessStartupPacket")?; + ensure!( + status == PROCESS_STARTUP_OK || status == PROCESS_STARTUP_ERROR, + "ProcessStartupPacket returned invalid status {status}" + ); + if status == PROCESS_STARTUP_OK { + self.send_conn_data + .call(&mut self.store) + .context("oliphaunt_wasix_send_conn_data")?; + self.protocol_started = true; + } + let flush_status = self + .pq_flush + .call(&mut self.store) + .context("oliphaunt_wasix_pq_flush after startup")?; + ensure!( + flush_status == 0, + "oliphaunt_wasix_pq_flush after startup returned {flush_status}" + ); + Ok(DirectStartupOutcome::Processed { + output: self.take_output_js()?, + status, + }) + } + + fn exec_protocol_raw_inner( + &mut self, + payload: &Uint8Array, + streaming: bool, + ) -> anyhow::Result { + ensure!( + self.state == DirectInstanceState::GuestPhase, + "direct protocol exchange did not enter its guest phase" + ); + ensure!( + self.protocol_started, + "Oliphaunt direct startup has not completed" + ); + if payload.length() == 0 { + return Ok(Uint8Array::new_with_length(0)); + } + + self.reset_io()?; + self.push_input_js(payload)?; + let payload_len = payload.length() as usize; + let max_attempts = (payload_len / 5).saturating_add(2).max(1); + let mut attempts = 0usize; + let mut input_ended = false; + while self.protocol_input_remaining()? > 0 { + attempts += 1; + ensure!( + attempts <= max_attempts, + "PostgreSQL direct protocol pump did not drain input" + ); + match self.call_main_loop()? { + MainLoopOutcome::Processed | MainLoopOutcome::Recovered => {} + MainLoopOutcome::InputEnded if streaming => { + input_ended = true; + break; + } + MainLoopOutcome::InputEnded => { + return Err(self.terminal_main_loop_outcome( + "PostgresMainLoopOnce reported input end while dispatching buffered protocol input", + )); + } + } + } + if !input_ended { + self.send_ready_after_main_loop()?; + self.flush_after_main_loop()?; + } + self.take_output_js() + } + + fn close_inner(&mut self) -> anyhow::Result<()> { + match self.state { + DirectInstanceState::Poisoned | DirectInstanceState::Closed => return Ok(()), + DirectInstanceState::StartupPhase + | DirectInstanceState::GuestPhase + | DirectInstanceState::Closing => { + self.poison_guest_phase(); + anyhow::bail!( + "Oliphaunt direct instance cannot close during an active guest phase; the direct instance is closed" + ); + } + DirectInstanceState::Open | DirectInstanceState::StartupRejected => {} + } + self.state = DirectInstanceState::Closing; + let result = (|| { + let expected_active = i32::from(self.backend_started); + let previous_active = self + .set_active + .call(&mut self.store, 0) + .context("oliphaunt_wasix_set_active(0)")?; + ensure!( + previous_active == expected_active, + "oliphaunt_wasix_set_active(0) returned previous state {previous_active}, expected {expected_active}" + ); + if let Some(run_atexit_funcs) = &self.run_atexit_funcs { + run_atexit_funcs + .call(&mut self.store) + .context("oliphaunt_wasix_run_atexit_funcs")?; + } + Ok(()) + })(); + self.backend_started = false; + self.protocol_started = false; + match result { + Ok(()) => { + self.state = DirectInstanceState::Closed; + Ok(()) + } + Err(error) => { + self.state = DirectInstanceState::Poisoned; + Err(error.context("direct instance cleanup failed; the direct instance is closed")) + } + } + } + async fn instantiate( + module: WebAssembly::Module, + module_bytes: Uint8Array, + options: RunOptions, + protocol_stdin: ProtocolStdin, + protocol_stdout: ProtocolStdout, + stderr: BoundedStderr, + ) -> Result { + // Direct execution intentionally has no configurable networking or + // worker-backed runtime. RunOptions is reused for args/env/fs mounts. + let mut runtime = super::runtime::runtime(Arc::new(CallerRealmTaskManager))?; + runtime.with_instantiation_hook(super::clock::DirectClockHook); + let runtime = Arc::new(runtime); + let program_name = options + .program() + .as_string() + .unwrap_or_else(|| DEFAULT_PROGRAM_NAME.to_owned()); + let mut builder = WasiEnvBuilder::new(program_name).runtime(runtime.clone()); + options.configure_direct_builder( + &mut builder, + Box::new(protocol_stdin.clone()), + Box::new(protocol_stdout.clone()), + Box::new(stderr.clone()), + )?; + + let module = wasmer::Module::from((module, module_bytes.to_vec())); + let mut store = Store::new(runtime.engine()); + let (instance, env) = builder + .instantiate_async(module, &mut store) + .await + .context("instantiate Oliphaunt direct WASIX module")?; + let guest_memory = oliphaunt_direct_memory(&env, &store) + .context("get WASIX guest memory for direct PGWire")?; + seed_exported_c_string( + &mut store, + &instance, + &env, + "my_exec_path", + DEFAULT_PROGRAM_NAME, + )?; + + let input_reset = typed_export(&mut store, &instance, "oliphaunt_wasix_input_reset")?; + let input_reserve = typed_export(&mut store, &instance, "oliphaunt_wasix_input_reserve")?; + let input_commit = typed_export(&mut store, &instance, "oliphaunt_wasix_input_commit")?; + let input_available = + typed_export(&mut store, &instance, "oliphaunt_wasix_input_available")?; + let output_reset = typed_export(&mut store, &instance, "oliphaunt_wasix_output_reset")?; + let output_len = typed_export(&mut store, &instance, "oliphaunt_wasix_output_len")?; + let output_data = typed_export(&mut store, &instance, "oliphaunt_wasix_output_data")?; + let set_protocol_transport = typed_export( + &mut store, + &instance, + "oliphaunt_wasix_set_protocol_transport", + )?; + let set_active = typed_export(&mut store, &instance, "oliphaunt_wasix_set_active")?; + let prepare_trusted_embedded_session = typed_export( + &mut store, + &instance, + "oliphaunt_wasix_prepare_trusted_embedded_session", + )?; + let startup_outcome = + typed_export(&mut store, &instance, "oliphaunt_wasix_startup_outcome_v1")?; + let wasi_start = typed_export(&mut store, &instance, "_start")?; + let start_oliphaunt = typed_export(&mut store, &instance, "oliphaunt_wasix_start")?; + let run_atexit_funcs = + optional_typed_export(&mut store, &instance, "oliphaunt_wasix_run_atexit_funcs")?; + let get_port = typed_export(&mut store, &instance, "oliphaunt_wasix_get_proc_port")?; + let process_startup = typed_export(&mut store, &instance, "ProcessStartupPacket")?; + let send_conn_data = typed_export(&mut store, &instance, "oliphaunt_wasix_send_conn_data")?; + let pq_flush = typed_export(&mut store, &instance, "oliphaunt_wasix_pq_flush")?; + let pq_buffer_remaining_data = + typed_export(&mut store, &instance, "pq_buffer_remaining_data")?; + let main_loop = typed_export(&mut store, &instance, "PostgresMainLoopOnce")?; + let send_ready = typed_export( + &mut store, + &instance, + "PostgresSendReadyForQueryIfNecessary", + )?; + + let mut direct = Self { + _runtime: runtime, + store, + _instance: instance, + _env: env, + protocol_stdin, + protocol_stdout, + stderr, + guest_memory, + input_reset, + input_reserve, + input_commit, + input_available, + output_reset, + output_len, + output_data, + set_protocol_transport, + set_active, + prepare_trusted_embedded_session, + startup_outcome, + wasi_start, + start_oliphaunt, + run_atexit_funcs, + get_port, + process_startup, + send_conn_data, + pq_flush, + pq_buffer_remaining_data, + main_loop, + send_ready, + backend_started: false, + protocol_started: false, + state: DirectInstanceState::Open, + }; + direct.reset_io()?; + Ok(direct) + } + + fn ensure_open(&self) -> anyhow::Result<()> { + ensure!( + self.state == DirectInstanceState::Open, + "Oliphaunt direct instance is closed" + ); + Ok(()) + } + + fn ensure_protocol_started(&self) -> anyhow::Result<()> { + match self.state { + DirectInstanceState::Open => {} + DirectInstanceState::StartupRejected => { + anyhow::bail!( + "Oliphaunt direct startup was rejected; the direct instance cannot be reused" + ) + } + _ => self.ensure_open()?, + } + ensure!( + self.protocol_started, + "Oliphaunt direct startup has not completed" + ); + Ok(()) + } + + fn run_guest_phase( + &mut self, + phase: &'static str, + operation: impl FnOnce(&mut Self) -> anyhow::Result, + ) -> anyhow::Result { + self.ensure_open()?; + self.state = DirectInstanceState::GuestPhase; + match operation(self) { + Ok(value) if self.state == DirectInstanceState::GuestPhase => { + self.state = DirectInstanceState::Open; + Ok(value) + } + Ok(_) => anyhow::bail!( + "{phase} reached a terminal lifecycle state; the direct instance is closed" + ), + Err(error) => { + if self.state == DirectInstanceState::GuestPhase { + self.poison_guest_phase(); + } + Err(error.context(format!( + "{phase} failed after entering the guest; the direct instance is closed" + ))) + } + } + } + + fn poison_guest_phase(&mut self) { + // Any failure after a guest phase starts can leave arbitrary guest + // state behind. No later operation, including close, may call another + // guest export. + self.state = DirectInstanceState::Poisoned; + self.backend_started = false; + self.protocol_started = false; + } + + fn terminal_main_loop_outcome(&self, failure: impl Into) -> anyhow::Error { + anyhow::anyhow!(failure.into()) + } + + fn terminal_main_loop_error(&self, error: wasmer::RuntimeError) -> anyhow::Error { + anyhow::Error::from(error) + .context("PostgresMainLoopOnce trapped instead of returning a typed outcome") + } + + fn call_main_loop(&mut self) -> anyhow::Result { + let status = match self.main_loop.call(&mut self.store) { + Ok(status) => status, + Err(error) => return Err(self.terminal_main_loop_error(error)), + }; + MainLoopOutcome::from_i32(status).ok_or_else(|| { + self.terminal_main_loop_outcome(format!( + "PostgresMainLoopOnce returned invalid typed outcome {status}" + )) + }) + } + + fn send_ready_after_main_loop(&mut self) -> anyhow::Result<()> { + self.send_ready + .call(&mut self.store) + .context("PostgresSendReadyForQueryIfNecessary trapped after a typed main-loop outcome") + } + + fn flush_after_main_loop(&mut self) -> anyhow::Result<()> { + let status = self + .pq_flush + .call(&mut self.store) + .context("oliphaunt_wasix_pq_flush trapped after a typed main-loop outcome")?; + ensure!( + status == 0, + "protocol output flush returned {status} after a typed main-loop outcome" + ); + Ok(()) + } + + fn start_backend(&mut self) -> anyhow::Result { + if self.backend_started { + return Ok(BackendStartOutcome::Alive); + } + let previous_active = self + .set_active + .call(&mut self.store, 1) + .context("oliphaunt_wasix_set_active(1)")?; + ensure!( + previous_active == 0, + "oliphaunt_wasix_set_active(1) returned previous state {previous_active}, expected 0" + ); + let prepare_status = self + .prepare_trusted_embedded_session + .call(&mut self.store) + .context("oliphaunt_wasix_prepare_trusted_embedded_session")?; + ensure!( + prepare_status == 0, + "oliphaunt_wasix_prepare_trusted_embedded_session rejected after PostgreSQL startup began" + ); + let startup_outcome_pointer = self + .startup_outcome + .call(&mut self.store) + .context("oliphaunt_wasix_startup_outcome_v1 before _start")?; + let pending = self.read_startup_outcome_descriptor(startup_outcome_pointer)?; + ensure!( + pending.kind == STARTUP_OUTCOME_PENDING, + "startup outcome was not pending before _start" + ); + ensure!( + pending.data_pointer == 0 && pending.data_length == 0, + "pending startup outcome carried protocol data" + ); + match self.wasi_start.call(&mut self.store) { + Err(error) if runtime_exit_code(&error) == Some(OLIPHAUNT_EXIT_ALIVE) => {} + Err(error) if runtime_exit_code(&error) == Some(OLIPHAUNT_EXIT_STARTUP_REJECTED) => { + let protocol = self + .read_startup_rejection(startup_outcome_pointer) + .context("read rejected _start outcome")?; + return Ok(BackendStartOutcome::Rejected(protocol)); + } + Ok(()) => anyhow::bail!("_start returned without an Oliphaunt lifecycle exit"), + Err(error) => return Err(error).context("_start Oliphaunt single-user backend"), + } + self.start_oliphaunt + .call(&mut self.store) + .context("oliphaunt_wasix_start")?; + self.backend_started = true; + Ok(BackendStartOutcome::Alive) + } + + fn read_startup_outcome_descriptor( + &self, + descriptor_pointer: i32, + ) -> anyhow::Result { + ensure!( + descriptor_pointer != 0, + "oliphaunt_wasix_startup_outcome_v1 returned null" + ); + let bytes = oliphaunt_copy_from_guest( + &self.guest_memory, + descriptor_pointer as u32, + STARTUP_OUTCOME_DESCRIPTOR_BYTES, + ) + .map_err(|error| anyhow::anyhow!("copy startup outcome descriptor: {error:?}"))? + .to_vec(); + ensure!( + bytes.len() == STARTUP_OUTCOME_DESCRIPTOR_BYTES as usize, + "short startup outcome descriptor" + ); + let version = read_u32_le(&bytes, 0); + let size = read_u32_le(&bytes, 4); + let kind = read_u32_le(&bytes, 8); + let reserved = read_u32_le(&bytes, 12); + ensure!( + version == STARTUP_OUTCOME_VERSION, + "unsupported startup outcome version {version}" + ); + ensure!( + size == STARTUP_OUTCOME_DESCRIPTOR_BYTES, + "invalid startup outcome descriptor size {size}" + ); + ensure!(reserved == 0, "startup outcome reserved field was nonzero"); + Ok(StartupOutcomeDescriptor { + kind, + data_pointer: read_u64_le(&bytes, 16), + data_length: read_u64_le(&bytes, 24), + }) + } + + fn read_startup_rejection(&self, descriptor_pointer: i32) -> anyhow::Result> { + let descriptor = self.read_startup_outcome_descriptor(descriptor_pointer)?; + ensure!( + descriptor.kind == STARTUP_OUTCOME_REJECTED, + "_start Exit98 did not publish a rejected startup outcome" + ); + ensure!( + (1..=1_048_576).contains(&descriptor.data_length) && descriptor.data_pointer > 0, + "rejected startup outcome carried empty or oversized protocol data" + ); + let end = descriptor + .data_pointer + .checked_add(descriptor.data_length) + .context("startup rejection protocol bounds overflow")?; + ensure!( + end <= u32::MAX as u64 + 1, + "startup rejection protocol exceeds Wasm32 memory bounds" + ); + let pointer = u32::try_from(descriptor.data_pointer) + .context("startup rejection protocol pointer exceeds u32")?; + let length = u32::try_from(descriptor.data_length) + .context("startup rejection protocol length exceeds u32")?; + let protocol = oliphaunt_copy_from_guest(&self.guest_memory, pointer, length) + .map_err(|error| anyhow::anyhow!("copy startup rejection protocol: {error:?}"))? + .to_vec(); + ensure!( + protocol.len() == length as usize, + "short startup rejection protocol copy" + ); + validate_startup_rejection_protocol(&protocol)?; + Ok(protocol) + } + + fn reset_io(&mut self) -> anyhow::Result<()> { + ensure!( + self.input_reset + .call(&mut self.store) + .context("oliphaunt_wasix_input_reset")? + == 0, + "oliphaunt_wasix_input_reset failed" + ); + ensure!( + self.output_reset + .call(&mut self.store) + .context("oliphaunt_wasix_output_reset")? + == 0, + "oliphaunt_wasix_output_reset failed" + ); + Ok(()) + } + + fn push_input_js(&mut self, bytes: &Uint8Array) -> anyhow::Result<()> { + let len = i32::try_from(bytes.length()).context("direct protocol input exceeds i32")?; + if len == 0 { + return Ok(()); + } + let ptr = self + .input_reserve + .call(&mut self.store, len) + .context("oliphaunt_wasix_input_reserve")?; + ensure!(ptr > 0, "oliphaunt_wasix_input_reserve returned null"); + oliphaunt_copy_to_guest(&self.guest_memory, ptr as u32, bytes) + .map_err(|error| anyhow::anyhow!("copy direct protocol input into guest: {error:?}"))?; + let committed = self + .input_commit + .call(&mut self.store, len) + .context("oliphaunt_wasix_input_commit")?; + ensure!(committed == len, "short direct protocol input commit"); + Ok(()) + } + + fn take_output_js(&mut self) -> anyhow::Result { + let len = self + .output_len + .call(&mut self.store) + .context("oliphaunt_wasix_output_len")?; + ensure!(len >= 0, "negative direct protocol output length"); + if len == 0 { + return Ok(Uint8Array::new_with_length(0)); + } + let ptr = self + .output_data + .call(&mut self.store) + .context("oliphaunt_wasix_output_data")?; + ensure!(ptr > 0, "oliphaunt_wasix_output_data returned null"); + let output = oliphaunt_copy_from_guest(&self.guest_memory, ptr as u32, len as u32) + .map_err(|error| { + anyhow::anyhow!("copy direct protocol output from guest: {error:?}") + })?; + ensure!( + self.output_reset + .call(&mut self.store) + .context("oliphaunt_wasix_output_reset after read")? + == 0, + "oliphaunt_wasix_output_reset after read failed" + ); + Ok(output) + } + + fn protocol_input_remaining(&mut self) -> anyhow::Result { + let available = self + .input_available + .call(&mut self.store) + .context("oliphaunt_wasix_input_available")?; + ensure!(available >= 0, "negative direct protocol input length"); + if available > 0 { + return Ok(available); + } + let buffered = self + .pq_buffer_remaining_data + .call(&mut self.store) + .context("pq_buffer_remaining_data")?; + ensure!(buffered >= 0, "negative buffered protocol input length"); + Ok(buffered) + } + + fn startup_rejection_error(&self, error: anyhow::Error, protocol: Vec) -> Error { + let error = self.stderr.attach(error); + let js_error = js_sys::Error::new(&error.to_string()); + let _ = js_sys::Reflect::set( + &js_error, + &wasm_bindgen::JsValue::from_str("protocolResponse"), + &Uint8Array::from(protocol.as_slice()), + ); + let _ = js_sys::Reflect::set( + &js_error, + &wasm_bindgen::JsValue::from_str("detailedMessage"), + &wasm_bindgen::JsValue::from_str(&format!("{error:#}")), + ); + Error::js(js_error) + } +} + +fn read_u32_le(bytes: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()) +} + +fn read_u64_le(bytes: &[u8], offset: usize) -> u64 { + u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap()) +} + +fn validate_startup_rejection_protocol(protocol: &[u8]) -> anyhow::Result<()> { + let mut offset = 0usize; + let mut found_error_response = false; + while offset < protocol.len() { + ensure!( + protocol.len() - offset >= 5, + "truncated startup rejection protocol frame" + ); + let tag = protocol[offset]; + let frame_length = + u32::from_be_bytes(protocol[offset + 1..offset + 5].try_into().unwrap()) as usize; + ensure!( + frame_length >= 4, + "invalid startup rejection protocol frame length {frame_length}" + ); + let frame_end = offset + .checked_add(1) + .and_then(|value| value.checked_add(frame_length)) + .context("startup rejection protocol frame bounds overflow")?; + ensure!( + frame_end <= protocol.len(), + "truncated startup rejection protocol frame" + ); + if tag == b'E' { + validate_error_response_fields(&protocol[offset + 5..frame_end])?; + found_error_response = true; + } + offset = frame_end; + } + ensure!( + found_error_response, + "startup rejection protocol did not contain an ErrorResponse" + ); + Ok(()) +} + +fn validate_error_response_fields(fields: &[u8]) -> anyhow::Result<()> { + ensure!( + fields.last() == Some(&0), + "startup ErrorResponse was not terminated" + ); + let mut offset = 0usize; + let mut found_sqlstate = false; + while offset < fields.len() - 1 { + ensure!( + fields[offset] != 0, + "startup ErrorResponse terminated before the end of its frame" + ); + let field_type = fields[offset]; + offset += 1; + let terminator = fields[offset..] + .iter() + .position(|byte| *byte == 0) + .context("startup ErrorResponse contained an unterminated field")?; + if field_type == b'C' { + ensure!( + terminator == 5, + "startup ErrorResponse carried an invalid SQLSTATE" + ); + found_sqlstate = true; + } + offset += terminator + 1; + } + ensure!( + offset == fields.len() - 1, + "startup ErrorResponse contained trailing bytes" + ); + ensure!( + found_sqlstate, + "startup ErrorResponse did not contain a SQLSTATE" + ); + Ok(()) +} + +fn typed_export( + store: &mut Store, + instance: &WasmerInstance, + name: &str, +) -> anyhow::Result> +where + Args: WasmTypeList, + Rets: WasmTypeList, +{ + instance + .exports + .get_typed_function::(&*store, name) + .or_else(|_| { + instance + .exports + .get_typed_function::(&*store, &format!("_{name}")) + }) + .with_context(|| format!("get {name} export")) +} + +fn optional_typed_export( + store: &mut Store, + instance: &WasmerInstance, + name: &str, +) -> anyhow::Result>> +where + Args: WasmTypeList, + Rets: WasmTypeList, +{ + if instance.exports.get_function(name).is_err() + && instance.exports.get_function(&format!("_{name}")).is_err() + { + return Ok(None); + } + typed_export(store, instance, name).map(Some) +} + +fn seed_exported_c_string( + store: &mut Store, + instance: &WasmerInstance, + env: &WasiFunctionEnv, + name: &str, + value: &str, +) -> anyhow::Result<()> { + let Ok(global) = instance.exports.get_global(name) else { + return Ok(()); + }; + let Value::I32(ptr) = global.get(&mut *store) else { + return Ok(()); + }; + if ptr <= 0 { + return Ok(()); + } + let mut bytes = value.as_bytes().to_vec(); + bytes.push(0); + env.data(&*store) + .try_memory_view(&*store) + .context("get WASIX memory view")? + .write(ptr as u64, &bytes) + .with_context(|| format!("seed {name} at 0x{ptr:x}"))?; + Ok(()) +} + +fn runtime_exit_code(error: &wasmer::RuntimeError) -> Option { + error + .downcast_ref::() + .and_then(|error| match error { + WasiError::Exit(code) => Some(code.raw()), + _ => None, + }) +} diff --git a/src/wasix/browser-host/adapter/run.rs b/src/wasix/browser-host/adapter/run.rs new file mode 100644 index 000000000..aa6762909 --- /dev/null +++ b/src/wasix/browser-host/adapter/run.rs @@ -0,0 +1,65 @@ +use std::sync::Arc; + +use futures::channel::oneshot; +use js_sys::{Uint8Array, WebAssembly}; +use wasm_bindgen::{JsCast, prelude::wasm_bindgen}; +use wasm_bindgen_futures::JsFuture; +use wasmer_wasix::WasiEnvBuilder; + +use super::{Instance, RunOptions, instance::ExitCondition, utils::Error}; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "WebAssembly.Module | Uint8Array")] + pub type WasmModule; +} + +/// Isolated process execution for our initdb guest, using the SDK's scheduler. +#[wasm_bindgen(js_name = runWasix)] +pub async fn run_wasix(input: WasmModule, options: RunOptions) -> Result { + let module = if let Some(module) = input.dyn_ref::() { + let bytes = options.module_bytes().ok_or_else(|| { + anyhow::anyhow!("runWasix requires moduleBytes with a precompiled module") + })?; + wasmer::Module::from((module.clone(), bytes.to_vec())) + } else { + let bytes = input + .dyn_ref::() + .ok_or_else(|| anyhow::anyhow!("runWasix expects Wasm bytes or a compiled module"))?; + let module: WebAssembly::Module = JsFuture::from(WebAssembly::compile(bytes)) + .await + .map_err(Error::js)? + .dyn_into() + .map_err(Error::js)?; + wasmer::Module::from((module, bytes.to_vec())) + }; + let pool = Arc::new(crate::tasks::ThreadPool::new(None)); + let runtime: Arc = + Arc::new(super::runtime::runtime(pool.clone())?); + let program = options + .program() + .as_string() + .unwrap_or_else(|| "wasm".to_owned()); + let mut builder = WasiEnvBuilder::new(program).runtime(runtime.clone()); + let (stdin, stdout, stderr) = options.configure_builder(&mut builder)?; + let (sender, exit) = oneshot::channel(); + let env = builder.build()?; + let mut task = wasmer_wasix::bin_factory::spawn_exec_module(module, env, &runtime)?; + wasm_bindgen_futures::spawn_local(async move { + let result = match task.wait_finished().await { + Ok(code) if code.raw() == 0 => Ok(()), + Ok(code) => Err( + wasmer_wasix::WasiRuntimeError::Wasi(wasmer_wasix::WasiError::Exit(code)).into(), + ), + Err(error) => Err(anyhow::anyhow!(error.to_string())), + }; + let _ = sender.send(ExitCondition::from_result(result)); + }); + Ok(Instance { + stdin, + stdout, + stderr, + exit, + pool, + }) +} diff --git a/src/wasix/browser-host/adapter/runtime.rs b/src/wasix/browser-host/adapter/runtime.rs new file mode 100644 index 000000000..309ac07d3 --- /dev/null +++ b/src/wasix/browser-host/adapter/runtime.rs @@ -0,0 +1,11 @@ +use super::utils::Error; +use std::sync::Arc; +use wasmer_wasix::{PluggableRuntime, VirtualTaskManager}; +/// Keep runtime configuration local to each direct instance or tool process. +/// No registry, networking or shared command pool participates in SQL calls. +pub(crate) type Runtime = PluggableRuntime; +pub(crate) fn runtime(tasks: Arc) -> Result { + let mut runtime = PluggableRuntime::new(tasks); + runtime.set_tty(Arc::new(wasmer_wasix::runtime::DefaultTty::default())); + Ok(runtime) +} diff --git a/src/wasix/browser-host/adapter/streams.rs b/src/wasix/browser-host/adapter/streams.rs new file mode 100644 index 000000000..5e95e0820 --- /dev/null +++ b/src/wasix/browser-host/adapter/streams.rs @@ -0,0 +1,344 @@ +use anyhow::Context; +use bytes::BytesMut; +use futures::{Stream, future::Either}; +use js_sys::{JsString, Promise, Reflect, Uint8Array}; +use tracing::Instrument; +use virtual_fs::{AsyncReadExt, AsyncWriteExt, Pipe}; +use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen}; +use wasm_bindgen_futures::JsFuture; +use web_sys::{ + ReadableStream, ReadableStreamDefaultController, ReadableStreamDefaultReader, WritableStream, +}; + +use super::utils::Error; + +/// Set up a pipe where data written from JavaScript can be read by the WASIX +/// process. +pub(crate) fn input_pipe() -> (Pipe, WritableStream) { + let (left, right) = Pipe::channel(); + + let sink = JsValue::from(WritableStreamSink { pipe: right }); + + let callback: wasm_bindgen::prelude::Closure f64> = + wasm_bindgen::closure::Closure::new(|chunk: Uint8Array| chunk.byte_length() as f64); + + let strategy = web_sys::QueuingStrategy::new(); + strategy.set_high_water_mark(256.0); + strategy.set_size(callback.into_js_value().unchecked_ref()); + let stream = + WritableStream::new_with_underlying_sink_and_strategy(sink.unchecked_ref(), &strategy) + .unwrap(); + + (left, stream) +} + +#[derive(Debug)] +#[wasm_bindgen(skip_typescript)] +struct WritableStreamSink { + pipe: Pipe, +} + +#[wasm_bindgen] +impl WritableStreamSink { + /// This method, also defined by the developer, will be called if the app + /// signals that it has finished writing chunks to the stream. The contents + /// should do whatever is necessary to finalize writes to the underlying + /// sink, and release access to it. If this process is asynchronous, it can + /// return a promise to signal success or failure. This method will be + /// called only after all queued-up writes have succeeded. + pub fn close(&mut self) -> Promise { + let mut pipe = self.pipe.clone(); + + wasm_bindgen_futures::future_to_promise( + async move { + pipe.flush() + .await + .context("Flushing failed") + .map_err(Error::from)?; + pipe.close(); + tracing::debug!("Pipe closed"); + + Ok(JsValue::UNDEFINED) + } + .in_current_span() + .instrument(tracing::debug_span!("close")), + ) + } + + /// This method, also defined by the developer, will be called if the app + /// signals that it wishes to abruptly close the stream and put it in an + /// errored state. It can clean up any held resources, much like close(), + /// but abort() will be called even if writes are queued up — those chunks + /// will be thrown away. If this process is asynchronous, it can return a + /// promise to signal success or failure. The reason parameter contains a + /// string describing why the stream was aborted. + pub fn abort(&mut self, reason: JsString) { + tracing::debug!(%reason, "Aborting the stream"); + self.pipe.close(); + } + + /// This method, also defined by the developer, will be called when a new + /// chunk of data (specified in the chunk parameter) is ready to be written + /// to the underlying sink. It can return a promise to signal success or + /// failure of the write operation. This method will be called only after + /// previous writes have succeeded, and never after the stream is closed or + /// aborted (see below). + pub fn write(&mut self, chunk: Uint8Array) -> Promise { + let mut pipe = self.pipe.clone(); + let data = chunk.to_vec(); + + wasm_bindgen_futures::future_to_promise( + async move { + tracing::trace!( + bytes_written = data.len(), + ?data, + data_utf8 = String::from_utf8_lossy(&data).as_ref(), + ); + + pipe.write_all(&data) + .await + .context("Write failed") + .map_err(Error::from)?; + Ok(JsValue::UNDEFINED) + } + .in_current_span() + .instrument(tracing::trace_span!("write")), + ) + } +} + +/// Set up a pipe where the WASIX pipe writes data that will be read from +/// JavaScript. +pub(crate) fn output_pipe() -> (Pipe, ReadableStream) { + let (left, right) = Pipe::channel(); + + let source = JsValue::from(ReadableStreamSource { pipe: right }); + + let callback: wasm_bindgen::prelude::Closure f64> = + wasm_bindgen::closure::Closure::new(|chunk: Uint8Array| chunk.byte_length() as f64); + + let strategy = web_sys::QueuingStrategy::new(); + strategy.set_high_water_mark(256.0); + strategy.set_size(callback.into_js_value().unchecked_ref()); + let stream = + ReadableStream::new_with_underlying_source_and_strategy(source.unchecked_ref(), &strategy) + .unwrap(); + + (left, stream) +} + +#[derive(Debug)] +#[wasm_bindgen(skip_typescript)] +struct ReadableStreamSource { + pipe: Pipe, +} + +#[wasm_bindgen] +impl ReadableStreamSource { + /// This method, also defined by the developer, will be called repeatedly + /// when the stream's internal queue of chunks is not full, up until it + /// reaches its high water mark. If pull() returns a promise, then it won't + /// be called again until that promise fulfills; if the promise rejects, the + /// stream will become errored. The controller parameter passed to this + /// method is a ReadableStreamDefaultController or a + /// ReadableByteStreamController, depending on the value of the type + /// property. This can be used by the developer to control the stream as + /// more chunks are fetched. This function will not be called until start() + /// successfully completes. Additionally, it will only be called repeatedly + /// if it enqueues at least one chunk or fulfills a BYOB request; a no-op + /// pull() implementation will not be continually called. + pub fn pull(&mut self, controller: ReadableStreamDefaultController) -> Promise { + let mut pipe = self.pipe.clone(); + + wasm_bindgen_futures::future_to_promise( + async move { + /// The maximum buffer size we will allow - helps avoid OOMs. + const MAX_CAPACITY: usize = 10_000_000; + + let capacity = controller + .desired_size() + .map(|size| std::cmp::min(size as usize, MAX_CAPACITY)) + .unwrap_or(128); + let mut buffer = BytesMut::with_capacity(capacity); + + match pipe.read_buf(&mut buffer).await.context("Read failed") { + Ok(0) => { + tracing::debug!("EOF"); + controller.close()?; + } + Ok(bytes_read) => { + let data = &buffer[..bytes_read]; + tracing::trace!( + bytes_read, + ?data, + data_utf8 = String::from_utf8_lossy(data).as_ref() + ); + + let buffer = Uint8Array::from(data); + controller.enqueue_with_chunk(&buffer)?; + } + Err(e) => { + tracing::debug!(error = &*e); + let err = JsValue::from(Error::from(e)); + controller.error_with_e(&err); + } + } + + Ok(JsValue::UNDEFINED) + } + .in_current_span() + .instrument(tracing::trace_span!("pull")), + ) + } + + /// This method, also defined by the developer, will be called if the app + /// signals that the stream is to be cancelled (e.g. if + /// ReadableStream.cancel() is called). The contents should do whatever is + /// necessary to release access to the stream source. If this process is + /// asynchronous, it can return a promise to signal success or failure. The + /// reason parameter contains a string describing why the stream was + /// cancelled. + pub fn cancel(&mut self) { + tracing::debug!("Read stream cancelled"); + self.pipe.close(); + } + + /// This property controls what type of readable stream is being dealt with. + /// If it is included with a value set to `"bytes"`, the passed controller + /// object will be a `ReadableByteStreamController`` capable of handling a + /// BYOB (bring your own buffer)/byte stream. If it is not included, the + /// passed controller will be a `ReadableStreamDefaultController`. + #[wasm_bindgen(getter, js_name = "type")] + pub fn type_(&self) -> Option { + // Note: We can't use BYOB for zero-copy streaming because it'd mean + // JavaScript code gets a reference to the buffer allocated inside the + // pull() method. That Uint8Array is a view into a linear memory + // and would any access after the pull method's promise completes would + // be a use-after-free. + // + // This also works around a limitation in Safari where returning + // JsString::from("bytes") causes the browser's *native* code to run the + // constructor for ReadableByteStreamController, which isn't implemented + // yet. + None + } +} + +pub(crate) fn read_to_end(stream: ReadableStream) -> impl Stream, Error>> { + let reader = match ReadableStreamDefaultReader::new(&stream) { + Ok(reader) => reader, + Err(_) => { + tracing::trace!( + "The ReadableStream is already locked. Leaving it up to the user to consume." + ); + return Either::Left(futures::stream::empty()); + } + }; + + struct DropGuard(ReadableStreamDefaultReader); + impl Drop for DropGuard { + fn drop(&mut self) { + // Make sure the reader lock always gets released so the caller + // isn't left with an unusable stream + self.0.release_lock(); + } + } + + let stream = futures::stream::try_unfold(DropGuard(reader), move |reader| async { + let next_chunk = JsFuture::from(reader.0.read()).await.map_err(Error::js)?; + let chunk = get_chunk(next_chunk)?; + Ok(chunk.map(|c| (c, reader))) + }); + + Either::Right(stream) +} + +fn get_chunk(next_chunk: JsValue) -> Result>, Error> { + let done = JsValue::from_str("done"); + let value = JsValue::from_str("value"); + + let done = Reflect::get(&next_chunk, &done).map_err(Error::js)?; + if done.is_truthy() { + return Ok(None); + } + + let chunk = Reflect::get(&next_chunk, &value).map_err(Error::js)?; + let chunk = Uint8Array::new(&chunk); + + Ok(Some(chunk.to_vec())) +} + +#[cfg(test)] +mod tests { + use futures::TryStreamExt; + use wasm_bindgen_futures::JsFuture; + use wasm_bindgen_test::wasm_bindgen_test; + + use super::*; + + #[wasm_bindgen_test] + async fn writing_to_the_pipe_is_readable_from_js() { + let (mut pipe, stream) = output_pipe(); + + pipe.write_all(b"Hello, World!").await.unwrap(); + pipe.close(); + + let data = read_to_end(stream) + .try_fold(Vec::new(), |mut buffer, chunk| async { + buffer.extend(chunk); + Ok(buffer) + }) + .await + .unwrap(); + assert_eq!(String::from_utf8(data).unwrap(), "Hello, World!"); + } + + #[wasm_bindgen_test] + async fn multiple_writes_to_an_output_pipe_are_seen_by_js() { + let (pipe, stream) = output_pipe(); + + // Pretend to be a WASIX program writing to stdout in the background + wasm_bindgen_futures::spawn_local({ + let mut pipe = pipe.clone(); + async move { + pipe.write_all(b"Hello").await.unwrap(); + pipe.write_all(b", ").await.unwrap(); + pipe.write_all(b"World").await.unwrap(); + pipe.write_all(b"!").await.unwrap(); + pipe.close(); + } + }); + + // Pretend to be some JS code using the ReadableStream API to read from + // stdout. + let data = read_to_end(stream) + .try_fold(Vec::new(), |mut buffer, chunk| async { + buffer.extend(chunk); + Ok(buffer) + }) + .await + .unwrap(); + assert_eq!(String::from_utf8(data).unwrap(), "Hello, World!"); + + // Make sure one handle to the pipe stays alive until the very end (e.g. + // because it was stored in the runtime). + drop(pipe); + } + + #[wasm_bindgen_test] + async fn data_written_by_js_is_readable_from_the_pipe() { + let (mut pipe, stream) = input_pipe(); + let chunk = Uint8Array::from(b"Hello, World!".as_ref()); + + let writer = stream.get_writer().unwrap(); + JsFuture::from(writer.write_with_chunk(&chunk)) + .await + .unwrap(); + JsFuture::from(writer.close()).await.unwrap(); + + let mut data = String::new(); + pipe.read_to_string(&mut data).await.unwrap(); + + assert_eq!(data, "Hello, World!"); + } +} diff --git a/src/wasix/browser-host/adapter/tool_direct.rs b/src/wasix/browser-host/adapter/tool_direct.rs new file mode 100644 index 000000000..dcc059ba1 --- /dev/null +++ b/src/wasix/browser-host/adapter/tool_direct.rs @@ -0,0 +1,592 @@ +use std::{ + cell::RefCell, + error::Error as StdError, + fmt, + io::{self, SeekFrom}, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context as TaskContext, Poll}, +}; + +use anyhow::{Context, ensure}; +use js_sys::{Function, Uint8Array, WebAssembly}; +use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; +use virtual_fs::VirtualFile; +use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen}; +use wasmer::{Store, TypedFunction}; +use wasmer_types::ModuleHash; +use wasmer_wasix::{Runtime as _, WasiEnvBuilder, WasiError}; + +use super::{ + RunOptions, + caller_realm::CallerRealmTaskManager, + runtime::Runtime, + utils::{Error, js_error}, +}; + +const DEFAULT_PROGRAM_NAME: &str = "wasm"; +use super::protocol_contract::PROTOCOL_CALLBACK_CHUNK_BYTES as PROTOCOL_CHUNK_BYTES; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "OliphauntToolOutput")] + pub type JsToolOutput; +} + +#[wasm_bindgen(typescript_custom_section)] +const TOOL_OUTPUT_TYPE_DEFINITION: &'static str = r#" +export type OliphauntToolOutput = { + code: number; + stdoutBytes: Uint8Array; + stderrBytes: Uint8Array; +} +"#; + +fn tool_output(code: i32, stdout: Vec, stderr: Vec) -> JsToolOutput { + let output = js_sys::Object::new(); + let _ = js_sys::Reflect::set(&output, &JsValue::from_str("code"), &JsValue::from(code)); + let _ = js_sys::Reflect::set( + &output, + &JsValue::from_str("stdoutBytes"), + &Uint8Array::from(stdout.as_slice()), + ); + let _ = js_sys::Reflect::set( + &output, + &JsValue::from_str("stderrBytes"), + &Uint8Array::from(stderr.as_slice()), + ); + output.unchecked_into() +} + +/// Immutable frontend-program state shared by repeated invocations in one +/// JavaScript realm. Every invocation still creates fresh WASI process state. +#[wasm_bindgen] +pub struct OliphauntPreparedTool { + runtime: Arc, + module: wasmer::Module, + module_hash: ModuleHash, +} + +#[wasm_bindgen(js_name = prepareOliphauntTool)] +pub fn prepare_oliphaunt_tool( + module: WebAssembly::Module, + module_bytes: Uint8Array, +) -> Result { + let module_bytes = module_bytes.to_vec(); + let module_hash = ModuleHash::new(&module_bytes); + let module = wasmer::Module::from((module, module_bytes)); + let runtime = Arc::new(super::runtime::runtime(Arc::new(CallerRealmTaskManager))?); + Ok(OliphauntPreparedTool { + runtime, + module, + module_hash, + }) +} + +#[derive(Clone)] +struct ProtocolCallbacks { + read: Function, + write: Function, +} + +thread_local! { + static PROTOCOL_CALLBACKS: RefCell> = const { RefCell::new(None) }; +} + +/// Keeps the caller-realm callbacks active only while the guest is executing. +struct ActiveProtocolCallbacks; + +impl ActiveProtocolCallbacks { + fn begin(read: Function, write: Function) -> anyhow::Result { + PROTOCOL_CALLBACKS.with(|active| { + let mut active = active.borrow_mut(); + ensure!( + active.is_none(), + "an Oliphaunt tool protocol session is already active" + ); + *active = Some(ProtocolCallbacks { read, write }); + Ok(Self) + }) + } +} + +impl Drop for ActiveProtocolCallbacks { + fn drop(&mut self) { + PROTOCOL_CALLBACKS.with(|active| *active.borrow_mut() = None); + } +} + +#[derive(Clone, Debug, Default)] +struct ToolProtocolFile; + +impl ToolProtocolFile { + fn callbacks() -> io::Result { + PROTOCOL_CALLBACKS + .with(|active| active.borrow().clone()) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::BrokenPipe, + "Oliphaunt tool protocol callbacks are inactive", + ) + }) + } +} + +impl AsyncRead for ToolProtocolFile { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + buffer: &mut ReadBuf<'_>, + ) -> Poll> { + let requested = buffer.remaining().min(PROTOCOL_CHUNK_BYTES); + if requested == 0 { + return Poll::Ready(Ok(())); + } + + let callbacks = match Self::callbacks() { + Ok(callbacks) => callbacks, + Err(error) => return Poll::Ready(Err(error)), + }; + let value = match callbacks + .read + .call1(&JsValue::UNDEFINED, &JsValue::from(requested as u32)) + { + Ok(value) => value, + Err(error) => { + return Poll::Ready(Err(io::Error::other(format!( + "Oliphaunt tool protocol read failed: {}", + js_error(error) + )))); + } + }; + let input = match value.dyn_into::() { + Ok(input) => input, + Err(_) => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "Oliphaunt tool protocol read must return a Uint8Array", + ))); + } + }; + let length = input.length() as usize; + if length > requested { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "Oliphaunt tool protocol read exceeded the requested byte count", + ))); + } + if length > 0 { + input.copy_to(buffer.initialize_unfilled_to(length)); + buffer.advance(length); + } + Poll::Ready(Ok(())) + } +} + +impl AsyncWrite for ToolProtocolFile { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + input: &[u8], + ) -> Poll> { + if input.is_empty() { + return Poll::Ready(Ok(0)); + } + + let callbacks = match Self::callbacks() { + Ok(callbacks) => callbacks, + Err(error) => return Poll::Ready(Err(error)), + }; + let length = input.len().min(PROTOCOL_CHUNK_BYTES); + // Copy the Rust-owned input into JavaScript-owned storage before the + // callback. It may retain or mutate this independent chunk without + // aliasing memory that Rust can reuse after this call returns. + let chunk = Uint8Array::from(&input[..length]); + match callbacks.write.call1(&JsValue::UNDEFINED, &chunk) { + Ok(_) => Poll::Ready(Ok(length)), + Err(error) => Poll::Ready(Err(io::Error::other(format!( + "Oliphaunt tool protocol write failed: {}", + js_error(error) + )))), + } + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncSeek for ToolProtocolFile { + fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } +} + +impl VirtualFile for ToolProtocolFile { + fn last_accessed(&self) -> u64 { + 0 + } + + fn last_modified(&self) -> u64 { + 0 + } + + fn created_time(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + 0 + } + + fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { + Ok(()) + } + + fn unlink(&mut self) -> virtual_fs::Result<()> { + Ok(()) + } + + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) + } + + fn poll_write_ready( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + ) -> Poll> { + Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) + } +} + +#[derive(Clone, Copy, Debug)] +enum CaptureStream { + Stdout, + Stderr, +} + +impl fmt::Display for CaptureStream { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(match self { + Self::Stdout => "stdout", + Self::Stderr => "stderr", + }) + } +} + +#[derive(Clone, Copy, Debug)] +enum CaptureFailure { + AllocationFailed(CaptureStream), + LockPoisoned, +} + +impl CaptureFailure { + fn io_error(self) -> io::Error { + let kind = match self { + Self::AllocationFailed(_) => io::ErrorKind::OutOfMemory, + Self::LockPoisoned => io::ErrorKind::Other, + }; + io::Error::new(kind, self) + } +} + +impl fmt::Display for CaptureFailure { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::AllocationFailed(stream) => { + write!( + formatter, + "Oliphaunt WASIX tool {stream} capture allocation failed" + ) + } + Self::LockPoisoned => { + formatter.write_str("Oliphaunt WASIX tool output capture lock was poisoned") + } + } + } +} + +impl StdError for CaptureFailure {} + +#[derive(Debug, Default)] +struct CaptureState { + stdout: Vec, + stderr: Vec, + failure: Option, +} + +impl CaptureState { + fn output(&self, stream: CaptureStream) -> &Vec { + match stream { + CaptureStream::Stdout => &self.stdout, + CaptureStream::Stderr => &self.stderr, + } + } + + fn output_mut(&mut self, stream: CaptureStream) -> &mut Vec { + match stream { + CaptureStream::Stdout => &mut self.stdout, + CaptureStream::Stderr => &mut self.stderr, + } + } + + fn discard_output(&mut self) { + self.stdout = Vec::new(); + self.stderr = Vec::new(); + } + + fn fail(&mut self, failure: CaptureFailure) -> io::Error { + let failure = *self.failure.get_or_insert(failure); + self.discard_output(); + failure.io_error() + } + + fn poison_lock(&mut self) -> CaptureFailure { + self.failure = Some(CaptureFailure::LockPoisoned); + self.discard_output(); + CaptureFailure::LockPoisoned + } + + fn append(&mut self, stream: CaptureStream, input: &[u8]) -> io::Result { + if let Some(failure) = self.failure { + return Err(failure.io_error()); + } + if self.output_mut(stream).try_reserve(input.len()).is_err() { + return Err(self.fail(CaptureFailure::AllocationFailed(stream))); + } + self.output_mut(stream).extend_from_slice(input); + Ok(input.len()) + } +} + +/// Complete in-memory stdout/stderr capture for a fresh tool process. +/// The handle refuses to publish either stream after any capture failure. +#[derive(Clone, Debug)] +struct CaptureFile { + state: Arc>, + stream: CaptureStream, +} + +#[derive(Clone, Debug)] +struct CaptureHandle { + state: Arc>, +} + +impl CaptureFile { + fn pair() -> (Self, Self, CaptureHandle) { + let state = Arc::new(Mutex::new(CaptureState::default())); + ( + Self { + state: state.clone(), + stream: CaptureStream::Stdout, + }, + Self { + state: state.clone(), + stream: CaptureStream::Stderr, + }, + CaptureHandle { state }, + ) + } + + fn with_state( + &self, + operation: impl FnOnce(&mut CaptureState) -> io::Result, + ) -> io::Result { + let mut state = match self.state.lock() { + Ok(state) => state, + Err(error) => { + let mut state = error.into_inner(); + return Err(state.poison_lock().io_error()); + } + }; + operation(&mut state) + } +} + +impl CaptureHandle { + fn failure(&self) -> Option { + match self.state.lock() { + Ok(state) => state.failure, + Err(error) => { + let mut state = error.into_inner(); + state.poison_lock(); + state.failure + } + } + } + + fn finish(&self) -> anyhow::Result<(Vec, Vec)> { + let mut state = match self.state.lock() { + Ok(state) => state, + Err(error) => { + let mut state = error.into_inner(); + let failure = state.poison_lock(); + return Err(anyhow::Error::new(failure)); + } + }; + if let Some(failure) = state.failure { + state.discard_output(); + return Err(anyhow::Error::new(failure)); + } + Ok(( + std::mem::take(&mut state.stdout), + std::mem::take(&mut state.stderr), + )) + } +} + +impl AsyncRead for CaptureFile { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + _buffer: &mut ReadBuf<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncWrite for CaptureFile { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + input: &[u8], + ) -> Poll> { + let stream = self.stream; + Poll::Ready(self.with_state(|state| state.append(stream, input))) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +impl AsyncSeek for CaptureFile { + fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + let stream = self.stream; + Poll::Ready(self.with_state(|state| Ok(state.output(stream).len() as u64))) + } +} + +impl VirtualFile for CaptureFile { + fn last_accessed(&self) -> u64 { + 0 + } + + fn last_modified(&self) -> u64 { + 0 + } + + fn created_time(&self) -> u64 { + 0 + } + + fn size(&self) -> u64 { + let stream = self.stream; + match self.with_state(|state| Ok(state.output(stream).len() as u64)) { + Ok(size) => size, + Err(_) => 0, + } + } + + fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { + Err(virtual_fs::FsError::PermissionDenied) + } + + fn unlink(&mut self) -> virtual_fs::Result<()> { + Ok(()) + } + + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } + + fn poll_write_ready( + self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + ) -> Poll> { + Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) + } +} + +/// Run an Oliphaunt frontend tool synchronously in the caller's JavaScript +/// realm, while routing its private PGWire device through blocking callbacks. +/// The protocol write callback receives an owned JavaScript copy that it may retain. +#[wasm_bindgen(js_name = runOliphauntToolDirect)] +pub async fn run_oliphaunt_tool_direct( + prepared: &OliphauntPreparedTool, + options: RunOptions, + protocol_read: Function, + protocol_write: Function, +) -> Result { + if let Some(tty) = prepared.runtime.tty() { + tty.reset(); + } + let program_name = options + .program() + .as_string() + .unwrap_or_else(|| DEFAULT_PROGRAM_NAME.to_owned()); + let (stdout, stderr, capture) = CaptureFile::pair(); + let mut builder = WasiEnvBuilder::new(program_name).runtime(prepared.runtime.clone()); + options.configure_tool_direct_builder( + &mut builder, + Box::new(ToolProtocolFile), + Box::new(stdout.clone()), + Box::new(stderr.clone()), + )?; + + let mut store = Store::new(prepared.runtime.engine()); + let (instance, _env) = builder + .instantiate_ext_async(prepared.module.clone(), prepared.module_hash, &mut store) + .await + .context("instantiate Oliphaunt WASIX tool")?; + let start: TypedFunction<(), ()> = instance + .exports + .get_typed_function(&store, "_start") + .context("get Oliphaunt WASIX tool _start export")?; + + let code = { + let _callbacks = ActiveProtocolCallbacks::begin(protocol_read, protocol_write)?; + match start.call(&mut store) { + Ok(()) => 0, + Err(error) => match runtime_exit_code(&error) { + Some(code) => code, + None => { + if let Some(failure) = capture.failure() { + return Err(Error::from(anyhow::Error::new(failure))); + } + return Err(Error::from( + anyhow::Error::from(error).context("run Oliphaunt WASIX tool"), + )); + } + }, + } + }; + + let (stdout, stderr) = capture.finish().map_err(Error::from)?; + Ok(tool_output(code, stdout, stderr)) +} + +fn runtime_exit_code(error: &wasmer::RuntimeError) -> Option { + error + .downcast_ref::() + .and_then(|error| match error { + WasiError::Exit(code) => Some(code.raw()), + _ => None, + }) +} diff --git a/src/wasix/browser-host/adapter/utils.rs b/src/wasix/browser-host/adapter/utils.rs new file mode 100644 index 000000000..6540f07a9 --- /dev/null +++ b/src/wasix/browser-host/adapter/utils.rs @@ -0,0 +1,167 @@ +use std::{ + collections::BTreeMap, + fmt::{Debug, Display}, +}; + +use js_sys::{JsString, Uint8Array}; + +use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen}; + +/// Try to extract the most appropriate error message from a [`JsValue`], +/// falling back to a generic error message. +pub(crate) fn js_error(value: JsValue) -> anyhow::Error { + if let Some(e) = value.dyn_ref::() { + anyhow::Error::msg(String::from(e.message())) + } else if let Some(obj) = value.dyn_ref::() { + return anyhow::Error::msg(String::from(obj.to_string())); + } else if let Some(s) = value.dyn_ref::() { + return anyhow::Error::msg(String::from(s)); + } else { + anyhow::anyhow!("An unknown error occurred: {value:?}") + } +} + +/// A wrapper around [`anyhow::Error`] that can be returned to JS to raise +/// an exception. +#[derive(Debug)] +pub enum Error { + Rust(anyhow::Error), + JavaScript(JsValue), +} + +impl Error { + pub(crate) fn js(error: impl Into) -> Self { + Error::JavaScript(error.into()) + } + + pub(crate) fn into_anyhow(self) -> anyhow::Error { + match self { + Error::Rust(e) => e, + Error::JavaScript(js) => js_error(js), + } + } +} + +impl> From for Error { + fn from(value: E) -> Self { + Error::Rust(value.into()) + } +} + +impl From for JsValue { + fn from(error: Error) -> Self { + match error { + Error::JavaScript(e) => e, + Error::Rust(error) => { + let message = error.to_string(); + let js_error = js_sys::Error::new(&message); + + let _ = js_sys::Reflect::set( + &js_error, + &JsString::from("message"), + &JsString::from(error.to_string()), + ); + + let _ = js_sys::Reflect::set( + &js_error, + &JsString::from("detailedMessage"), + &JsString::from(format!("{error:?}")), + ); + + let causes: js_sys::Array = std::iter::successors(error.source(), |e| e.source()) + .map(|e| JsString::from(e.to_string())) + .collect(); + let _ = js_sys::Reflect::set(&js_error, &JsString::from("causes"), &causes); + + js_error.into() + } + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Rust(e) => Display::fmt(e, f), + Error::JavaScript(js) => { + if let Some(e) = js.dyn_ref::() { + write!(f, "{}", String::from(e.message())) + } else if let Some(obj) = js.dyn_ref::() { + write!(f, "{}", String::from(obj.to_string())) + } else if let Some(s) = js.dyn_ref::() { + write!(f, "{}", String::from(s)) + } else { + write!(f, "A JavaScript error occurred") + } + } + } + } +} + +pub(crate) fn object_entries(obj: &js_sys::Object) -> Result, Error> { + let mut entries = BTreeMap::new(); + + for key in js_sys::Object::keys(obj) { + let key: JsString = key + .dyn_into() + .map_err(|_| Error::js(js_sys::TypeError::new("Object keys should be strings")))?; + let value = js_sys::Reflect::get(obj, &key).map_err(Error::js)?; + entries.insert(key, value); + } + + Ok(entries) +} + +pub(crate) fn js_string_array(array: js_sys::Array) -> Result, Error> { + let mut parsed = Vec::new(); + + for arg in array { + match arg.dyn_into::() { + Ok(arg) => parsed.push(String::from(arg)), + Err(_) => { + return Err(Error::js(js_sys::TypeError::new( + "Expected an array of strings", + ))); + } + } + } + + Ok(parsed) +} + +pub(crate) fn js_record_of_strings(obj: &js_sys::Object) -> Result, Error> { + let mut parsed = Vec::new(); + + for (key, value) in super::utils::object_entries(obj)? { + let key: String = key.into(); + let value: String = value + .dyn_into::() + .map_err(|_| { + Error::js(js_sys::TypeError::new( + "Expected an object mapping strings to strings", + )) + })? + .into(); + parsed.push((key, value)); + } + + Ok(parsed) +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "string | Uint8Array")] + pub type StringOrBytes; +} + +impl StringOrBytes { + pub fn as_bytes(&self) -> Vec { + if let Some(s) = self.dyn_ref::() { + String::from(s).into() + } else if let Some(buffer) = self.dyn_ref::() { + buffer.to_vec() + } else { + unreachable!() + } + } +} diff --git a/src/wasix/browser-host/build-provenance.mts b/src/wasix/browser-host/build-provenance.mts index 8611ca941..d4a301129 100644 --- a/src/wasix/browser-host/build-provenance.mts +++ b/src/wasix/browser-host/build-provenance.mts @@ -1,5 +1,5 @@ import { createHash } from 'node:crypto'; -import { readFile } from 'node:fs/promises'; +import { readdir, readFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -33,6 +33,12 @@ export async function loadHostBuildContract() { ...patchSeries.map((patch) => `src/wasix/browser-host/patches/${patch}`), buildScriptPath, provenanceScriptPath, + 'src/wasix/browser-host/prepare-sdk.mts', + 'src/wasix/browser-host/bundle-sdk.mts', + 'src/wasix/browser-host/index.mts', + 'src/wasix/browser-host/rust-toolchain.toml', + 'src/wasix/browser-host/Cargo.lock', + ...(await adapterSources('src/wasix/browser-host/adapter')), 'tools/dev/curl-platform-flags.sh', 'tools/dev/acquisition.sh', 'src/third-party/tools/fetch-sources.sh', @@ -51,9 +57,12 @@ export async function loadHostBuildContract() { wasmerJsCommit: tomlString(source, 'wasmer-js', 'commit'), wasmerWasixVersion: tomlString(source, 'wasmer-wasix', 'version'), virtualFsVersion: tomlString(source, 'virtual-fs', 'version'), + rustToolchain: (await readFile(resolve(hostDirectory, 'rust-toolchain.toml'), 'utf8')).match( + /^channel\s*=\s*"([^"]+)"/mu, + )?.[1], inputsSha256: sha256(digests.join('')), guestConcurrency: 'typed-single-program-host-policy', - clockDispatch: 'server-direct-js-16ms-or-1024-reads-shared-setter-fallback-tools-canonical', + clockDispatch: 'adapter-direct-js-16ms-or-1024-reads-shared-setter-fallback-tools-canonical', fdClose: 'typed-filesystem-durability-policy', syncFilesystemBridge: 'realm-local-fresh-owned-js-transfer', toolProtocolWrite: 'owned-js-copy-before-callback', @@ -78,6 +87,16 @@ export async function loadHostBuildContract() { return Object.freeze({ inputs, patchSeries: Object.freeze(patchSeries), provenance }); } +async function adapterSources(directory: string): Promise { + const sources = []; + for (const entry of await readdir(resolve(repositoryRoot, directory), { withFileTypes: true })) { + const path = `${directory}/${entry.name}`; + if (entry.isDirectory()) sources.push(...(await adapterSources(path))); + else if (entry.name.endsWith('.rs')) sources.push(path); + } + return sources.sort(); +} + function validateProtocolTransportContract(contract) { const modes = contract?.modes; if ( diff --git a/src/wasix/browser-host/build-sdk.sh b/src/wasix/browser-host/build-sdk.sh index bab50bcb6..6ceceb9ed 100755 --- a/src/wasix/browser-host/build-sdk.sh +++ b/src/wasix/browser-host/build-sdk.sh @@ -86,10 +86,7 @@ cleanup() { trap cleanup EXIT wasmer_js_dir="$build_root/wasmer-js" -wasmer_wasix_dir="$build_root/wasmer-wasix-$wasmer_wasix_version" -wasmer_dir="$build_root/wasmer-$wasmer_version" -virtual_fs_dir="$build_root/virtual-fs-$virtual_fs_version" -virtual_mio_dir="$build_root/virtual-mio-$virtual_mio_version" +wasmer_dir="$build_root/wasmer" # Use the same bounded transports, exact pins and safe extraction as the other # producers. Only temporary source trees are patched; verified archives persist. @@ -97,20 +94,17 @@ source "$repo_root/src/third-party/tools/fetch-sources.sh" bun - "$source_manifest" "$build_root" <<'JS' import {readFileSync, writeFileSync} from 'node:fs'; const pins = Bun.TOML.parse(readFileSync(process.argv[2], 'utf8')); -for (const name of ['wasmer-js', 'wasmer-wasix', 'wasmer', 'virtual-fs', 'virtual-mio']) { +for (const name of ['wasmer-js', 'wasmer']) { const pin = pins[name]; - const source = name === 'wasmer-js' - ? {name, kind:'git', url:pin.url, branch:'oliphaunt-pinned', commit:pin.commit} - : {name:`${name}-${pin.version}`, kind:'archive', url:pin.url, branch:pin.version, - commit:pin.sha256, sha256:pin.sha256, stripPrefix:`${name}-${pin.version}`}; + const source = {name, kind:'git', url:pin.url, branch:'oliphaunt-pinned', commit:pin.commit}; writeFileSync(`${process.argv[3]}/${name}.json`, JSON.stringify(source)); } JS oliphaunt_acquisition_start 'browser host sources' 1800 -for source_name in wasmer-js wasmer-wasix wasmer virtual-fs virtual-mio; do +for source_name in wasmer-js wasmer; do fetch_source "$build_root/$source_name.json" "$build_root" "$target_parent/archives" fetch done -actual_wasmer_js_version="$(bun "$repo_root/tools/dev/node-info.mts" package-version "$wasmer_js_dir/package.json")" +actual_wasmer_js_version="$(bun "$repo_root/tools/dev/node-info.mts" package-version "$wasmer_js_dir/js/package.json")" if [[ "$actual_wasmer_js_version" != "$wasmer_js_version" ]]; then echo "wasix-ts host build: pinned Wasmer JS version is $actual_wasmer_js_version, expected $wasmer_js_version" >&2 exit 1 @@ -123,18 +117,9 @@ while IFS= read -r patch_name; do ????-wasmer-js-*.patch) patch_dir="$wasmer_js_dir" ;; - ????-wasmer-wasix-*.patch) - patch_dir="$wasmer_wasix_dir" - ;; ????-wasmer-*.patch) patch_dir="$wasmer_dir" ;; - ????-virtual-fs-*.patch) - patch_dir="$virtual_fs_dir" - ;; - ????-virtual-mio-*.patch) - patch_dir="$virtual_mio_dir" - ;; *) echo "wasix-ts host build: patch target is not declared by its canonical name: $patch_name" >&2 exit 1 @@ -143,21 +128,17 @@ while IFS= read -r patch_name; do "$patch_command" --batch --forward --fuzz=0 -d "$patch_dir" -p1 < "$patch_file" done <<< "$patch_series" -install -m 0644 "$host_dir/protocol-contract.generated.rs" "$wasmer_js_dir/src/protocol_contract.rs" - -# The pinned source commit's npm lock predates its package metadata. Patch only -# the missing root metadata and dependencies, then install the integrity-pinned -# graph without allowing the package manager to rewrite it. -npm --prefix "$wasmer_js_dir" ci --ignore-scripts --no-audit --no-fund +bun "$host_dir/prepare-sdk.mts" "$wasmer_js_dir" "$wasmer_dir" +npm --prefix "$wasmer_js_dir/js" ci --ignore-scripts --no-audit --no-fund ( cd "$wasmer_js_dir" - CARGO_TARGET_DIR="$cargo_target_dir" wasm-pack build --release --target=web --weak-refs --no-pack - npm run build:rollup + CARGO_TARGET_DIR="$cargo_target_dir" node js/scripts/build-wasm.mjs ) +bun "$host_dir/bundle-sdk.mts" "$wasmer_js_dir/js" for output in index.mjs worker.mjs wasmer_js_bg.wasm; do - if [[ ! -f "$wasmer_js_dir/dist/$output" ]]; then + if [[ ! -f "$wasmer_js_dir/js/dist/$output" ]]; then echo "wasix-ts host build: expected output missing: dist/$output" >&2 exit 1 fi @@ -168,7 +149,7 @@ if [[ -e "$staging_dir" ]]; then rm -rf -- "$staging_dir" fi mkdir -p "$staging_dir" -cp -R "$wasmer_js_dir/dist" "$staging_dir/dist" +cp -R "$wasmer_js_dir/js/dist" "$staging_dir/dist" cp "$wasmer_js_dir/LICENSE" "$staging_dir/LICENSE" printf '%s\n' "$input_hash" > "$staging_dir/.oliphaunt-input-sha256" bun "$provenance_script" --json > "$staging_dir/provenance.json" diff --git a/src/wasix/browser-host/bundle-sdk.mts b/src/wasix/browser-host/bundle-sdk.mts new file mode 100644 index 000000000..a45ccd3aa --- /dev/null +++ b/src/wasix/browser-host/bundle-sdk.mts @@ -0,0 +1,34 @@ +import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import { resolve } from 'node:path'; + +const directory = resolve(process.argv[2]); +const ts = createRequire(resolve(directory, 'package.json'))('typescript'); +const output = resolve(directory, 'dist'); +await mkdir(output, { recursive: true }); +for (const [input, filename] of [ + ['index.mts', 'index.mjs'], + ['src/browser-worker.ts', 'worker.mjs'], +]) { + const build = await Bun.build({ + entrypoints: [resolve(directory, input)], + target: 'browser', + format: 'esm', + minify: true, + }); + if (!build.success) throw new AggregateError(build.logs, `browser host bundle failed: ${input}`); + await writeFile(resolve(output, filename), await build.outputs[0].text()); +} +await copyFile( + resolve(directory, 'pkg/wasmer_sdk_js_bg.wasm'), + resolve(output, 'wasmer_js_bg.wasm'), +); +const declaration = ts + .transpileDeclaration(await readFile(resolve(directory, 'index.mts'), 'utf8'), { + compilerOptions: { module: ts.ModuleKind.NodeNext, target: ts.ScriptTarget.ES2022 }, + }) + .outputText.replace(/^import .*;\n|^export .*from .*;\n/gmu, ''); +await writeFile( + resolve(output, 'index.d.mts'), + (await readFile(resolve(directory, 'pkg/wasmer_sdk_js.d.ts'), 'utf8')) + declaration, +); diff --git a/src/wasix/browser-host/clock.test.mts b/src/wasix/browser-host/clock.test.mts index 1ef6d98db..973cf4f37 100644 --- a/src/wasix/browser-host/clock.test.mts +++ b/src/wasix/browser-host/clock.test.mts @@ -3,17 +3,17 @@ import { readFileSync } from 'node:fs'; import test from 'node:test'; import { runInNewContext } from 'node:vm'; -// Execute the actual inline JavaScript shipped by the source patch, not a -// second implementation of its clock arithmetic. +// Execute the shipped adapter and platform provider, not duplicate arithmetic. const additions = readFileSync( - new URL('./patches/0013-wasmer-wasix-fast-single-backend-clock.patch', import.meta.url), + new URL('./patches/0045-wasmer-wasix-correct-browser-clock-domains.patch', import.meta.url), 'utf8', ) .split('\n') .filter((line) => line.startsWith('+') && !line.startsWith('+++')) .map((line) => line.slice(1)) .join('\n'); -const javascript = [...additions.matchAll(/inline_js = r#"([\s\S]*?)"#/gu)] +const sources = additions + readFileSync(new URL('./adapter/clock.rs', import.meta.url), 'utf8'); +const javascript = [...sources.matchAll(/inline_js = r#"([\s\S]*?)"#/gu)] .map((match) => match[1].replaceAll('export function ', 'function ')) .join('\n'); @@ -61,54 +61,162 @@ test('direct clock preserves the canonical epoch and bounds fallback intervals', assert.equal(clock(2, 0n, 0), 28); }); -test('linking a setter preserves fast reads; calling it switches all shared-memory readers', () => { - const factories = runInNewContext( - `${javascript}; ({read: oliphauntDirectClockImport, set: oliphauntClockSetImport})`, - { Date: { now: () => 100 }, performance: { now: () => 100 } }, +// Import-only Wasm instances exercise real immutable import bindings, including +// a setter-only side module. They all use the same memory as a linked guest. +function clockModule(memory: WebAssembly.Memory, name: string, callback: Function) { + const string = (value: string) => [value.length, ...Buffer.from(value)]; + const section = (id: number, bytes: number[]) => [id, bytes.length, ...bytes]; + const getter = name === 'clock_time_get'; + const namespace = getter ? 'wasi_snapshot_preview1' : 'wasix_32v1'; + const type = getter ? [0x60, 3, 0x7f, 0x7e, 0x7f, 1, 0x7f] : [0x60, 2, 0x7f, 0x7e, 1, 0x7f]; + const module = new WebAssembly.Module( + new Uint8Array([ + 0, + 97, + 115, + 109, + 1, + 0, + 0, + 0, + ...section(1, [1, ...type]), + ...section(2, [ + 2, + ...string(namespace), + ...string(name), + 0, + 0, + ...string('env'), + ...string('memory'), + 2, + memory.buffer instanceof SharedArrayBuffer ? 3 : 1, + 1, + 2, + ]), + ...section(7, [1, ...string('call'), 0, 0]), + ]), ); - for (const clockId of [0, 1]) { - for (const errno of [0, 28]) { - const memory = new WebAssembly.Memory({ initial: 1, maximum: 2, shared: true }); - let calls = 0; - let time = 100_000_000n; - const fallback = (_id: number, _precision: bigint, pointer: number) => { - calls++; - new DataView(memory.buffer).setBigUint64(pointer, time, true); - return 0; - }; - const main = factories.read(memory, fallback); - const side = factories.read(memory, fallback); - main(clockId, 0n, 0); - side(clockId, 0n, 8); - const setter = factories.set(memory, (_id: number, value: bigint) => { - // WASIX can run pending work before updating its clock offset. - const before = calls; - main(clockId, 0n, 0); - assert.equal(calls, before + 1); - if (errno === 0) time = value; - return errno; - }); - main(clockId, 0n, 0); - side(clockId, 0n, 8); - assert.equal(calls, 2, 'importing the setter must not disable fast reads'); - assert.equal(setter(clockId, 5_000_000_000n), errno); - const late = factories.read(memory, fallback); - memory.grow(1); - for (const read of [main, side, late]) { + return new WebAssembly.Instance(module, { + [namespace]: { [name]: callback }, + env: { memory }, + }).exports.call as (...args: (number | bigint)[]) => number; +} + +for (const clockId of [0, 1]) { + test(`clock setter switches main, existing and late readers together: domain ${clockId}`, () => { + let now = 100; + const factories = runInNewContext( + `${javascript}; ({read: oliphauntDirectClockImport, set: oliphauntClockSetImport})`, + { Date: { now: () => now }, performance: { now: () => now } }, + ); + const memory = new WebAssembly.Memory({ initial: 1, maximum: 2, shared: true }); + const offsets = [0n, 0n]; + let calls = 0; + const fallback = (id: number, _precision: bigint, pointer: number) => { + calls++; + if (pointer > memory.buffer.byteLength - 8) return 28; + new DataView(memory.buffer).setBigUint64( + pointer, + BigInt(now) * 1_000_000n + offsets[id], + true, + ); + return 0; + }; + const main = clockModule(memory, 'clock_time_get', factories.read(memory, fallback)); + const side = clockModule(memory, 'clock_time_get', factories.read(memory, fallback)); + main(clockId, 0n, 0); + side(clockId, 0n, 8); + now++; + main(clockId, 0n, 0); + side(clockId, 0n, 8); + assert.equal(calls, 2); + const setter = clockModule( + memory, + 'clock_time_set', + factories.set(memory, (id: number, target: bigint) => { + // Pending runtime work can reenter a reader before the offset is changed. const before = calls; - read(clockId, 0n, 65_536); + main(id, 0n, 0); assert.equal(calls, before + 1); - assert.equal(new DataView(memory.buffer).getBigUint64(65_536, true), time); - } - const other = new WebAssembly.Memory({ initial: 1 }); - const independent = factories.read(other, () => { - calls++; + offsets[id] = target - BigInt(now) * 1_000_000n; return 0; - }); + }), + ); + // Linking a setter is not a reason to abandon the fast path. + main(clockId, 0n, 0); + assert.equal(calls, 2); + assert.equal(setter(clockId, 5_000_000_000n), 0); + const late = clockModule(memory, 'clock_time_get', factories.read(memory, fallback)); + now++; + for (const read of [main, side, late]) { const before = calls; - independent(clockId, 0n, 0); - independent(clockId, 0n, 0); - assert.equal(calls, before + 1, 'unrelated databases keep their fast path'); + assert.equal(read(clockId, 0n, 0), 0); + assert.equal(calls, before + 1); + assert.equal(new DataView(memory.buffer).getBigUint64(0, true), 5_001_000_000n); } + // Other clock domains also use the same authoritative runtime after a set. + const before = calls; + main(1 - clockId, 0n, 0); + assert.equal(calls, before + 1); + memory.grow(1); + assert.equal(late(clockId, 0n, 65_536), 0); + assert.equal(new DataView(memory.buffer).getBigUint64(65_536, true), 5_001_000_000n); + assert.equal(main(clockId, 0n, memory.buffer.byteLength), 28); + + const other = new WebAssembly.Memory({ initial: 1, maximum: 2 }); + const otherRead = clockModule( + other, + 'clock_time_get', + factories.read(other, () => 0), + ); + otherRead(clockId, 0n, 0); + let otherCalls = 0; + const isolatedRead = factories.read(other, () => { + otherCalls++; + return 0; + }); + isolatedRead(clockId, 0n, 0); + isolatedRead(clockId, 0n, 0); + assert.equal(otherCalls, 1, 'another database must retain its fast path'); + }); +} + +test('setter errors and traps propagate without reviving fast readers', () => { + const factories = runInNewContext( + `${javascript}; ({read: oliphauntDirectClockImport, set: oliphauntClockSetImport})`, + { Date: { now: () => 100 }, performance: { now: () => 100 } }, + ); + for (const trap of [false, true]) { + const memory = new WebAssembly.Memory({ initial: 1, maximum: 2 }); + let calls = 0; + const read = clockModule( + memory, + 'clock_time_get', + factories.read(memory, () => { + calls++; + return 0; + }), + ); + const error = new Error('clock setter trap'); + const set = clockModule( + memory, + 'clock_time_set', + factories.set(memory, () => { + if (trap) throw error; + return 28; + }), + ); + read(0, 0n, 0); + read(0, 0n, 0); + assert.equal(calls, 1); + if (trap) + assert.throws( + () => set(0, 1n), + (thrown) => thrown === error, + ); + else assert.equal(set(0, 1n), 28); + read(0, 0n, 0); + read(0, 0n, 0); + assert.equal(calls, 3); } }); diff --git a/src/wasix/browser-host/index.mts b/src/wasix/browser-host/index.mts new file mode 100644 index 000000000..793fc0131 --- /dev/null +++ b/src/wasix/browser-host/index.mts @@ -0,0 +1,25 @@ +import initialize, { setSDKUrl, setWorkerUrl } from './pkg/wasmer_sdk_js.js'; +import type { InitInput, InitOutput } from './pkg/wasmer_sdk_js.js'; + +export * from './pkg/wasmer_sdk_js.js'; +export { default } from './pkg/wasmer_sdk_js.js'; + +export type WasmerInitOptions = Readonly<{ + module?: InitInput | Promise; + memory?: WebAssembly.Memory; + workerUrl?: string | URL; + sdkUrl?: string | URL; + log?: string; + registryUrl?: string; + token?: string; +}>; + +export async function init(options: WasmerInitOptions = {}): Promise { + const output = await initialize({ + module_or_path: options.module ?? new URL('./wasmer_js_bg.wasm', import.meta.url), + memory: options.memory, + }); + setSDKUrl(String(options.sdkUrl ?? new URL('./index.mjs', import.meta.url))); + setWorkerUrl(String(options.workerUrl ?? new URL('./worker.mjs', import.meta.url))); + return output; +} diff --git a/src/wasix/browser-host/moon.yml b/src/wasix/browser-host/moon.yml index 295835261..643d71415 100644 --- a/src/wasix/browser-host/moon.yml +++ b/src/wasix/browser-host/moon.yml @@ -9,7 +9,7 @@ tasks: test: tags: ["quality", "unit"] command: "bun test src/wasix/browser-host/clock.test.mts" - inputs: ["clock.test.mts", "patches/0013-wasmer-wasix-fast-single-backend-clock.patch"] + inputs: ["clock.test.mts", "adapter/clock.rs", "patches/0045-wasmer-wasix-correct-browser-clock-domains.patch"] options: runFromWorkspaceRoot: true build: diff --git a/src/wasix/browser-host/patches/0001-wasmer-js-run-configured-wasix-process.patch b/src/wasix/browser-host/patches/0001-wasmer-js-run-configured-wasix-process.patch deleted file mode 100644 index c6ee8f4cd..000000000 --- a/src/wasix/browser-host/patches/0001-wasmer-js-run-configured-wasix-process.patch +++ /dev/null @@ -1,120 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: execute the configured WASIX process - -Use the configured WASIX environment and propagate nonzero process exits. -Oliphaunt needs its mounts, program arguments and host policy honored when -running PostgreSQL and frontend tools. This is a general runner correction; -the surrounding Oliphaunt policy remains a downstream integration. - -diff --git a/Cargo.toml b/Cargo.toml -index b21b6eb..65d86be 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -127,7 +127,7 @@ wasm-bindgen-test = "0.3" - - [profile.release] - lto = true --opt-level = 'z' -+opt-level = 3 - - [package.metadata.wasm-pack.profile.release.wasm-bindgen] - debug-js-glue = false -@@ -135,9 +135,13 @@ demangle-name-section = false - dwarf-debug-info = false - - [package.metadata.wasm-pack.profile.release] --wasm-opt = ["--enable-threads", "--enable-bulk-memory", "-Oz"] -+wasm-opt = ["--enable-threads", "--enable-bulk-memory", "-O3"] - - [patch.crates-io] -+wasmer = { path = "../wasmer-6.1.0" } -+wasmer-wasix = { path = "../wasmer-wasix-0.601.0" } -+virtual-fs = { path = "../virtual-fs-0.601.0" } -+virtual-mio = { path = "../virtual-mio-0.601.0" } - #webc = {git = "https://github.com/wasmerio/pirita", branch = "in-memory-manifest"} - #virtual-net = { git = "https://github.com/wasmerio/wasmer", branch = "master" } - #virtual-fs = { git = "https://github.com/wasmerio/wasmer", branch = "master" } -diff --git a/src/run.rs b/src/run.rs -index 6e25018..99ee44e 100644 ---- a/src/run.rs -+++ b/src/run.rs -@@ -1,11 +1,10 @@ - use futures::channel::oneshot; --use wasmer_types::ModuleHash; - use std::sync::Arc; - use wasm_bindgen::{prelude::wasm_bindgen, JsCast}; -+use wasm_bindgen_futures::JsFuture; - use wasmer_wasix::Runtime as _; --use wasmer_wasix::WasiEnvBuilder; --use wasmer_wasix::runtime::module_cache::HashedModuleData; --use wasmer_wasix::runners::wasi::{WasiRunner, RuntimeOrEngine}; -+use wasmer_wasix::runtime::task_manager::VirtualTaskManagerExt; -+use wasmer_wasix::{WasiEnvBuilder, WasiError, WasiRuntimeError}; - - use crate::{instance::ExitCondition, utils::Error, Instance, RunOptions}; - -@@ -43,9 +42,9 @@ async fn run_wasix_inner(wasm_module: WasmModule, config: RunOptions) -> Result< - let (stdin, stdout, stderr) = config.configure_builder(&mut builder)?; - - let (exit_code_tx, exit_code_rx) = oneshot::channel(); -- let mut runner = WasiRunner::new(); - - let module: wasmer::Module = wasm_module.to_module(&*runtime).await?; -+ let module_bytes = module.serialize().unwrap(); - - // Note: The WasiEnvBuilder::run() method blocks, so we need to run it on - // the thread pool. -@@ -54,8 +53,26 @@ async fn run_wasix_inner(wasm_module: WasmModule, config: RunOptions) -> Result< - module, - Box::new(move |module| { - let _span = tracing::debug_span!("run").entered(); -- let hash = ModuleHash::random(); -- let result = runner.run_wasm(RuntimeOrEngine::Runtime(runtime), &program_name, module, hash ); -+ let module: js_sys::WebAssembly::Module = module.into(); -+ let module: wasmer::Module = (module, module_bytes).into(); -+ let result = (|| -> Result<(), anyhow::Error> { -+ let env = builder.build()?; -+ let runtime = env.runtime.clone(); -+ let tasks = runtime.task_manager().clone(); -+ let mut task_handle = -+ wasmer_wasix::bin_factory::spawn_exec_module(module, env, &runtime)?; -+ let result = tasks.spawn_and_block_on(async move { -+ task_handle.wait_finished().await -+ })?; -+ -+ match result { -+ Ok(exit_code) if exit_code.raw() == 0 => Ok(()), -+ Ok(exit_code) => { -+ Err(WasiRuntimeError::Wasi(WasiError::Exit(exit_code)).into()) -+ } -+ Err(err) => Err(anyhow::anyhow!(err.to_string())), -+ } -+ })(); - let _ = exit_code_tx.send(ExitCondition::from_result(result)); - }), - )?; -@@ -77,15 +94,19 @@ extern "C" { - impl WasmModule { - async fn to_module( - &self, -- runtime: &dyn wasmer_wasix::Runtime, -+ _runtime: &dyn wasmer_wasix::Runtime, - ) -> Result { - if let Some(module) = self.dyn_ref::() { - Ok(module.clone().into()) - } else if let Some(buffer) = self.dyn_ref::() { - let buffer = buffer.to_vec(); -- let hashed_module = HashedModuleData::new(&buffer); -- let module: wasmer::Module = runtime.load_hashed_module(hashed_module, None).await?; -- Ok(module) -+ let bytes = js_sys::Uint8Array::from(buffer.as_slice()); -+ let module = JsFuture::from(js_sys::WebAssembly::compile(&bytes)) -+ .await -+ .map_err(Error::js)? -+ .dyn_into::() -+ .map_err(Error::js)?; -+ Ok((module, buffer).into()) - } else { - unreachable!(); - } diff --git a/src/wasix/browser-host/patches/0002-wasmer-wasix-add-0702-compatibility-imports.patch b/src/wasix/browser-host/patches/0002-wasmer-wasix-add-0702-compatibility-imports.patch deleted file mode 100644 index 064fe0ced..000000000 --- a/src/wasix/browser-host/patches/0002-wasmer-wasix-add-0702-compatibility-imports.patch +++ /dev/null @@ -1,111 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: provide explicit compatibility imports - -Resolve newer WASIX guest imports against the pinned host ABI. Unsupported -fork/context operations return explicit failures rather than pretending that -this older host implements them. This compatibility bridge is version-specific; -upstream should adopt the actual ABI implementations rather than these stubs. - -diff --git a/src/lib.rs b/src/lib.rs -index 00615d4..5f50af3 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -76,8 +76,8 @@ - pub use wasmer_wasix_types; - - use wasmer::{ -- imports, namespace, AsStoreMut, Exports, FunctionEnv, Imports, Memory32, MemoryAccessError, -- MemorySize, RuntimeError, -+ imports, namespace, AsStoreMut, Exports, FunctionEnv, FunctionEnvMut, Imports, Memory32, -+ MemoryAccessError, MemorySize, RuntimeError, WasmPtr, - }; - - pub use virtual_fs; -@@ -117,6 +117,34 @@ use crate::{ - }, - }; - -+// Compatibility imports added in WASIX 0.702. Current libc imports all five. -+// Normal termination calls proc_exit2, whose non-vfork behavior matches -+// proc_exit; setjmp-vfork and context switching remain unsupported. -+fn unsupported_proc_fork_env( -+ mut ctx: FunctionEnvMut<'_, WasiEnv>, -+ _child_pid_ptr: WasmPtr, -+) -> Result { -+ WasiEnv::do_pending_operations(&mut ctx)?; -+ Ok(Errno::Notsup) -+} -+ -+fn unsupported_context_create( -+ mut ctx: FunctionEnvMut<'_, WasiEnv>, -+ _new_context_ptr: WasmPtr, -+ _entrypoint: u32, -+) -> Result { -+ WasiEnv::do_pending_operations(&mut ctx)?; -+ Ok(Errno::Notsup) -+} -+ -+fn unsupported_context_operation( -+ mut ctx: FunctionEnvMut<'_, WasiEnv>, -+ _target_context_id: u64, -+) -> Result { -+ WasiEnv::do_pending_operations(&mut ctx)?; -+ Ok(Errno::Notsup) -+} -+ - /// This is returned in `RuntimeError`. - /// Use `downcast` or `downcast_ref` to retrieve the `ExitCode`. - #[derive(Error, Debug)] -@@ -563,6 +591,7 @@ pub fn wasix_exports_32(store: &mut impl AsStoreMut, env: &FunctionEnv) - "poll_oneoff" => Function::new_typed_with_env(&mut store, env, poll_oneoff::), - "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), - "proc_fork" => Function::new_typed_with_env(&mut store, env, proc_fork::), -+ "proc_fork_env" => Function::new_typed_with_env(&mut store, env, unsupported_proc_fork_env::), - "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), - "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal), - "proc_signals_get" => Function::new_typed_with_env(&mut store, env, proc_signals_get::), -@@ -570,6 +599,7 @@ pub fn wasix_exports_32(store: &mut impl AsStoreMut, env: &FunctionEnv) - "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), - "proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::), - "proc_exec3" => Function::new_typed_with_env(&mut store, env, proc_exec3::), -+ "proc_exit2" => Function::new_typed_with_env(&mut store, env, proc_exit::), - "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), - "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), - "proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::), -@@ -597,6 +627,9 @@ pub fn wasix_exports_32(store: &mut impl AsStoreMut, env: &FunctionEnv) - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield::), - "stack_checkpoint" => Function::new_typed_with_env(&mut store, env, stack_checkpoint::), - "stack_restore" => Function::new_typed_with_env(&mut store, env, stack_restore::), -+ "context_create" => Function::new_typed_with_env(&mut store, env, unsupported_context_create::), -+ "context_switch" => Function::new_typed_with_env(&mut store, env, unsupported_context_operation), -+ "context_destroy" => Function::new_typed_with_env(&mut store, env, unsupported_context_operation), - "futex_wait" => Function::new_typed_with_env(&mut store, env, futex_wait::), - "futex_wake" => Function::new_typed_with_env(&mut store, env, futex_wake::), - "futex_wake_all" => Function::new_typed_with_env(&mut store, env, futex_wake_all::), -@@ -703,6 +736,7 @@ pub fn wasix_exports_64(store: &mut impl AsStoreMut, env: &FunctionEnv) - "poll_oneoff" => Function::new_typed_with_env(&mut store, env, poll_oneoff::), - "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), - "proc_fork" => Function::new_typed_with_env(&mut store, env, proc_fork::), -+ "proc_fork_env" => Function::new_typed_with_env(&mut store, env, unsupported_proc_fork_env::), - "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), - "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal), - "proc_signals_get" => Function::new_typed_with_env(&mut store, env, proc_signals_get::), -@@ -710,6 +744,7 @@ pub fn wasix_exports_64(store: &mut impl AsStoreMut, env: &FunctionEnv) - "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), - "proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::), - "proc_exec3" => Function::new_typed_with_env(&mut store, env, proc_exec3::), -+ "proc_exit2" => Function::new_typed_with_env(&mut store, env, proc_exit::), - "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), - "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), - "proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::), -@@ -737,6 +772,9 @@ pub fn wasix_exports_64(store: &mut impl AsStoreMut, env: &FunctionEnv) - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield::), - "stack_checkpoint" => Function::new_typed_with_env(&mut store, env, stack_checkpoint::), - "stack_restore" => Function::new_typed_with_env(&mut store, env, stack_restore::), -+ "context_create" => Function::new_typed_with_env(&mut store, env, unsupported_context_create::), -+ "context_switch" => Function::new_typed_with_env(&mut store, env, unsupported_context_operation), -+ "context_destroy" => Function::new_typed_with_env(&mut store, env, unsupported_context_operation), - "futex_wait" => Function::new_typed_with_env(&mut store, env, futex_wait::), - "futex_wake" => Function::new_typed_with_env(&mut store, env, futex_wake::), - "futex_wake_all" => Function::new_typed_with_env(&mut store, env, futex_wake_all::), diff --git a/src/wasix/browser-host/patches/0003-wasmer-js-install-browser-runtime-devices.patch b/src/wasix/browser-host/patches/0003-wasmer-js-install-browser-runtime-devices.patch deleted file mode 100644 index 9217a0576..000000000 --- a/src/wasix/browser-host/patches/0003-wasmer-js-install-browser-runtime-devices.patch +++ /dev/null @@ -1,42 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: install browser runtime devices - -Install the filesystem devices needed by PostgreSQL and frontend tools in -the configured browser environment. Host-selected execution, clock and close -policies are explicit; they must not be selected by untrusted guest variables. -This is the downstream device/profile composition point. - -diff --git a/src/options.rs b/src/options.rs -index 9fb3958..d074351 100644 ---- a/src/options.rs -+++ b/src/options.rs -@@ -1,8 +1,12 @@ --use std::{collections::BTreeMap, sync::Arc}; -+use std::{ -+ collections::BTreeMap, -+ path::{Path, PathBuf}, -+ sync::Arc, -+}; - - use anyhow::Context; - use js_sys::Array; --use virtual_fs::TmpFileSystem; -+use virtual_fs::{random_file::RandomFile, TmpFileSystem}; - use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue, UnwrapThrowExt}; - use wasmer_wasix::WasiEnvBuilder; - -@@ -226,6 +230,13 @@ impl RunOptions { - - pub(crate) fn filesystem(&self) -> Result { - let root = TmpFileSystem::new(); -+ for path in ["/dev", "/dev/shm"] { -+ virtual_fs::FileSystem::create_dir(&root, Path::new(path)) -+ .with_context(|| format!("Unable to create \"{path}\""))?; -+ } -+ root.new_open_options_ext() -+ .insert_device_file(PathBuf::from("/dev/urandom"), Box::::default()) -+ .context("Unable to create /dev/urandom")?; - - for (dest, fs) in self.mounted_directories()? { - tracing::trace!(%dest, ?fs, "Mounting directory"); diff --git a/src/wasix/browser-host/patches/0005-wasmer-js-use-object-wasm-init.patch b/src/wasix/browser-host/patches/0005-wasmer-js-use-object-wasm-init.patch deleted file mode 100644 index 7114c8ba4..000000000 --- a/src/wasix/browser-host/patches/0005-wasmer-js-use-object-wasm-init.patch +++ /dev/null @@ -1,25 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: use object-form Wasm initialization - -Use the initialization argument shape generated by the pinned wasm-bindgen -version. This removes a compatibility warning without changing the module or -memory supplied by the caller; it belongs with the corresponding upstream -wasm-bindgen update. - -diff --git a/src-js/index.ts b/src-js/index.ts -index e0c303f..8da1d59 100644 ---- a/src-js/index.ts -+++ b/src-js/index.ts -@@ -57,7 +57,10 @@ export const init = async ( - - setRegistry(initValue); - -- let output = await load(initValue.module, initValue.memory); -+ let output = await load({ -+ module_or_path: initValue.module, -+ memory: initValue.memory, -+ }); - if (initValue.log) { - initializeLogger(initValue.log); - } diff --git a/src/wasix/browser-host/patches/0006-wasmer-js-reuse-precompiled-wasix-module.patch b/src/wasix/browser-host/patches/0006-wasmer-js-reuse-precompiled-wasix-module.patch deleted file mode 100644 index ba690ab2d..000000000 --- a/src/wasix/browser-host/patches/0006-wasmer-js-reuse-precompiled-wasix-module.patch +++ /dev/null @@ -1,427 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: reuse compiled modules and finish after pool idle - -Accept an already compiled WebAssembly module alongside original bytes so -repeated process launches do not compile identical code again. Wait for the -worker pool to become idle before publishing process completion, including -nested tasks. Reuse and scheduler lifecycle changes should be reviewed as -separate general-purpose upstream changes. - -diff --git a/src/options.rs b/src/options.rs -index d074351f..967ac166 100644 ---- a/src/options.rs -+++ b/src/options.rs -@@ -55,6 +55,8 @@ type CommonOptions = { - export type RunOptions = CommonOptions & { - /** The name of the program being run (passed in as arg 0) */ - program?: string; -+ /** Original bytes for a precompiled WebAssembly.Module. */ -+ moduleBytes?: Uint8Array; - /** - * The WASIX runtime to use. - * -@@ -175,6 +177,9 @@ extern "C" { - #[wasm_bindgen(method, getter)] - pub(crate) fn program(this: &RunOptions) -> JsValue; - -+ #[wasm_bindgen(method, getter, js_name = moduleBytes)] -+ pub(crate) fn module_bytes(this: &RunOptions) -> Option; -+ - #[wasm_bindgen(method, getter)] - pub(crate) fn runtime(this: &RunOptions) -> OptionalRuntime; - -diff --git a/src/run.rs b/src/run.rs -index 99ee44ef..d4d485fd 100644 ---- a/src/run.rs -+++ b/src/run.rs -@@ -1,15 +1,50 @@ - use futures::channel::oneshot; --use std::sync::Arc; -+use std::sync::{Arc, Mutex}; - use wasm_bindgen::{prelude::wasm_bindgen, JsCast}; - use wasm_bindgen_futures::JsFuture; -+use wasmer_wasix::types::wasi::ExitCode; - use wasmer_wasix::Runtime as _; --use wasmer_wasix::runtime::task_manager::VirtualTaskManagerExt; - use wasmer_wasix::{WasiEnvBuilder, WasiError, WasiRuntimeError}; - --use crate::{instance::ExitCondition, utils::Error, Instance, RunOptions}; -+use crate::{ -+ instance::ExitCondition, -+ tasks::{ThreadPool, WorkerMessage}, -+ utils::Error, -+ Instance, RunOptions, -+}; - - const DEFAULT_PROGRAM_NAME: &str = "wasm"; - -+type ExitSender = Arc>>>; -+ -+fn complete_when_pool_idle( -+ exit_code_tx: ExitSender, -+ thread_pool: Arc, -+ exit: ExitCondition, -+) { -+ // The scheduler may still be returning a nested WASM task to its worker. -+ // Transfer the completion callback to the scheduler and resolve wait() -+ // only after all blocking workers in this process-owned pool are idle. -+ let _ = WorkerMessage::RunWhenPoolIdle(Box::new(move || { -+ let Some(exit_code_tx) = exit_code_tx.lock().unwrap().take() else { -+ return; -+ }; -+ let _ = exit_code_tx.send(exit); -+ thread_pool.close(); -+ })) -+ .emit(); -+} -+ -+fn task_exit_condition(result: Result>) -> ExitCondition { -+ let result = match result { -+ Ok(exit_code) if exit_code.raw() == 0 => Ok(()), -+ Ok(exit_code) => Err(WasiRuntimeError::Wasi(WasiError::Exit(exit_code)).into()), -+ Err(err) => Err(anyhow::anyhow!(err.to_string())), -+ }; -+ -+ ExitCondition::from_result(result) -+} -+ - /// Run a WASIX program. - /// - /// # WASI Compatibility -@@ -30,8 +65,11 @@ pub async fn run_wasix(wasm_module: WasmModule, config: RunOptions) -> Result Result { - let mut runtime = config.runtime().resolve()?.into_inner(); -- // We set it up with the default pool -- runtime = Arc::new(runtime.with_default_pool()); -+ // A process owns its scheduler and blocking workers. Isolating each run -+ // prevents completed workers and pending callbacks from leaking into the -+ // next process. -+ let thread_pool = Arc::new(ThreadPool::new()); -+ runtime = Arc::new(runtime.with_task_manager(thread_pool.clone())); - - let program_name = config - .program() -@@ -42,38 +80,59 @@ async fn run_wasix_inner(wasm_module: WasmModule, config: RunOptions) -> Result< - let (stdin, stdout, stderr) = config.configure_builder(&mut builder)?; - - let (exit_code_tx, exit_code_rx) = oneshot::channel(); -+ let exit_code_tx = Arc::new(Mutex::new(Some(exit_code_tx))); - -- let module: wasmer::Module = wasm_module.to_module(&*runtime).await?; -- let module_bytes = module.serialize().unwrap(); -+ let (module, module_bytes) = wasm_module -+ .to_module(&*runtime, config.module_bytes().map(|bytes| bytes.to_vec())) -+ .await?; - - // Note: The WasiEnvBuilder::run() method blocks, so we need to run it on - // the thread pool. - let tasks = runtime.task_manager().clone(); -+ let completion_tasks = tasks.clone(); - tasks.spawn_with_module( - module, - Box::new(move |module| { - let _span = tracing::debug_span!("run").entered(); - let module: js_sys::WebAssembly::Module = module.into(); - let module: wasmer::Module = (module, module_bytes).into(); -- let result = (|| -> Result<(), anyhow::Error> { -+ let result = (|| -> Result<_, anyhow::Error> { - let env = builder.build()?; - let runtime = env.runtime.clone(); -- let tasks = runtime.task_manager().clone(); -- let mut task_handle = -- wasmer_wasix::bin_factory::spawn_exec_module(module, env, &runtime)?; -- let result = tasks.spawn_and_block_on(async move { -- task_handle.wait_finished().await -- })?; -- -- match result { -- Ok(exit_code) if exit_code.raw() == 0 => Ok(()), -- Ok(exit_code) => { -- Err(WasiRuntimeError::Wasi(WasiError::Exit(exit_code)).into()) -+ Ok(wasmer_wasix::bin_factory::spawn_exec_module( -+ module, env, &runtime, -+ )?) -+ })(); -+ -+ match result { -+ Ok(mut task_handle) => { -+ let exit_code_tx_for_waiter = exit_code_tx.clone(); -+ let thread_pool_for_waiter = thread_pool.clone(); -+ let schedule_result = completion_tasks.task_shared(Box::new(move || { -+ Box::pin(async move { -+ let exit = task_exit_condition(task_handle.wait_finished().await); -+ complete_when_pool_idle( -+ exit_code_tx_for_waiter, -+ thread_pool_for_waiter, -+ exit, -+ ); -+ }) -+ })); -+ -+ if let Err(error) = schedule_result { -+ complete_when_pool_idle( -+ exit_code_tx, -+ thread_pool, -+ ExitCondition::from_result(Err(error.into())), -+ ); - } -- Err(err) => Err(anyhow::anyhow!(err.to_string())), - } -- })(); -- let _ = exit_code_tx.send(ExitCondition::from_result(result)); -+ Err(error) => complete_when_pool_idle( -+ exit_code_tx, -+ thread_pool, -+ ExitCondition::from_result(Err(error)), -+ ), -+ } - }), - )?; - -@@ -95,9 +154,15 @@ impl WasmModule { - async fn to_module( - &self, - _runtime: &dyn wasmer_wasix::Runtime, -- ) -> Result { -+ original_bytes: Option>, -+ ) -> Result<(wasmer::Module, Vec), Error> { - if let Some(module) = self.dyn_ref::() { -- Ok(module.clone().into()) -+ let bytes = original_bytes.ok_or_else(|| { -+ anyhow::anyhow!( -+ "runWasix requires moduleBytes when passed a precompiled WebAssembly.Module" -+ ) -+ })?; -+ Ok((module.clone().into(), bytes)) - } else if let Some(buffer) = self.dyn_ref::() { - let buffer = buffer.to_vec(); - let bytes = js_sys::Uint8Array::from(buffer.as_slice()); -@@ -106,7 +171,7 @@ impl WasmModule { - .map_err(Error::js)? - .dyn_into::() - .map_err(Error::js)?; -- Ok((module, buffer).into()) -+ Ok(((module, buffer.clone()).into(), buffer)) - } else { - unreachable!(); - } -diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs -index 6d185001..2e9b0eeb 100644 ---- a/src/tasks/mod.rs -+++ b/src/tasks/mod.rs -@@ -53,5 +53,5 @@ pub(crate) use self::{ - use std::{future::Future, pin::Pin}; - - type AsyncTask = Box Pin + 'static>> + Send + 'static>; --type BlockingTask = Box; -+pub(crate) type BlockingTask = Box; - type BlockingModuleTask = Box; -diff --git a/src/tasks/scheduler.rs b/src/tasks/scheduler.rs -index 95ed5d1e..800a5428 100644 ---- a/src/tasks/scheduler.rs -+++ b/src/tasks/scheduler.rs -@@ -5,6 +5,7 @@ use std::{ - }; - - use anyhow::{Context, Error}; -+use derivative::Derivative; - use tokio::sync::mpsc::UnboundedSender; - use tokio::sync::mpsc::{self}; - use tracing::Instrument; -@@ -13,8 +14,8 @@ use wasmer::js::AsJs; - use wasmer_types::ModuleHash; - - use crate::tasks::{ -- AsyncJob, BlockingJob, Notification, PostMessagePayload, SchedulerMessage, WorkerHandle, -- WorkerMessage, -+ AsyncJob, BlockingJob, BlockingTask, Notification, PostMessagePayload, SchedulerMessage, -+ WorkerHandle, WorkerMessage, - }; - - /// A handle for interacting with the threadpool's scheduler. -@@ -125,7 +126,8 @@ impl Drop for Scheduler { - } - - /// The state for the actor in charge of the threadpool. --#[derive(Debug)] -+#[derive(Derivative)] -+#[derivative(Debug)] - struct SchedulerState { - /// Workers that are able to receive work. - idle: VecDeque, -@@ -135,6 +137,8 @@ struct SchedulerState { - /// A channel that can be used to send messages to this scheduler. - mailbox: Scheduler, - cached_modules: BTreeMap, -+ #[derivative(Debug(format_with = "crate::utils::hidden"))] -+ run_when_pool_idle: VecDeque, - } - - impl SchedulerState { -@@ -144,6 +148,7 @@ impl SchedulerState { - busy: VecDeque::new(), - mailbox, - cached_modules: BTreeMap::new(), -+ run_when_pool_idle: VecDeque::new(), - } - } - -@@ -209,6 +214,11 @@ impl SchedulerState { - ); - Ok(()) - } -+ SchedulerMessage::RunWhenPoolIdle(task) => { -+ self.run_when_pool_idle.push_back(task); -+ self.run_pool_idle_callbacks(); -+ Ok(()) -+ } - SchedulerMessage::WorkerIdle { worker_id } => { - move_worker(worker_id, &mut self.busy, &mut self.idle); - tracing::trace!( -@@ -217,12 +227,22 @@ impl SchedulerState { - busy_workers=?self.busy.iter().map(|w| w.id()).collect::>(), - "Worker marked as idle", - ); -+ self.run_pool_idle_callbacks(); - Ok(()) - } - SchedulerMessage::Markers { uninhabited, .. } => match uninhabited {}, - } - } - -+ fn run_pool_idle_callbacks(&mut self) { -+ if !self.busy.is_empty() { -+ return; -+ } -+ while let Some(task) = self.run_when_pool_idle.pop_front() { -+ task(); -+ } -+ } -+ - /// Send a task to one of the worker threads, preferring workers that aren't - /// running synchronous work. - fn post_message(&mut self, msg: PostMessagePayload) -> Result<(), Error> { -diff --git a/src/tasks/scheduler_message.rs b/src/tasks/scheduler_message.rs -index f142a0df..43af3233 100644 ---- a/src/tasks/scheduler_message.rs -+++ b/src/tasks/scheduler_message.rs -@@ -31,6 +31,8 @@ pub(crate) enum SchedulerMessage { - WorkerIdle { worker_id: u32 }, - /// Mark a worker as busy. - WorkerBusy { worker_id: u32 }, -+ /// Run a callback after every blocking worker becomes idle. -+ RunWhenPoolIdle(#[derivative(Debug(format_with = "crate::utils::hidden"))] BlockingTask), - /// Tell all workers to cache a WebAssembly module. - #[allow(dead_code)] - CacheModule { -@@ -82,6 +84,10 @@ impl SchedulerMessage { - let worker_id = de.serde(consts::WORKER_ID)?; - Ok(SchedulerMessage::WorkerBusy { worker_id }) - } -+ consts::TYPE_RUN_WHEN_POOL_IDLE => { -+ let task = de.boxed(consts::PTR)?; -+ Ok(SchedulerMessage::RunWhenPoolIdle(task)) -+ } - consts::TYPE_CACHE_MODULE => { - let hash = de.string(consts::MODULE_HASH)?; - let hash = if let Ok(hash) = ModuleHash::sha256_parse_hex(&hash) { -@@ -146,6 +152,11 @@ impl SchedulerMessage { - SchedulerMessage::WorkerBusy { worker_id } => Serializer::new(consts::TYPE_WORKER_BUSY) - .set(consts::WORKER_ID, worker_id) - .finish(), -+ SchedulerMessage::RunWhenPoolIdle(task) => { -+ Serializer::new(consts::TYPE_RUN_WHEN_POOL_IDLE) -+ .boxed(consts::PTR, task) -+ .finish() -+ } - SchedulerMessage::CacheModule { hash, module } => { - Serializer::new(consts::TYPE_CACHE_MODULE) - .set(consts::MODULE_HASH, hash.to_string()) -@@ -185,6 +196,7 @@ mod consts { - pub const TYPE_SPAWN_BLOCKING: &str = "spawn-blocking"; - pub const TYPE_WORKER_IDLE: &str = "worker-idle"; - pub const TYPE_WORKER_BUSY: &str = "worker-busy"; -+ pub const TYPE_RUN_WHEN_POOL_IDLE: &str = "run-when-pool-idle"; - pub const TYPE_CACHE_MODULE: &str = "cache-module"; - pub const TYPE_SPAWN_WITH_MODULE: &str = "spawn-with-module"; - pub const TYPE_SPAWN_WITH_MODULE_AND_MEMORY: &str = "spawn-with-module-and-memory"; -diff --git a/src/tasks/worker_handle.rs b/src/tasks/worker_handle.rs -index 6e2a6954..983e07e8 100644 ---- a/src/tasks/worker_handle.rs -+++ b/src/tasks/worker_handle.rs -@@ -108,6 +108,7 @@ fn on_message(msg: web_sys::MessageEvent, sender: &Scheduler, worker_id: u32) { - let msg = match base_msg { - WorkerMessage::MarkBusy => SchedulerMessage::WorkerBusy { worker_id }, - WorkerMessage::MarkIdle => SchedulerMessage::WorkerIdle { worker_id }, -+ WorkerMessage::RunWhenPoolIdle(task) => SchedulerMessage::RunWhenPoolIdle(task), - WorkerMessage::Scheduler(msg) => msg, - }; - sender.send(msg).map_err(|_| Error::msg("Send failed")) -diff --git a/src/tasks/worker_message.rs b/src/tasks/worker_message.rs -index cfc54ba2..dd4b95d6 100644 ---- a/src/tasks/worker_message.rs -+++ b/src/tasks/worker_message.rs -@@ -1,21 +1,25 @@ -+use derivative::Derivative; - use wasm_bindgen::{JsCast, JsValue}; - use web_sys::DedicatedWorkerGlobalScope; - - use crate::{ - tasks::{ - interop::{Deserializer, Serializer}, -- SchedulerMessage, -+ BlockingTask, SchedulerMessage, - }, - utils::Error, - }; - - /// A message the worker sends back to the scheduler. --#[derive(Debug)] -+#[derive(Derivative)] -+#[derivative(Debug)] - pub(crate) enum WorkerMessage { - /// Mark this worker as busy. - MarkBusy, - /// Mark this worker as idle. - MarkIdle, -+ /// Run a callback after every blocking worker becomes idle. -+ RunWhenPoolIdle(#[derivative(Debug(format_with = "crate::utils::hidden"))] BlockingTask), - Scheduler(SchedulerMessage), - } - -@@ -26,6 +30,10 @@ impl WorkerMessage { - match de.ty()?.as_str() { - consts::TYPE_BUSY => Ok(WorkerMessage::MarkBusy), - consts::TYPE_IDLE => Ok(WorkerMessage::MarkIdle), -+ consts::TYPE_RUN_WHEN_POOL_IDLE => { -+ let task = de.boxed(consts::TASK)?; -+ Ok(WorkerMessage::RunWhenPoolIdle(task)) -+ } - consts::TYPE_SCHEDULER => { - let value: JsValue = de.js(consts::MESSAGE)?; - let msg = SchedulerMessage::try_from_js(value)?; -@@ -39,6 +47,11 @@ impl WorkerMessage { - match self { - WorkerMessage::MarkBusy => Serializer::new(consts::TYPE_BUSY).finish(), - WorkerMessage::MarkIdle => Serializer::new(consts::TYPE_IDLE).finish(), -+ WorkerMessage::RunWhenPoolIdle(task) => { -+ Serializer::new(consts::TYPE_RUN_WHEN_POOL_IDLE) -+ .boxed(consts::TASK, task) -+ .finish() -+ } - WorkerMessage::Scheduler(msg) => { - let msg = msg.into_js()?; - Serializer::new(consts::TYPE_SCHEDULER) -@@ -72,8 +85,10 @@ impl WorkerMessage { - mod consts { - pub const TYPE_BUSY: &str = "busy"; - pub const TYPE_IDLE: &str = "idle"; -+ pub const TYPE_RUN_WHEN_POOL_IDLE: &str = "run-when-pool-idle"; - pub const TYPE_SCHEDULER: &str = "scheduler"; - pub const MESSAGE: &str = "msg"; -+ pub const TASK: &str = "task"; - } - - #[cfg(test)] diff --git a/src/wasix/browser-host/patches/0007-wasmer-js-run-oliphaunt-direct.patch b/src/wasix/browser-host/patches/0007-wasmer-js-run-oliphaunt-direct.patch deleted file mode 100644 index f1496b391..000000000 --- a/src/wasix/browser-host/patches/0007-wasmer-js-run-oliphaunt-direct.patch +++ /dev/null @@ -1,760 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: drive the embedded PostgreSQL guest directly - -Add a same-realm synchronous driver for the explicit embedded guest exports. -It preserves typed startup/recovery outcomes and owns the guest lifecycle, -removing worker message transport from browser-main execution. This is an -Oliphaunt-specific ABI adapter, not a generic PostgreSQL or WASIX interface; -the series adds bounded transport and fail-closed phases on top. - -diff --git a/src/lib.rs b/src/lib.rs -index 7b22da5..6803f8e 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -12,6 +12,7 @@ mod logging; - mod net; - mod options; - mod package_loader; -+mod postgres_direct; - pub mod registry; - mod run; - mod runtime; -@@ -29,6 +30,7 @@ pub use crate::{ - js_runtime::{JsRuntime, RuntimeOptions}, - logging::initialize_logger, - options::{RunOptions, SpawnOptions}, -+ postgres_direct::{instantiate_oliphaunt_direct, OliphauntDirectInstance}, - registry::RegistryConfig, - run::run_wasix, - utils::StringOrBytes, -diff --git a/src/options.rs b/src/options.rs -index 967ac16..d56c81d 100644 ---- a/src/options.rs -+++ b/src/options.rs -@@ -186,6 +186,19 @@ extern "C" { - } - - impl RunOptions { -+ pub(crate) fn configure_direct_builder( -+ &self, -+ builder: &mut WasiEnvBuilder, -+ ) -> Result<(), Error> { -+ self.configure_common_builder(builder)?; -+ -+ builder.set_stdin(Box::::default()); -+ builder.set_stdout(Box::::default()); -+ builder.set_stderr(Box::::default()); -+ -+ Ok(()) -+ } -+ - /// Propagate any provided options to the [`WasiEnvBuilder`], returning - /// streams that can be used for stdin/stdout/stderr. - pub(crate) fn configure_builder( -@@ -199,13 +212,7 @@ impl RunOptions { - ), - Error, - > { -- for arg in self.parse_args()? { -- builder.add_arg(arg); -- } -- -- for (key, value) in self.parse_env()? { -- builder.add_env(key, value); -- } -+ self.configure_common_builder(builder)?; - - let stdin = match self.read_stdin() { - Some(stdin) => { -@@ -226,11 +233,29 @@ impl RunOptions { - let (stderr_file, stderr) = crate::streams::output_pipe(); - builder.set_stderr(Box::new(stderr_file)); - -+ Ok((stdin, stdout, stderr)) -+ } -+ -+ fn configure_common_builder( -+ &self, -+ builder: &mut WasiEnvBuilder, -+ ) -> Result<(), Error> { -+ for arg in self.parse_args()? { -+ builder.add_arg(arg); -+ } -+ -+ for (key, value) in self.parse_env()? { -+ builder.add_env(key, value); -+ } -+ -+ if let Some(cwd) = self.parse_cwd()? { -+ builder.set_current_dir(cwd); -+ } - let fs = self.filesystem()?; - builder.set_fs(Box::new(fs)); - builder.add_preopen_dir("/")?; - -- Ok((stdin, stdout, stderr)) -+ Ok(()) - } - - pub(crate) fn filesystem(&self) -> Result { -diff --git a/src/postgres_direct.rs b/src/postgres_direct.rs -new file mode 100644 -index 0000000..c45000a ---- /dev/null -+++ b/src/postgres_direct.rs -@@ -0,0 +1,565 @@ -+use std::sync::Arc; -+ -+use anyhow::{ensure, Context}; -+use js_sys::{Uint8Array, WebAssembly}; -+use wasm_bindgen::prelude::wasm_bindgen; -+use wasmer::{Instance as WasmerInstance, Store, TypedFunction, Value, WasmTypeList}; -+use wasmer_wasix::{Runtime as _, WasiEnvBuilder, WasiError, WasiFunctionEnv}; -+ -+use crate::{runtime::Runtime, tasks::CallerRealmTaskManager, utils::Error, RunOptions}; -+ -+const DEFAULT_PROGRAM_NAME: &str = "/bin/postgres"; -+const OLIPHAUNT_EXIT_ALIVE: i32 = 99; -+ -+#[derive(Debug, Clone, Copy, PartialEq, Eq)] -+enum MainLoopOutcome { -+ Processed, -+ Recovered, -+ InputEnded, -+} -+ -+impl MainLoopOutcome { -+ fn from_i32(value: i32) -> Option { -+ match value { -+ 0 => Some(Self::Processed), -+ 1 => Some(Self::Recovered), -+ 2 => Some(Self::InputEnded), -+ _ => None, -+ } -+ } -+} -+ -+/// Instantiate the integrated Oliphaunt/PostgreSQL guest in this JS realm. -+/// -+/// The returned driver is synchronous by design: every guest export runs on -+/// the caller's stack, and this code never constructs a Worker or ThreadPool. -+#[wasm_bindgen(js_name = instantiateOliphauntDirect)] -+pub async fn instantiate_oliphaunt_direct( -+ module: WebAssembly::Module, -+ module_bytes: Uint8Array, -+ options: RunOptions, -+) -> Result { -+ OliphauntDirectInstance::instantiate(module, module_bytes, options).await -+} -+ -+#[wasm_bindgen] -+pub struct OliphauntDirectInstance { -+ // Runtime and instance must outlive every function handle and the Store. -+ _runtime: Arc, -+ store: Store, -+ _instance: WasmerInstance, -+ env: WasiFunctionEnv, -+ malloc: TypedFunction, -+ free: TypedFunction, -+ input_reset: TypedFunction<(), i32>, -+ input_write: TypedFunction<(i32, i32), i32>, -+ input_available: TypedFunction<(), i32>, -+ output_reset: TypedFunction<(), i32>, -+ output_len: TypedFunction<(), i32>, -+ output_read: TypedFunction<(i32, i32), i32>, -+ set_active: TypedFunction, -+ wasi_start: TypedFunction<(), ()>, -+ start_oliphaunt: TypedFunction<(), ()>, -+ run_atexit_funcs: Option>, -+ get_port: TypedFunction<(), i32>, -+ process_startup: TypedFunction<(i32, i32, i32), i32>, -+ send_conn_data: TypedFunction<(), ()>, -+ pq_flush: TypedFunction<(), ()>, -+ pq_buffer_remaining_data: TypedFunction<(), i32>, -+ main_loop: TypedFunction<(), i32>, -+ send_ready: TypedFunction<(), ()>, -+ backend_started: bool, -+ protocol_started: bool, -+ closed: bool, -+} -+ -+#[wasm_bindgen] -+impl OliphauntDirectInstance { -+ /// Start PostgreSQL and process one frontend startup packet. -+ #[wasm_bindgen(js_name = startup)] -+ pub fn startup(&mut self, packet: Uint8Array) -> Result { -+ match self.startup_inner(&packet.to_vec()) { -+ Ok(output) => Ok(Uint8Array::from(output.as_slice())), -+ Err(error) => Err(self.startup_error(error)), -+ } -+ } -+ -+ /// Execute raw PostgreSQL frontend-protocol bytes synchronously. -+ #[wasm_bindgen(js_name = execProtocolRaw)] -+ pub fn exec_protocol_raw(&mut self, input: Uint8Array) -> Result { -+ self.exec_protocol_raw_inner(&input.to_vec()) -+ .map(|output| Uint8Array::from(output.as_slice())) -+ .map_err(Error::from) -+ } -+ -+ /// Shut down the embedded lifecycle. This does not consume the JS object. -+ pub fn close(&mut self) -> Result<(), Error> { -+ self.close_inner().map_err(Error::from) -+ } -+} -+ -+impl OliphauntDirectInstance { -+ fn startup_inner(&mut self, packet: &[u8]) -> anyhow::Result> { -+ self.ensure_open()?; -+ ensure!( -+ !self.protocol_started, -+ "Oliphaunt direct startup already completed" -+ ); -+ self.start_backend()?; -+ self.reset_io()?; -+ self.push_input(packet)?; -+ -+ let port = self -+ .get_port -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_get_proc_port")?; -+ ensure!(port > 0, "oliphaunt_wasix_get_proc_port returned null"); -+ let status = self -+ .process_startup -+ .call(&mut self.store, port, 1, 1) -+ .context("ProcessStartupPacket")?; -+ self.pq_flush.call(&mut self.store).ok(); -+ if status == 0 { -+ self.send_conn_data -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_send_conn_data")?; -+ self.pq_flush -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_pq_flush after startup")?; -+ self.protocol_started = true; -+ } -+ self.take_output() -+ } -+ -+ fn exec_protocol_raw_inner(&mut self, payload: &[u8]) -> anyhow::Result> { -+ self.ensure_open()?; -+ ensure!( -+ self.protocol_started, -+ "Oliphaunt direct startup has not completed" -+ ); -+ if payload.is_empty() { -+ return Ok(Vec::new()); -+ } -+ -+ self.reset_io()?; -+ self.push_input(&payload)?; -+ let max_attempts = (payload.len() / 5).saturating_add(2).max(1); -+ let mut attempts = 0usize; -+ while self.protocol_input_remaining()? > 0 { -+ attempts += 1; -+ ensure!( -+ attempts <= max_attempts, -+ "PostgreSQL direct protocol pump did not drain input" -+ ); -+ match self.call_main_loop()? { -+ MainLoopOutcome::Processed | MainLoopOutcome::Recovered => {} -+ MainLoopOutcome::InputEnded => { -+ return Err(self.terminal_main_loop_outcome( -+ "PostgresMainLoopOnce reported input end while dispatching buffered protocol input", -+ )); -+ } -+ } -+ } -+ self.send_ready_after_main_loop()?; -+ self.flush_after_main_loop()?; -+ self.take_output() -+ } -+ -+ fn close_inner(&mut self) -> anyhow::Result<()> { -+ if self.closed { -+ return Ok(()); -+ } -+ self.set_active -+ .call(&mut self.store, 0) -+ .context("oliphaunt_wasix_set_active(0)")?; -+ if let Some(run_atexit_funcs) = &self.run_atexit_funcs { -+ run_atexit_funcs -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_run_atexit_funcs")?; -+ } -+ self.closed = true; -+ self.backend_started = false; -+ self.protocol_started = false; -+ Ok(()) -+ } -+ async fn instantiate( -+ module: WebAssembly::Module, -+ module_bytes: Uint8Array, -+ options: RunOptions, -+ ) -> Result { -+ // Direct execution intentionally has no configurable networking or -+ // worker-backed runtime. RunOptions is reused for args/env/fs mounts. -+ let runtime = -+ Arc::new(Runtime::with_defaults()?.with_task_manager(Arc::new(CallerRealmTaskManager))); -+ let program_name = options -+ .program() -+ .as_string() -+ .unwrap_or_else(|| DEFAULT_PROGRAM_NAME.to_owned()); -+ let mut builder = WasiEnvBuilder::new(program_name).runtime(runtime.clone()); -+ options.configure_direct_builder(&mut builder)?; -+ -+ let module = wasmer::Module::from((module, module_bytes.to_vec())); -+ let mut store = Store::new(runtime.engine()); -+ let (instance, env) = builder -+ .instantiate_async(module, &mut store) -+ .await -+ .context("instantiate Oliphaunt direct WASIX module")?; -+ seed_exported_c_string( -+ &mut store, -+ &instance, -+ &env, -+ "my_exec_path", -+ DEFAULT_PROGRAM_NAME, -+ )?; -+ -+ let malloc = typed_export(&mut store, &instance, "malloc")?; -+ let free = typed_export(&mut store, &instance, "pg_free") -+ .or_else(|_| typed_export(&mut store, &instance, "free"))?; -+ let input_reset = typed_export(&mut store, &instance, "oliphaunt_wasix_input_reset")?; -+ let input_write = typed_export(&mut store, &instance, "oliphaunt_wasix_input_write")?; -+ let input_available = -+ typed_export(&mut store, &instance, "oliphaunt_wasix_input_available")?; -+ let output_reset = typed_export(&mut store, &instance, "oliphaunt_wasix_output_reset")?; -+ let output_len = typed_export(&mut store, &instance, "oliphaunt_wasix_output_len")?; -+ let output_read = typed_export(&mut store, &instance, "oliphaunt_wasix_output_read")?; -+ let set_active = typed_export(&mut store, &instance, "oliphaunt_wasix_set_active")?; -+ let wasi_start = typed_export(&mut store, &instance, "_start")?; -+ let start_oliphaunt = typed_export(&mut store, &instance, "oliphaunt_wasix_start")?; -+ let run_atexit_funcs = -+ optional_typed_export(&mut store, &instance, "oliphaunt_wasix_run_atexit_funcs")?; -+ let get_port = typed_export(&mut store, &instance, "oliphaunt_wasix_get_proc_port")?; -+ let process_startup = typed_export(&mut store, &instance, "ProcessStartupPacket")?; -+ let send_conn_data = typed_export(&mut store, &instance, "oliphaunt_wasix_send_conn_data")?; -+ let pq_flush = typed_export(&mut store, &instance, "oliphaunt_wasix_pq_flush")?; -+ let pq_buffer_remaining_data = -+ typed_export(&mut store, &instance, "pq_buffer_remaining_data")?; -+ let main_loop = typed_export(&mut store, &instance, "PostgresMainLoopOnce")?; -+ let send_ready = typed_export( -+ &mut store, -+ &instance, -+ "PostgresSendReadyForQueryIfNecessary", -+ )?; -+ -+ let mut direct = Self { -+ _runtime: runtime, -+ store, -+ _instance: instance, -+ env, -+ malloc, -+ free, -+ input_reset, -+ input_write, -+ input_available, -+ output_reset, -+ output_len, -+ output_read, -+ set_active, -+ wasi_start, -+ start_oliphaunt, -+ run_atexit_funcs, -+ get_port, -+ process_startup, -+ send_conn_data, -+ pq_flush, -+ pq_buffer_remaining_data, -+ main_loop, -+ send_ready, -+ backend_started: false, -+ protocol_started: false, -+ closed: false, -+ }; -+ direct.reset_io()?; -+ Ok(direct) -+ } -+ -+ fn ensure_open(&self) -> anyhow::Result<()> { -+ ensure!(!self.closed, "Oliphaunt direct instance is closed"); -+ Ok(()) -+ } -+ -+ fn poison_main_loop(&mut self) { -+ // A trap, invalid typed outcome, or failed post-step export can leave -+ // arbitrary guest state behind. Do not call another guest export, -+ // including the ordinary close sequence. -+ self.closed = true; -+ self.backend_started = false; -+ self.protocol_started = false; -+ } -+ -+ fn terminal_main_loop_outcome(&mut self, failure: impl Into) -> anyhow::Error { -+ let failure = failure.into(); -+ self.poison_main_loop(); -+ anyhow::anyhow!("{failure}; the direct instance is closed") -+ } -+ -+ fn terminal_main_loop_error(&mut self, error: wasmer::RuntimeError) -> anyhow::Error { -+ self.poison_main_loop(); -+ anyhow::Error::from(error).context( -+ "PostgresMainLoopOnce trapped instead of returning a typed outcome; \ -+ the direct instance is closed", -+ ) -+ } -+ -+ fn call_main_loop(&mut self) -> anyhow::Result { -+ let status = match self.main_loop.call(&mut self.store) { -+ Ok(status) => status, -+ Err(error) => return Err(self.terminal_main_loop_error(error)), -+ }; -+ MainLoopOutcome::from_i32(status).ok_or_else(|| { -+ self.terminal_main_loop_outcome(format!( -+ "PostgresMainLoopOnce returned invalid typed outcome {status}" -+ )) -+ }) -+ } -+ -+ fn send_ready_after_main_loop(&mut self) -> anyhow::Result<()> { -+ match self.send_ready.call(&mut self.store) { -+ Ok(()) => Ok(()), -+ Err(error) => { -+ self.poison_main_loop(); -+ Err(anyhow::Error::from(error).context( -+ "PostgresSendReadyForQueryIfNecessary trapped after a typed main-loop outcome; \ -+ the direct instance is closed", -+ )) -+ } -+ } -+ } -+ -+ fn flush_after_main_loop(&mut self) -> anyhow::Result<()> { -+ match self.pq_flush.call(&mut self.store) { -+ Ok(()) => Ok(()), -+ Err(error) => { -+ self.poison_main_loop(); -+ Err(anyhow::Error::from(error).context( -+ "oliphaunt_wasix_pq_flush trapped after a typed main-loop outcome; \ -+ the direct instance is closed", -+ )) -+ } -+ } -+ } -+ -+ fn start_backend(&mut self) -> anyhow::Result<()> { -+ if self.backend_started { -+ return Ok(()); -+ } -+ self.set_active -+ .call(&mut self.store, 1) -+ .context("oliphaunt_wasix_set_active(1)")?; -+ match self.wasi_start.call(&mut self.store) { -+ Ok(()) => {} -+ Err(error) if runtime_exit_code(&error) == Some(OLIPHAUNT_EXIT_ALIVE) => {} -+ Err(error) => return Err(error).context("_start Oliphaunt single-user backend"), -+ } -+ self.start_oliphaunt -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_start")?; -+ self.backend_started = true; -+ Ok(()) -+ } -+ -+ fn reset_io(&mut self) -> anyhow::Result<()> { -+ ensure!( -+ self.input_reset -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_input_reset")? -+ == 0, -+ "oliphaunt_wasix_input_reset failed" -+ ); -+ ensure!( -+ self.output_reset -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_output_reset")? -+ == 0, -+ "oliphaunt_wasix_output_reset failed" -+ ); -+ Ok(()) -+ } -+ -+ fn push_input(&mut self, bytes: &[u8]) -> anyhow::Result<()> { -+ if bytes.is_empty() { -+ return Ok(()); -+ } -+ let ptr = self.allocate(bytes.len() as i32)?; -+ let result = (|| { -+ let view = self -+ .env -+ .data(&self.store) -+ .try_memory_view(&self.store) -+ .context("get WASIX memory view")?; -+ view.write(ptr as u64, bytes) -+ .context("write direct protocol input into guest")?; -+ let written = self -+ .input_write -+ .call(&mut self.store, ptr, bytes.len() as i32) -+ .context("oliphaunt_wasix_input_write")?; -+ ensure!( -+ written == bytes.len() as i32, -+ "short direct protocol input write" -+ ); -+ Ok(()) -+ })(); -+ self.free -+ .call(&mut self.store, ptr) -+ .context("free direct protocol input")?; -+ result -+ } -+ -+ fn take_output(&mut self) -> anyhow::Result> { -+ let len = self -+ .output_len -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_output_len")?; -+ ensure!(len >= 0, "negative direct protocol output length"); -+ if len == 0 { -+ return Ok(Vec::new()); -+ } -+ let ptr = self.allocate(len)?; -+ let result = (|| { -+ let read = self -+ .output_read -+ .call(&mut self.store, ptr, len) -+ .context("oliphaunt_wasix_output_read")?; -+ ensure!( -+ read >= 0 && read <= len, -+ "invalid direct protocol output read" -+ ); -+ let mut bytes = vec![0; read as usize]; -+ let view = self -+ .env -+ .data(&self.store) -+ .try_memory_view(&self.store) -+ .context("get WASIX memory view")?; -+ view.read(ptr as u64, &mut bytes) -+ .context("read direct protocol output from guest")?; -+ Ok(bytes) -+ })(); -+ self.free -+ .call(&mut self.store, ptr) -+ .context("free direct protocol output")?; -+ let bytes = result?; -+ ensure!( -+ self.output_reset -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_output_reset after read")? -+ == 0, -+ "oliphaunt_wasix_output_reset after read failed" -+ ); -+ Ok(bytes) -+ } -+ -+ fn allocate(&mut self, len: i32) -> anyhow::Result { -+ let ptr = self -+ .malloc -+ .call(&mut self.store, len) -+ .context("malloc direct guest buffer")?; -+ ensure!(ptr > 0, "malloc returned null for direct guest buffer"); -+ Ok(ptr) -+ } -+ -+ fn protocol_input_remaining(&mut self) -> anyhow::Result { -+ let available = self -+ .input_available -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_input_available")?; -+ ensure!(available >= 0, "negative direct protocol input length"); -+ if available > 0 { -+ return Ok(available); -+ } -+ self.pq_buffer_remaining_data -+ .call(&mut self.store) -+ .context("pq_buffer_remaining_data") -+ } -+ -+ fn startup_error(&mut self, error: anyhow::Error) -> Error { -+ let _ = self.pq_flush.call(&mut self.store); -+ let protocol = self.take_output().unwrap_or_default(); -+ if protocol.is_empty() { -+ return Error::from(error); -+ } -+ -+ let js_error = js_sys::Error::new(&error.to_string()); -+ let _ = js_sys::Reflect::set( -+ &js_error, -+ &wasm_bindgen::JsValue::from_str("protocolResponse"), -+ &Uint8Array::from(protocol.as_slice()), -+ ); -+ let _ = js_sys::Reflect::set( -+ &js_error, -+ &wasm_bindgen::JsValue::from_str("detailedMessage"), -+ &wasm_bindgen::JsValue::from_str(&format!("{error:#}")), -+ ); -+ Error::js(js_error) -+ } -+} -+ -+fn typed_export( -+ store: &mut Store, -+ instance: &WasmerInstance, -+ name: &str, -+) -> anyhow::Result> -+where -+ Args: WasmTypeList, -+ Rets: WasmTypeList, -+{ -+ instance -+ .exports -+ .get_typed_function::(&*store, name) -+ .or_else(|_| { -+ instance -+ .exports -+ .get_typed_function::(&*store, &format!("_{name}")) -+ }) -+ .with_context(|| format!("get {name} export")) -+} -+ -+fn optional_typed_export( -+ store: &mut Store, -+ instance: &WasmerInstance, -+ name: &str, -+) -> anyhow::Result>> -+where -+ Args: WasmTypeList, -+ Rets: WasmTypeList, -+{ -+ if instance.exports.get_function(name).is_err() -+ && instance.exports.get_function(&format!("_{name}")).is_err() -+ { -+ return Ok(None); -+ } -+ typed_export(store, instance, name).map(Some) -+} -+ -+fn seed_exported_c_string( -+ store: &mut Store, -+ instance: &WasmerInstance, -+ env: &WasiFunctionEnv, -+ name: &str, -+ value: &str, -+) -> anyhow::Result<()> { -+ let Ok(global) = instance.exports.get_global(name) else { -+ return Ok(()); -+ }; -+ let Value::I32(ptr) = global.get(&mut *store) else { -+ return Ok(()); -+ }; -+ if ptr <= 0 { -+ return Ok(()); -+ } -+ let mut bytes = value.as_bytes().to_vec(); -+ bytes.push(0); -+ env.data(&*store) -+ .try_memory_view(&*store) -+ .context("get WASIX memory view")? -+ .write(ptr as u64, &bytes) -+ .with_context(|| format!("seed {name} at 0x{ptr:x}"))?; -+ Ok(()) -+} -+ -+fn runtime_exit_code(error: &wasmer::RuntimeError) -> Option { -+ error -+ .downcast_ref::() -+ .and_then(|error| match error { -+ WasiError::Exit(code) => Some(code.raw()), -+ _ => None, -+ }) -+} -diff --git a/src/runtime.rs b/src/runtime.rs -index d71638f..cb106d7 100644 ---- a/src/runtime.rs -+++ b/src/runtime.rs -@@ -83,7 +83,7 @@ impl Runtime { - Ok(rt) - } - -- pub(crate) fn with_task_manager(&self, task_manager: Arc) -> Self { -+ pub(crate) fn with_task_manager(&self, task_manager: Arc) -> Self { - let mut runtime = self.clone(); - // Update the http client - let http_client = DefaultHttpClient::default(); -diff --git a/src/tasks/caller_realm.rs b/src/tasks/caller_realm.rs -new file mode 100644 -index 0000000..0ad7233 ---- /dev/null -+++ b/src/tasks/caller_realm.rs -@@ -0,0 +1,50 @@ -+use std::{future::Future, pin::Pin, time::Duration}; -+ -+use futures::future::BoxFuture; -+use wasmer_wasix::{runtime::task_manager::TaskWasm, VirtualTaskManager, WasiThreadError}; -+ -+/// A deliberately single-realm task manager for synchronous guest calls. -+/// -+/// PostgreSQL's direct protocol pump never yields control back to JavaScript -+/// while a guest export is running. Timers therefore have to complete during -+/// their first poll, while operations that require another WASM execution -+/// context fail explicitly instead of being queued behind the blocked realm. -+#[derive(Debug, Default)] -+pub(crate) struct CallerRealmTaskManager; -+ -+#[async_trait::async_trait] -+impl VirtualTaskManager for CallerRealmTaskManager { -+ fn sleep_now( -+ &self, -+ duration: Duration, -+ ) -> Pin + Send + Sync + 'static>> { -+ let deadline = instant::Instant::now() + duration; -+ Box::pin(async move { -+ while instant::Instant::now() < deadline { -+ std::hint::spin_loop(); -+ } -+ }) -+ } -+ -+ fn task_shared( -+ &self, -+ _task: Box BoxFuture<'static, ()> + Send + 'static>, -+ ) -> Result<(), WasiThreadError> { -+ Err(WasiThreadError::Unsupported) -+ } -+ -+ fn task_wasm(&self, _task: TaskWasm<'_>) -> Result<(), WasiThreadError> { -+ Err(WasiThreadError::Unsupported) -+ } -+ -+ fn task_dedicated( -+ &self, -+ _task: Box, -+ ) -> Result<(), WasiThreadError> { -+ Err(WasiThreadError::Unsupported) -+ } -+ -+ fn thread_parallelism(&self) -> Result { -+ Ok(1) -+ } -+} -diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs -index 2e9b0ee..76f8695 100644 ---- a/src/tasks/mod.rs -+++ b/src/tasks/mod.rs -@@ -31,6 +31,7 @@ - //! [`Worker`]: thread_pool_worker::ThreadPoolWorker - //! [`Scheduler`]: scheduler::Scheduler - -+mod caller_realm; - mod interop; - mod post_message_payload; - mod scheduler; -@@ -42,6 +43,7 @@ mod worker_handle; - mod worker_message; - - pub(crate) use self::{ -+ caller_realm::CallerRealmTaskManager, - post_message_payload::{AsyncJob, BlockingJob, Notification, PostMessagePayload}, - scheduler::Scheduler, - scheduler_message::SchedulerMessage, diff --git a/src/wasix/browser-host/patches/0009-wasmer-wasix-instantiate-main-module-async.patch b/src/wasix/browser-host/patches/0009-wasmer-wasix-instantiate-main-module-async.patch deleted file mode 100644 index 0d9706c62..000000000 --- a/src/wasix/browser-host/patches/0009-wasmer-wasix-instantiate-main-module-async.patch +++ /dev/null @@ -1,274 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: propagate asynchronous main-module instantiation - -Thread the asynchronous engine instantiation API through the WASIX builder, -environment and linker while preserving the synchronous entrypoint. This is -the host-side companion to the engine API patch, needed to start large browser -guests without falling back to worker-only placement. - -diff --git a/src/state/builder.rs b/src/state/builder.rs ---- a/src/state/builder.rs -+++ b/src/state/builder.rs -@@ -6,6 +6,7 @@ use std::{ - sync::Arc, - }; - -+use futures::FutureExt; - use rand::Rng; - use thiserror::Error; - use virtual_fs::{ArcFile, FileSystem, FsError, TmpFileSystem, VirtualFile}; -@@ -1046,6 +1047,43 @@ impl WasiEnvBuilder { - module_hash: ModuleHash, - store: &mut impl AsStoreMut, - ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { -+ self.instantiate_ext_inner(module, module_hash, store, false) -+ .now_or_never() -+ .expect("synchronous WASI builder instantiation must not yield") -+ } -+ -+ /// Instantiate a JavaScript-backed module through the browser's -+ /// asynchronous `WebAssembly.instantiate()` path. -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ #[allow(clippy::result_large_err)] -+ pub async fn instantiate_async( -+ self, -+ module: Module, -+ store: &mut impl AsStoreMut, -+ ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { -+ self.instantiate_ext_async(module, ModuleHash::random(), store) -+ .await -+ } -+ -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ #[allow(clippy::result_large_err)] -+ pub async fn instantiate_ext_async( -+ self, -+ module: Module, -+ module_hash: ModuleHash, -+ store: &mut impl AsStoreMut, -+ ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { -+ self.instantiate_ext_inner(module, module_hash, store, true) -+ .await -+ } -+ -+ async fn instantiate_ext_inner( -+ self, -+ module: Module, -+ module_hash: ModuleHash, -+ store: &mut impl AsStoreMut, -+ async_instantiation: bool, -+ ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { - let init = self.build_init()?; - let call_init = init.call_initialize; - let env = WasiEnv::from_init(init, module_hash)?; -@@ -1058,7 +1096,17 @@ impl WasiEnvBuilder { - .map(|ty| wasmer::Memory::new(store, ty)) - .transpose() - .map_err(WasiThreadError::MemoryCreateFailed)?; -- Ok(env.instantiate(module, store, memory, true, call_init, None)?) -+ Ok(env -+ .instantiate_inner( -+ module, -+ store, -+ memory, -+ true, -+ call_init, -+ None, -+ async_instantiation, -+ ) -+ .await?) - } - } - -diff --git a/src/state/env.rs b/src/state/env.rs ---- a/src/state/env.rs -+++ b/src/state/env.rs -@@ -7,7 +7,7 @@ use std::{ - time::Duration, - }; - --use futures::future::BoxFuture; -+use futures::{future::BoxFuture, FutureExt}; - use rand::Rng; - use virtual_fs::{FileSystem, FsError, VirtualFile}; - use virtual_net::DynVirtualNetworking; -@@ -416,7 +416,52 @@ impl WasiEnv { - memory: Option, - update_layout: bool, - call_initialize: bool, -- parent_linker_and_ctx: Option<(Linker, &mut FunctionEnvMut)>, -+ parent_linker_and_ctx: Option<(Linker, &mut FunctionEnvMut<'_, WasiEnv>)>, -+ ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> { -+ self.instantiate_inner( -+ module, -+ store, -+ memory, -+ update_layout, -+ call_initialize, -+ parent_linker_and_ctx, -+ false, -+ ) -+ .now_or_never() -+ .expect("synchronous WASI instantiation must not yield") -+ } -+ -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ pub(crate) async fn instantiate_async( -+ self, -+ module: Module, -+ store: &mut impl AsStoreMut, -+ memory: Option, -+ update_layout: bool, -+ call_initialize: bool, -+ parent_linker_and_ctx: Option<(Linker, &mut FunctionEnvMut<'_, WasiEnv>)>, -+ ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> { -+ self.instantiate_inner( -+ module, -+ store, -+ memory, -+ update_layout, -+ call_initialize, -+ parent_linker_and_ctx, -+ true, -+ ) -+ .await -+ } -+ -+ pub(crate) async fn instantiate_inner( -+ self, -+ module: Module, -+ store: &mut impl AsStoreMut, -+ memory: Option, -+ update_layout: bool, -+ call_initialize: bool, -+ parent_linker_and_ctx: Option<(Linker, &mut FunctionEnvMut<'_, WasiEnv>)>, -+ async_instantiation: bool, - ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> { - let pid = self.process.pid(); - -@@ -451,7 +496,7 @@ impl WasiEnv { - }; - - // TODO: make stack size configurable -- Linker::new( -+ Linker::new_inner( - engine, - &module, - &mut store, -@@ -459,7 +504,9 @@ impl WasiEnv { - &mut func_env, - 8 * 1024 * 1024, - &ld_library_path, -+ async_instantiation, - ) -+ .await - } - }; - -@@ -493,7 +540,19 @@ impl WasiEnv { - }; - - // Construct the instance. -- let instance = match Instance::new(&mut store, &module, &import_object) { -+ let instance_result = if async_instantiation { -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ { -+ Instance::new_async(&mut store, &module, &import_object).await -+ } -+ #[cfg(not(all(feature = "js", target_arch = "wasm32")))] -+ { -+ Instance::new(&mut store, &module, &import_object) -+ } -+ } else { -+ Instance::new(&mut store, &module, &import_object) -+ }; -+ let instance = match instance_result { - Ok(a) => a, - Err(err) => { - tracing::error!( -diff --git a/src/state/linker.rs b/src/state/linker.rs ---- a/src/state/linker.rs -+++ b/src/state/linker.rs -@@ -272,6 +272,7 @@ use std::{ - - use bus::Bus; - use derive_more::Debug; -+use futures::FutureExt; - use shared_buffer::OwnedBuffer; - use tracing::trace; - use virtual_fs::{AsyncReadExt, FileSystem, FsError}; -@@ -957,6 +958,53 @@ impl Linker { - stack_size: u64, - ld_library_path: &[&Path], - ) -> Result<(Self, LinkedMainModule), LinkError> { -+ Self::new_inner( -+ engine, -+ main_module, -+ store, -+ memory, -+ func_env, -+ stack_size, -+ ld_library_path, -+ false, -+ ) -+ .now_or_never() -+ .expect("synchronous linker construction must not yield") -+ } -+ -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ pub async fn new_async( -+ engine: Engine, -+ main_module: &Module, -+ store: &mut StoreMut<'_>, -+ memory: Option, -+ func_env: &mut WasiFunctionEnv, -+ stack_size: u64, -+ ld_library_path: &[&Path], -+ ) -> Result<(Self, LinkedMainModule), LinkError> { -+ Self::new_inner( -+ engine, -+ main_module, -+ store, -+ memory, -+ func_env, -+ stack_size, -+ ld_library_path, -+ true, -+ ) -+ .await -+ } -+ -+ pub(crate) async fn new_inner( -+ engine: Engine, -+ main_module: &Module, -+ store: &mut StoreMut<'_>, -+ memory: Option, -+ func_env: &mut WasiFunctionEnv, -+ stack_size: u64, -+ ld_library_path: &[&Path], -+ async_instantiation: bool, -+ ) -> Result<(Self, LinkedMainModule), LinkError> { - let dylink_section = parse_dylink0_section(main_module)?; - - trace!(?dylink_section, "Loading main module"); -@@ -1136,7 +1184,18 @@ impl Linker { - // use that ordering. My *guess* is that, since main exports all the libc - // functions and those are called frequently by basically any code, then giving - // stubs to main will be faster, but we need numbers before we decide this. -- let main_instance = Instance::new(store, main_module, &imports)?; -+ let main_instance = if async_instantiation { -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ { -+ Instance::new_async(store, main_module, &imports).await? -+ } -+ #[cfg(not(all(feature = "js", target_arch = "wasm32")))] -+ { -+ Instance::new(store, main_module, &imports)? -+ } -+ } else { -+ Instance::new(store, main_module, &imports)? -+ }; - instance_group.main_instance = Some(main_instance.clone()); - - let runtime_path = linker_state.main_module_dylink_info.runtime_path.clone(); diff --git a/src/wasix/browser-host/patches/0010-wasmer-js-refresh-npm-lock.patch b/src/wasix/browser-host/patches/0010-wasmer-js-refresh-npm-lock.patch deleted file mode 100644 index 6ab8fbf9a..000000000 --- a/src/wasix/browser-host/patches/0010-wasmer-js-refresh-npm-lock.patch +++ /dev/null @@ -1,86 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: align the pinned npm lock with package metadata - -Supply the missing root metadata and dependency entries in the pinned source -lock so npm ci can install without rewriting it. This is reproducibility repair -for this exact upstream revision, not a runtime optimization. - -diff --git a/package-lock.json b/package-lock.json -index ef54a7d..83d263d 100644 ---- a/package-lock.json -+++ b/package-lock.json -@@ -1,13 +1,16 @@ - { - "name": "@wasmer/sdk", -- "version": "0.7.0-beta.2", -+ "version": "0.8.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@wasmer/sdk", -- "version": "0.7.0-beta.2", -+ "version": "0.8.0", - "license": "MIT", -+ "dependencies": { -+ "web-worker": "^1.3.0" -+ }, - "devDependencies": { - "@babel/preset-env": "^7.23.3", - "@esm-bundle/chai": "^4.3.4-fix.0", -@@ -20,6 +23,7 @@ - "@rollup/plugin-typescript": "^11.1.5", - "@rollup/plugin-wasm": "^6.2.2", - "@types/mocha": "^10.0.7", -+ "@types/node": "^22.14.0", - "@web/dev-server-esbuild": "^1.0.1", - "@web/test-runner": "^0.18.0", - "dotenv": "^16.4.5", -@@ -2939,12 +2943,13 @@ - "dev": true - }, - "node_modules/@types/node": { -- "version": "20.10.5", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", -- "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", -+ "version": "22.20.1", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", -+ "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", - "dev": true, -+ "license": "MIT", - "dependencies": { -- "undici-types": "~5.26.4" -+ "undici-types": "~6.21.0" - } - }, - "node_modules/@types/parse5": { -@@ -7521,10 +7526,11 @@ - } - }, - "node_modules/undici-types": { -- "version": "5.26.5", -- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", -- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", -- "dev": true -+ "version": "6.21.0", -+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", -+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", -+ "dev": true, -+ "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", -@@ -7649,6 +7655,12 @@ - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, -+ "node_modules/web-worker": { -+ "version": "1.5.0", -+ "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", -+ "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", -+ "license": "Apache-2.0" -+ }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/src/wasix/browser-host/patches/0011-wasmer-wasix-deny-single-backend-guest-spawn.patch b/src/wasix/browser-host/patches/0011-wasmer-wasix-deny-single-backend-guest-spawn.patch deleted file mode 100644 index f21814408..000000000 --- a/src/wasix/browser-host/patches/0011-wasmer-wasix-deny-single-backend-guest-spawn.patch +++ /dev/null @@ -1,338 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: make execution and I/O policy host-owned - -Represent single-program execution, clock selection and close behavior as a -typed host policy. Deny guest process/thread creation before side effects and -retain control-plane limits as an independent defense. Guest environment -variables must not grant capabilities. The generic policy mechanism and -Oliphaunt's selected profile should be separated for upstream review. - -diff --git a/src/capabilities.rs b/src/capabilities.rs -index e3c3b0ab..cbff2665 100644 ---- a/src/capabilities.rs -+++ b/src/capabilities.rs -@@ -2,6 +2,73 @@ use std::time::Duration; - - use crate::http::HttpClientCapabilityV1; - -+/// Defines how much guest-controlled execution a WASI environment permits. -+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -+pub enum WasiGuestExecutionMode { -+ /// Retain the normal WASIX process and thread behavior. -+ #[default] -+ General, -+ /// Keep the initial program as the environment's only process and thread. -+ SingleProgram, -+} -+ -+/// Selects the implementation used for `clock_time_get`. -+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -+pub enum WasiClockTimeGetMode { -+ /// Use the complete Rust syscall for every clock read. -+ #[default] -+ Canonical, -+ /// Use the JavaScript clock import with bounded canonical fallbacks. -+ DirectJs, -+} -+ -+/// Describes whether a filesystem needs WASIX to flush before closing a file. -+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -+pub enum WasiFdClosePolicy { -+ /// Preserve the upstream flush-before-close behavior. -+ #[default] -+ FlushBeforeClose, -+ /// Writes complete synchronously, so POSIX close must not become fsync. -+ WritesCompleteSynchronously, -+} -+ -+/// Immutable host-selected execution policy for one WASI environment. -+/// -+/// This policy is deliberately separate from guest environment variables and -+/// from mergeable [`Capabilities`]. The builder fixes it before instantiation. -+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -+pub struct WasiHostPolicy { -+ guest_execution: WasiGuestExecutionMode, -+ clock_time_get: WasiClockTimeGetMode, -+ fd_close: WasiFdClosePolicy, -+} -+ -+impl WasiHostPolicy { -+ pub const fn new( -+ guest_execution: WasiGuestExecutionMode, -+ clock_time_get: WasiClockTimeGetMode, -+ fd_close: WasiFdClosePolicy, -+ ) -> Self { -+ Self { -+ guest_execution, -+ clock_time_get, -+ fd_close, -+ } -+ } -+ -+ pub const fn guest_execution(self) -> WasiGuestExecutionMode { -+ self.guest_execution -+ } -+ -+ pub const fn clock_time_get(self) -> WasiClockTimeGetMode { -+ self.clock_time_get -+ } -+ -+ pub const fn fd_close(self) -> WasiFdClosePolicy { -+ self.fd_close -+ } -+} -+ - /// Defines capabilities for a Wasi environment. - #[derive(Clone, Debug, PartialEq, Eq, Hash)] - pub struct Capabilities { -diff --git a/src/os/task/control_plane.rs b/src/os/task/control_plane.rs -index 6f88ee67..c1a82b77 100644 ---- a/src/os/task/control_plane.rs -+++ b/src/os/task/control_plane.rs -@@ -120,9 +120,9 @@ impl WasiControlPlane { - pub(crate) fn register_task(&self) -> Result { - let count = self.state.task_count.fetch_add(1, Ordering::SeqCst); - if let Some(max) = self.state.config.max_task_count { -- if count > max { -+ if count >= max { - self.state.task_count.fetch_sub(1, Ordering::SeqCst); -- return Err(ControlPlaneError::TaskLimitReached { max: count }); -+ return Err(ControlPlaneError::TaskLimitReached { max }); - } - } - Ok(TaskCountGuard(self.state.task_count.clone())) -@@ -234,6 +234,11 @@ mod tests { - .new_thread(WasiMemoryLayout::default(), ThreadStartType::MainThread) - .unwrap(); - -+ assert_eq!( -+ p1.new_thread(WasiMemoryLayout::default(), ThreadStartType::MainThread) -+ .err(), -+ Some(ControlPlaneError::TaskLimitReached { max: 2 }) -+ ); - assert_eq!( - p.new_process(ModuleHash::random()).unwrap_err(), - ControlPlaneError::TaskLimitReached { max: 2 } -diff --git a/src/state/builder.rs b/src/state/builder.rs -index 768e427d..d57e1913 100644 ---- a/src/state/builder.rs -+++ b/src/state/builder.rs -@@ -17,7 +17,7 @@ use wasmer_config::package::PackageId; - use crate::journal::{DynJournal, DynReadableJournal, SnapshotTrigger}; - use crate::{ - bin_factory::{BinFactory, BinaryPackage}, -- capabilities::Capabilities, -+ capabilities::{Capabilities, WasiGuestExecutionMode, WasiHostPolicy}, - fs::{WasiFs, WasiFsRoot, WasiInodes}, - os::task::control_plane::{ControlPlaneConfig, ControlPlaneError, WasiControlPlane}, - state::WasiState, -@@ -84,6 +84,8 @@ pub struct WasiEnvBuilder { - - pub(super) capabilites: Capabilities, - -+ pub(super) host_policy: WasiHostPolicy, -+ - #[cfg(feature = "journal")] - pub(super) snapshot_on: Vec, - -@@ -759,6 +761,16 @@ impl WasiEnvBuilder { - self.capabilites = capabilities; - } - -+ /// Selects immutable host-side behavior before the environment is built. -+ pub fn host_policy(mut self, host_policy: WasiHostPolicy) -> Self { -+ self.set_host_policy(host_policy); -+ self -+ } -+ -+ pub fn set_host_policy(&mut self, host_policy: WasiHostPolicy) { -+ self.host_policy = host_policy; -+ } -+ - #[cfg(feature = "journal")] - pub fn add_snapshot_trigger(&mut self, on: SnapshotTrigger) { - self.snapshot_on.push(on); -@@ -966,9 +978,17 @@ impl WasiEnvBuilder { - let bin_factory = BinFactory::new(runtime.clone()); - - let capabilities = self.capabilites; -+ let host_policy = self.host_policy; -+ -+ let max_task_count = -+ if host_policy.guest_execution() == WasiGuestExecutionMode::SingleProgram { -+ Some(1) -+ } else { -+ capabilities.threading.max_threads -+ }; - - let plane_config = ControlPlaneConfig { -- max_task_count: capabilities.threading.max_threads, -+ max_task_count, - enable_asynchronous_threading: capabilities.threading.enable_asynchronous_threading, - enable_exponential_cpu_backoff: capabilities.threading.enable_exponential_cpu_backoff, - }; -@@ -982,6 +1002,7 @@ impl WasiEnvBuilder { - control_plane, - bin_factory, - capabilities, -+ host_policy, - memory_ty: None, - process: None, - thread: None, -diff --git a/src/state/env.rs b/src/state/env.rs -index a9701e1e..77d8a892 100644 ---- a/src/state/env.rs -+++ b/src/state/env.rs -@@ -27,7 +27,7 @@ use webc::metadata::annotations::Wasi; - use crate::journal::{DynJournal, JournalEffector, SnapshotTrigger}; - use crate::{ - bin_factory::{BinFactory, BinaryPackage, BinaryPackageCommand}, -- capabilities::Capabilities, -+ capabilities::{Capabilities, WasiHostPolicy}, - fs::{WasiFsRoot, WasiInodes}, - import_object_for_all_wasi_versions, - os::task::{ -@@ -54,6 +54,7 @@ pub struct WasiEnvInit { - pub mapped_commands: HashMap, - pub bin_factory: BinFactory, - pub capabilities: Capabilities, -+ pub(crate) host_policy: WasiHostPolicy, - - pub control_plane: WasiControlPlane, - pub memory_ty: Option, -@@ -110,6 +111,7 @@ impl WasiEnvInit { - mapped_commands: self.mapped_commands.clone(), - bin_factory: self.bin_factory.clone(), - capabilities: self.capabilities.clone(), -+ host_policy: self.host_policy, - control_plane: self.control_plane.clone(), - memory_ty: None, - process: None, -@@ -139,6 +141,8 @@ pub struct WasiEnv { - pub vfork: Option, - /// Seed used to rotate around the events returned by `poll_oneoff` - pub poll_seed: u64, -+ /// Immutable behavior selected by the host before instantiation. -+ pub(crate) host_policy: WasiHostPolicy, - /// Shared state of the WASI system. Manages all the data that the - /// executing WASI program can see. - pub(crate) state: Arc, -@@ -193,6 +197,7 @@ impl Clone for WasiEnv { - control_plane: self.control_plane.clone(), - process: self.process.clone(), - poll_seed: self.poll_seed, -+ host_policy: self.host_policy, - thread: self.thread.clone(), - layout: self.layout.clone(), - vfork: self.vfork.clone(), -@@ -237,6 +242,7 @@ impl WasiEnv { - layout: self.layout.clone(), - vfork: None, - poll_seed: 0, -+ host_policy: self.host_policy, - bin_factory, - state, - inner: Default::default(), -@@ -375,6 +381,7 @@ impl WasiEnv { - layout, - vfork: None, - poll_seed: 0, -+ host_policy: init.host_policy, - state: Arc::new(init.state), - inner: Default::default(), - owned_handles: Vec::new(), -diff --git a/src/syscalls/wasix/mod.rs b/src/syscalls/wasix/mod.rs -index 0ec461de..d6edc59b 100644 ---- a/src/syscalls/wasix/mod.rs -+++ b/src/syscalls/wasix/mod.rs -@@ -173,3 +173,9 @@ pub use tty_get::*; - pub use tty_set::*; - - use tracing::{debug_span, field, instrument, trace_span, Span}; -+ -+use crate::{capabilities::WasiGuestExecutionMode, WasiEnv}; -+ -+fn single_program_requested(env: &WasiEnv) -> bool { -+ env.host_policy.guest_execution() == WasiGuestExecutionMode::SingleProgram -+} -diff --git a/src/syscalls/wasix/proc_exec3.rs b/src/syscalls/wasix/proc_exec3.rs -index e72cdd7c..7362c34f 100644 ---- a/src/syscalls/wasix/proc_exec3.rs -+++ b/src/syscalls/wasix/proc_exec3.rs -@@ -34,6 +34,11 @@ pub fn proc_exec3( - ) -> Result { - WasiEnv::do_pending_operations(&mut ctx)?; - -+ if single_program_requested(ctx.data()) { -+ warn!("guest process replacement denied for the Oliphaunt single-backend pgwire runtime"); -+ return Ok(Errno::Notcapable); -+ } -+ - // If we were just restored the stack then we were woken after a deep sleep - if let Some(exit_code) = unsafe { handle_rewind::(&mut ctx) } { - // We should never get here as the process will be termined -diff --git a/src/syscalls/wasix/proc_fork.rs b/src/syscalls/wasix/proc_fork.rs -index f2b17e51..3c953a8e 100644 ---- a/src/syscalls/wasix/proc_fork.rs -+++ b/src/syscalls/wasix/proc_fork.rs -@@ -27,6 +27,11 @@ pub fn proc_fork( - ) -> Result { - WasiEnv::do_pending_operations(&mut ctx)?; - -+ if single_program_requested(ctx.data()) { -+ warn!("guest process fork denied for the Oliphaunt single-backend pgwire runtime"); -+ return Ok(Errno::Notcapable); -+ } -+ - wasi_try_ok!(ctx.data().ensure_static_module().map_err(|_| { - warn!("process forking not supported for dynamically linked modules"); - Errno::Notsup -diff --git a/src/syscalls/wasix/proc_spawn.rs b/src/syscalls/wasix/proc_spawn.rs -index 958b9b03..48d7b0d6 100644 ---- a/src/syscalls/wasix/proc_spawn.rs -+++ b/src/syscalls/wasix/proc_spawn.rs -@@ -42,6 +42,11 @@ pub fn proc_spawn( - ) -> Result { - WasiEnv::do_pending_operations(&mut ctx)?; - -+ if single_program_requested(ctx.data()) { -+ warn!("guest process creation denied for the Oliphaunt single-backend pgwire runtime"); -+ return Ok(Errno::Notcapable); -+ } -+ - let env = ctx.data(); - let control_plane = &env.control_plane; - let memory = unsafe { env.memory_view(&ctx) }; -diff --git a/src/syscalls/wasix/proc_spawn2.rs b/src/syscalls/wasix/proc_spawn2.rs -index b9071184..4d26e950 100644 ---- a/src/syscalls/wasix/proc_spawn2.rs -+++ b/src/syscalls/wasix/proc_spawn2.rs -@@ -44,6 +44,11 @@ pub fn proc_spawn2( - ) -> Result { - WasiEnv::do_pending_operations(&mut ctx)?; - -+ if single_program_requested(ctx.data()) { -+ warn!("guest process creation denied for the Oliphaunt single-backend pgwire runtime"); -+ return Ok(Errno::Notcapable); -+ } -+ - let env = ctx.data(); - let memory = unsafe { ctx.data().memory_view(&ctx) }; - let mut name = unsafe { get_input_str_ok!(&memory, name, name_len) }; -diff --git a/src/syscalls/wasix/thread_spawn.rs b/src/syscalls/wasix/thread_spawn.rs -index 87c4dfd5..8526b057 100644 ---- a/src/syscalls/wasix/thread_spawn.rs -+++ b/src/syscalls/wasix/thread_spawn.rs -@@ -57,6 +57,11 @@ pub fn thread_spawn_internal_from_wasi( - ctx: &mut FunctionEnvMut<'_, WasiEnv>, - start_ptr: WasmPtr, M>, - ) -> Result { -+ if single_program_requested(ctx.data()) { -+ warn!("guest thread creation denied for the Oliphaunt single-backend pgwire runtime"); -+ return Err(Errno::Notcapable); -+ } -+ - // Now we use the environment and memory references - let env = ctx.data(); - let memory = unsafe { env.memory_view(&ctx) }; diff --git a/src/wasix/browser-host/patches/0012-wasmer-js-remove-retired-wasm32-wasi-target.patch b/src/wasix/browser-host/patches/0012-wasmer-js-remove-retired-wasm32-wasi-target.patch deleted file mode 100644 index 37beec6ce..000000000 --- a/src/wasix/browser-host/patches/0012-wasmer-js-remove-retired-wasm32-wasi-target.patch +++ /dev/null @@ -1,18 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: remove the retired Rust target requirement - -Remove the obsolete wasm32-wasi target from the pinned toolchain manifest. -The browser SDK builds wasm32-unknown-unknown, so installing the retired target -is unnecessary and can prevent the otherwise valid source build. - -diff --git a/rust-toolchain.toml b/rust-toolchain.toml ---- a/rust-toolchain.toml -+++ b/rust-toolchain.toml -@@ -2,5 +2,5 @@ - # The nightly toolchain is needed so the generated wasm generates - # a shared memory (via +atomics) instead of a non-shared one (default behavior) - channel = "nightly-2025-07-05" --targets = ["wasm32-unknown-unknown", "wasm32-wasi"] -+targets = ["wasm32-unknown-unknown"] - components = ["rust-src", "rustfmt", "clippy"] diff --git a/src/wasix/browser-host/patches/0013-wasmer-wasix-fast-single-backend-clock.patch b/src/wasix/browser-host/patches/0013-wasmer-wasix-fast-single-backend-clock.patch deleted file mode 100644 index 100ae5124..000000000 --- a/src/wasix/browser-host/patches/0013-wasmer-wasix-fast-single-backend-clock.patch +++ /dev/null @@ -1,445 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: bound direct JavaScript clock dispatch - -Install an opt-in JavaScript clock provider for single-program WASIX guests. -Realtime and monotonic state remain independent, and each selected domain -returns to the canonical syscall after either 16 observed milliseconds or -1,024 direct reads. Clock setters disable direct reads for every module using -the same memory before calling WASIX; merely importing a setter is harmless. -Unsupported clocks and invalid memory retain canonical behavior. Journaling -and CPU backoff remain incompatible with the direct provider. - -Also correct the canonical wasm32 JavaScript platform clock implementation so -realtime and monotonic clocks use their respective domains and unsupported CPU -clocks report Notsup. - -diff --git a/src/lib.rs b/src/lib.rs -index 287f2a4b..a68a6630 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -92,6 +92,233 @@ pub use virtual_net::{ - }; - use wasmer_wasix_types::wasi::{Errno, ExitCode}; - -+#[cfg(all(feature = "js", target_arch = "wasm32"))] -+use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; -+ -+#[cfg(all(feature = "js", target_arch = "wasm32"))] -+#[wasm_bindgen(inline_js = r#" -+const clockStates = new WeakMap(); -+function clockState(memory) { -+ let state = clockStates.get(memory); -+ if (state === undefined) { -+ state = { canonical: false }; -+ clockStates.set(memory, state); -+ } -+ return state; -+} -+export function oliphauntDirectClockImport(memory, fallback) { -+ const state = clockState(memory); -+ const MAX_CANONICAL_INTERVAL_MILLIS = 16; -+ // Coarsened or stalled clock sources must not suppress -+ // canonical checkpoints forever. -+ const MAX_DIRECT_READS_BETWEEN_FALLBACKS = 1024; -+ const MAX_CANONICAL_NANOSECONDS = 9223372036854775807n; -+ let lastRealtimeFallbackMillis; -+ let lastMonotonicFallbackMillis; -+ let realtimeDirectReadsSinceFallback = 0; -+ let monotonicDirectReadsSinceFallback = 0; -+ let monotonicAnchor; -+ let buffer = memory.buffer; -+ let view = new DataView(buffer); -+ -+ function sampleRealtimeMillis() { -+ const millis = Date.now(); -+ return Number.isFinite(millis) && millis >= 0 ? millis : undefined; -+ } -+ -+ function samplePerformanceMillis() { -+ const performanceObject = globalThis.performance; -+ if (performanceObject === undefined || typeof performanceObject.now !== "function") { -+ return undefined; -+ } -+ const millis = performanceObject.now(); -+ return Number.isFinite(millis) && millis >= 0 ? millis : undefined; -+ } -+ -+ function refreshView(timePointer) { -+ let pointer; -+ if (typeof timePointer === "bigint") { -+ if (timePointer < 0n || timePointer > BigInt(Number.MAX_SAFE_INTEGER)) { -+ return undefined; -+ } -+ pointer = Number(timePointer); -+ } else if (typeof timePointer === "number" && Number.isInteger(timePointer)) { -+ pointer = timePointer >>> 0; -+ } else { -+ return undefined; -+ } -+ -+ if (buffer !== memory.buffer) { -+ buffer = memory.buffer; -+ view = new DataView(buffer); -+ } -+ return pointer <= buffer.byteLength - 8 ? pointer : undefined; -+ } -+ -+ function callFallback(clockId, precision, timePointer) { -+ const errno = fallback(clockId, precision, timePointer); -+ if (clockId === 0) { -+ realtimeDirectReadsSinceFallback = 0; -+ lastRealtimeFallbackMillis = -+ errno === 0 ? sampleRealtimeMillis() : undefined; -+ } else if (clockId === 1) { -+ monotonicDirectReadsSinceFallback = 0; -+ const sampledMonotonicMillis = -+ errno === 0 ? samplePerformanceMillis() : undefined; -+ lastMonotonicFallbackMillis = sampledMonotonicMillis; -+ const pointer = errno === 0 ? refreshView(timePointer) : undefined; -+ monotonicAnchor = -+ sampledMonotonicMillis !== undefined && pointer !== undefined -+ ? { -+ millis: sampledMonotonicMillis, -+ nanoseconds: view.getBigUint64(pointer, true), -+ } -+ : undefined; -+ } -+ return errno; -+ } -+ -+ return function clock_time_get(clockId, precision, timePointer) { -+ if (state.canonical) return fallback(clockId, precision, timePointer); -+ let nanoseconds; -+ if (clockId === 0) { -+ const realtimeMillis = sampleRealtimeMillis(); -+ if (realtimeMillis === undefined || -+ lastRealtimeFallbackMillis === undefined || -+ realtimeMillis < lastRealtimeFallbackMillis || -+ realtimeDirectReadsSinceFallback >= MAX_DIRECT_READS_BETWEEN_FALLBACKS || -+ realtimeMillis - lastRealtimeFallbackMillis >= MAX_CANONICAL_INTERVAL_MILLIS) { -+ return callFallback(clockId, precision, timePointer); -+ } -+ nanoseconds = BigInt(Math.trunc(realtimeMillis)) * 1000000n; -+ } else if (clockId === 1) { -+ const monotonicMillis = samplePerformanceMillis(); -+ if (monotonicMillis === undefined || -+ lastMonotonicFallbackMillis === undefined || -+ monotonicMillis < lastMonotonicFallbackMillis || -+ monotonicDirectReadsSinceFallback >= MAX_DIRECT_READS_BETWEEN_FALLBACKS || -+ monotonicMillis - lastMonotonicFallbackMillis >= MAX_CANONICAL_INTERVAL_MILLIS || -+ monotonicAnchor === undefined || -+ monotonicMillis < monotonicAnchor.millis) { -+ return callFallback(clockId, precision, timePointer); -+ } -+ nanoseconds = monotonicAnchor.nanoseconds + -+ BigInt(Math.trunc((monotonicMillis - monotonicAnchor.millis) * 1000000)); -+ } else { -+ return callFallback(clockId, precision, timePointer); -+ } -+ -+ if (nanoseconds > MAX_CANONICAL_NANOSECONDS) { -+ return callFallback(clockId, precision, timePointer); -+ } -+ -+ const pointer = refreshView(timePointer); -+ if (pointer === undefined) { -+ return callFallback(clockId, precision, timePointer); -+ } -+ view.setBigUint64(pointer, nanoseconds, true); -+ if (clockId === 0) { -+ realtimeDirectReadsSinceFallback += 1; -+ } else { -+ monotonicDirectReadsSinceFallback += 1; -+ } -+ return 0; -+ }; -+} -+ -+export function oliphauntClockSetImport(memory, fallback) { -+ const state = clockState(memory); -+ return function clock_time_set(clockId, time) { -+ // Pending WASIX work may reenter another module. Switch all readers -+ // first, and stay canonical even when the setter returns an error. -+ state.canonical = true; -+ return fallback(clockId, time); -+ }; -+} -+"#)] -+extern "C" { -+ #[wasm_bindgen(js_name = oliphauntDirectClockImport)] -+ fn oliphaunt_direct_clock_import(memory: &JsValue, fallback: &JsValue) -> js_sys::Function; -+ #[wasm_bindgen(js_name = oliphauntClockSetImport)] -+ fn oliphaunt_clock_set_import(memory: &JsValue, fallback: &JsValue) -> js_sys::Function; -+} -+ -+#[cfg(all(feature = "js", target_arch = "wasm32"))] -+pub(crate) fn install_oliphaunt_direct_clock( -+ store: &mut impl AsStoreMut, -+ module: &wasmer::Module, -+ env: &FunctionEnv, -+ memory: Option<&wasmer::Memory>, -+ imports: &mut Imports, -+) -> Result<(), String> { -+ use wasmer::js::AsJs; -+ -+ let (host_policy, has_backoff, has_journal) = { -+ let store_ref = store.as_store_ref(); -+ let env = env.as_ref(&store_ref); -+ ( -+ env.host_policy, -+ env.enable_exponential_cpu_backoff.is_some(), -+ env.enable_journal, -+ ) -+ }; -+ if host_policy.clock_time_get() != capabilities::WasiClockTimeGetMode::DirectJs { -+ return Ok(()); -+ } -+ if host_policy.guest_execution() != capabilities::WasiGuestExecutionMode::SingleProgram { -+ return Err("direct JavaScript clock requires single-program guest execution".into()); -+ } -+ if has_backoff { -+ return Err("direct JavaScript clock is incompatible with CPU backoff".into()); -+ } -+ if has_journal { -+ return Err("direct JavaScript clock is incompatible with journaling".into()); -+ } -+ -+ const CLOCK_NAMESPACES: [&str; 4] = [ -+ "wasi_snapshot_preview1", -+ "wasi_unstable", -+ "wasix_32v1", -+ "wasix_64v1", -+ ]; -+ let clock_imports = module -+ .imports() -+ .filter(|import| { -+ CLOCK_NAMESPACES.contains(&import.module()) -+ && matches!(import.name(), "clock_time_get" | "clock_time_set") -+ }) -+ .map(|import| (import.module().to_string(), import.name().to_string())) -+ .collect::>(); -+ if clock_imports.is_empty() { -+ return Ok(()); -+ } -+ let memory = memory.ok_or_else(|| { -+ "direct JavaScript clock requires memory to be available before instantiation".to_string() -+ })?; -+ -+ let raw_memory = memory.as_jsvalue(&store.as_store_ref()); -+ for (namespace, name) in clock_imports { -+ let fallback = imports -+ .get_export(&namespace, &name) -+ .and_then(|export| match export { -+ wasmer::Extern::Function(function) => Some(function), -+ _ => None, -+ }) -+ .ok_or_else(|| format!("missing {namespace}.{name} canonical fallback"))?; -+ let function_type = fallback.ty(&store.as_store_ref()); -+ let raw_fallback = fallback.as_jsvalue(&store.as_store_ref()); -+ let raw_direct = if name == "clock_time_set" { -+ oliphaunt_clock_set_import(&raw_memory, &raw_fallback) -+ } else { -+ oliphaunt_direct_clock_import(&raw_memory, &raw_fallback) -+ }; -+ let direct = wasmer::Function::from_jsvalue(store, &function_type, raw_direct.as_ref()) -+ .map_err(|error| format!("failed to install {namespace}.{name}: {error:?}"))?; -+ imports.define(&namespace, &name, direct); -+ } -+ Ok(()) -+} -+ - pub use crate::{ - fs::{default_fs_backing, Fd, WasiFs, WasiInodes, VIRTUAL_ROOT_FD}, - os::{ -diff --git a/src/state/env.rs b/src/state/env.rs -index 77d8a892..11842968 100644 ---- a/src/state/env.rs -+++ b/src/state/env.rs -@@ -546,6 +546,20 @@ impl WasiEnv { - None - }; - -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ crate::install_oliphaunt_direct_clock( -+ &mut store, -+ &module, -+ &func_env.env, -+ imported_memory.as_ref(), -+ &mut import_object, -+ ) -+ .map_err(|error| { -+ WasiThreadError::InstanceCreateFailed(Box::new(wasmer::InstantiationError::Link( -+ wasmer::LinkError::Resource(error), -+ ))) -+ })?; -+ - // Construct the instance. - let instance_result = if async_instantiation { - #[cfg(all(feature = "js", target_arch = "wasm32"))] -diff --git a/src/state/linker.rs b/src/state/linker.rs -index 4f509100..e462986d 100644 ---- a/src/state/linker.rs -+++ b/src/state/linker.rs -@@ -1180,6 +1180,20 @@ impl Linker { - &well_known_imports, - )?; - -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ crate::install_oliphaunt_direct_clock( -+ store, -+ main_module, -+ &func_env.env, -+ Some(&memory), -+ &mut imports, -+ ) -+ .map_err(|error| { -+ LinkError::InstantiationError(InstantiationError::Link(wasmer::LinkError::Resource( -+ error, -+ ))) -+ })?; -+ - // TODO: figure out which way is faster (stubs in main or stubs in sides), - // use that ordering. My *guess* is that, since main exports all the libc - // functions and those are called frequently by basically any code, then giving -@@ -1428,6 +1442,20 @@ impl Linker { - &mut pending_resolutions, - )?; - -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ crate::install_oliphaunt_direct_clock( -+ store, -+ &main_module, -+ &func_env.env, -+ Some(&memory), -+ &mut imports, -+ ) -+ .map_err(|error| { -+ LinkError::InstantiationError(InstantiationError::Link(wasmer::LinkError::Resource( -+ error, -+ ))) -+ })?; -+ - let main_instance = Instance::new(store, &main_module, &imports)?; - - instance_group.main_instance = Some(main_instance.clone()); -@@ -2647,6 +2675,20 @@ impl InstanceGroupState { - &well_known_imports, - )?; - -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ crate::install_oliphaunt_direct_clock( -+ store, -+ &module, -+ env, -+ Some(&self.memory), -+ &mut imports, -+ ) -+ .map_err(|error| { -+ LinkError::InstantiationError(InstantiationError::Link(wasmer::LinkError::Resource( -+ error, -+ ))) -+ })?; -+ - let instance = Instance::new(store, &module, &imports)?; - - let instance_handles = WasiModuleInstanceHandles::new( -@@ -2757,6 +2799,20 @@ impl InstanceGroupState { - pending_resolutions, - )?; - -+ #[cfg(all(feature = "js", target_arch = "wasm32"))] -+ crate::install_oliphaunt_direct_clock( -+ store, -+ &dl_module.module, -+ env, -+ Some(&self.memory), -+ &mut imports, -+ ) -+ .map_err(|error| { -+ LinkError::InstantiationError(InstantiationError::Link(wasmer::LinkError::Resource( -+ error, -+ ))) -+ })?; -+ - let instance = Instance::new(store, &dl_module.module, &imports)?; - - // This is a non-main instance of a side module, so it needs a new TLS area -diff --git a/src/syscalls/wasm.rs b/src/syscalls/wasm.rs -index dd731f0..1cac381 100644 ---- a/src/syscalls/wasm.rs -+++ b/src/syscalls/wasm.rs -@@ -1,6 +1,4 @@ --use std::mem; -- --use chrono::prelude::*; -+use wasm_bindgen::prelude::wasm_bindgen; - use wasmer::WasmRef; - - use crate::syscalls::types::{ -@@ -8,15 +6,33 @@ use crate::syscalls::types::{ - *, - }; - -+// WASIX processes share Rust memory across workers, but performance.now() is -+// relative to each worker's creation. Include timeOrigin so every process uses -+// the same monotonic timeline (https://www.w3.org/TR/hr-time-2/). -+#[wasm_bindgen(inline_js = r#" -+export function oliphaunt_monotonic_time_ms() { -+ return performance.timeOrigin + performance.now(); -+} -+"#)] -+extern "C" { -+ fn oliphaunt_monotonic_time_ms() -> f64; -+} -+ -+fn monotonic_time_ns() -> Result { -+ let time = std::time::Duration::try_from_secs_f64(oliphaunt_monotonic_time_ms() / 1000.0) -+ .map_err(|_| Errno::Overflow)?; -+ i64::try_from(time.as_nanos()).map_err(|_| Errno::Overflow) -+} -+ - pub fn platform_clock_res_get( - clock_id: Snapshot0Clockid, - resolution: WasmRef, - ) -> Result { - let t_out = match clock_id { -- Snapshot0Clockid::Monotonic => 10_000_000, -- Snapshot0Clockid::Realtime => 1, -- Snapshot0Clockid::ProcessCputimeId => 1, -- Snapshot0Clockid::ThreadCputimeId => 1, -+ Snapshot0Clockid::Monotonic | Snapshot0Clockid::Realtime => 1_000_000, -+ Snapshot0Clockid::ProcessCputimeId | Snapshot0Clockid::ThreadCputimeId => { -+ return Err(Errno::Notsup) -+ } - _ => return Err(Errno::Inval), - }; - Ok(t_out) -@@ -24,10 +40,22 @@ pub fn platform_clock_res_get( - - pub fn platform_clock_time_get( - clock_id: Snapshot0Clockid, -- precision: Timestamp, -+ _precision: Timestamp, - ) -> Result { -- Local::now() -- .timestamp_nanos_opt() -- .map(|ts| ts as i64) -- .ok_or(Errno::Overflow) -+ match clock_id { -+ Snapshot0Clockid::Realtime => { -+ let millis = js_sys::Date::now(); -+ if !millis.is_finite() || millis < 0.0 || millis > i64::MAX as f64 { -+ return Err(Errno::Overflow); -+ } -+ (millis as i64) -+ .checked_mul(1_000_000) -+ .ok_or(Errno::Overflow) -+ } -+ Snapshot0Clockid::Monotonic => monotonic_time_ns(), -+ Snapshot0Clockid::ProcessCputimeId | Snapshot0Clockid::ThreadCputimeId => { -+ Err(Errno::Notsup) -+ } -+ _ => Err(Errno::Inval), -+ } - } diff --git a/src/wasix/browser-host/patches/0014-wasmer-js-track-directory-mutations.patch b/src/wasix/browser-host/patches/0014-wasmer-js-track-directory-mutations.patch deleted file mode 100644 index b645bcf54..000000000 --- a/src/wasix/browser-host/patches/0014-wasmer-js-track-directory-mutations.patch +++ /dev/null @@ -1,408 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: expose directory mutation tracking - -Track changed filesystem paths so persistent stores publish mutations instead -of rescanning an entire database after every operation. Keep untracked mounts -available for immutable runtime data. This interface is downstream storage -integration; upstream value depends on a general dirty-path API and ownership -contract, not the PostgreSQL benchmark alone. - -diff --git a/src/fs/directory.rs b/src/fs/directory.rs -index 34af7da..51fafa5 100644 ---- a/src/fs/directory.rs -+++ b/src/fs/directory.rs -@@ -1,11 +1,17 @@ - use std::{ -+ collections::BTreeSet, - path::{Path, PathBuf}, -- sync::Arc, -+ pin::Pin, -+ sync::{Arc, Mutex}, -+ task::{Context as TaskContext, Poll}, - }; - - use anyhow::Context; --use js_sys::Reflect; -+use js_sys::{Array, Reflect}; - use tracing::Instrument; --use virtual_fs::{AsyncReadExt, AsyncWriteExt, FileSystem, FileType}; -+use virtual_fs::{ -+ AsyncRead, AsyncReadExt, AsyncSeek, AsyncWrite, AsyncWriteExt, FileSystem, FileType, ReadBuf, -+ VirtualFile, -+}; - use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; - use wasmer_wasix::runtime::task_manager::InlineWaker; -@@ -15,7 +21,11 @@ use crate::{utils::Error, StringOrBytes}; - /// A directory that can be mounted inside a WASIX instance. - #[derive(Debug, Clone, wasm_bindgen_derive::TryFromJsValue)] - #[wasm_bindgen] --pub struct Directory(Arc); -+pub struct Directory { -+ fs: Arc, -+ changes: Arc>>, -+ track_changes: bool, -+} - - #[wasm_bindgen] - impl Directory { -@@ -25,7 +34,7 @@ impl Directory { - match init { - Some(init) => { - let fs = init.initialize()?; -- Ok(Directory(fs)) -+ Ok(Directory::from_filesystem(fs)) - } - None => Ok(Directory::default()), - } -@@ -141,9 +150,78 @@ impl Directory { - - Ok(()) - } -+ -+ /// List paths changed through this directory since the last acknowledgement. -+ /// -+ /// The paths are relative to the directory root. A consumer treats each -+ /// path's current state as authoritative, so the same compact journal -+ /// represents file writes, creates, removes, and directory renames. -+ #[wasm_bindgen(js_name = "changedPaths")] -+ pub fn changed_paths(&self) -> Result { -+ let changes = self -+ .changes -+ .lock() -+ .map_err(|_| anyhow::anyhow!("directory change journal is poisoned"))?; -+ let values = Array::new(); -+ for path in changes.iter() { -+ values.push(&JsValue::from(path)); -+ } -+ Ok(values) -+ } -+ -+ /// Discard the current change journal without changing directory contents. -+ #[wasm_bindgen(js_name = "clearChanges")] -+ pub fn clear_changes(&self) -> Result<(), Error> { -+ self.changes -+ .lock() -+ .map_err(|_| anyhow::anyhow!("directory change journal is poisoned"))? -+ .clear(); -+ Ok(()) -+ } -+ -+ /// Inspect one path without parsing exception text to distinguish a -+ /// missing entry from a genuine storage failure. -+ #[wasm_bindgen(js_name = "entryType")] -+ pub fn entry_type(&self, mut path: String) -> Result { -+ if !path.starts_with('/') { -+ path.insert(0, '/'); -+ } -+ match self.fs.metadata(Path::new(&path)) { -+ Ok(metadata) if metadata.is_dir() => Ok("dir".to_owned()), -+ Ok(metadata) if metadata.is_file() => Ok("file".to_owned()), -+ Ok(_) => Ok("unknown".to_owned()), -+ Err(virtual_fs::FsError::EntryNotFound) => Ok("missing".to_owned()), -+ Err(error) => Err(error.into()), -+ } -+ } - } - - impl Directory { -+ fn from_filesystem(fs: Arc) -> Self { -+ Self::from_filesystem_with_tracking(fs, true) -+ } -+ -+ fn from_untracked_filesystem(fs: Arc) -> Self { -+ Self::from_filesystem_with_tracking(fs, false) -+ } -+ -+ fn from_filesystem_with_tracking( -+ fs: Arc, -+ track_changes: bool, -+ ) -> Self { -+ Self { -+ fs, -+ changes: Arc::new(Mutex::new(BTreeSet::new())), -+ track_changes, -+ } -+ } -+ -+ fn record_change(&self, path: &Path) { -+ if self.track_changes { -+ record_change(&self.changes, path); -+ } -+ } -+ - async fn _read_file(&self, mut path: String) -> Result, Error> { - if !path.starts_with('/') { - path.insert(0, '/'); -@@ -159,24 +237,28 @@ impl Directory { - - impl Default for Directory { - fn default() -> Self { -- Directory(Arc::new(virtual_fs::mem_fs::FileSystem::default())) -+ Directory::from_filesystem(Arc::new(virtual_fs::mem_fs::FileSystem::default())) - } - } - - impl FileSystem for Directory { - #[tracing::instrument(level = "trace", skip(self))] - fn read_dir(&self, path: &std::path::Path) -> virtual_fs::Result { -- self.0.read_dir(path) -+ self.fs.read_dir(path) - } - - #[tracing::instrument(level = "trace", skip(self))] - fn create_dir(&self, path: &std::path::Path) -> virtual_fs::Result<()> { -- self.0.create_dir(path) -+ self.fs.create_dir(path)?; -+ self.record_change(path); -+ Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self))] - fn remove_dir(&self, path: &std::path::Path) -> virtual_fs::Result<()> { -- self.0.remove_dir(path) -+ self.fs.remove_dir(path)?; -+ self.record_change(path); -+ Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self))] -@@ -185,17 +267,31 @@ impl FileSystem for Directory { - from: &'a std::path::Path, - to: &'a std::path::Path, - ) -> futures::future::BoxFuture<'a, virtual_fs::Result<()>> { -- Box::pin(self.0.rename(from, to).in_current_span()) -+ if !self.track_changes { -+ return Box::pin(self.fs.rename(from, to).in_current_span()); -+ } -+ let operation = self.fs.rename(from, to).in_current_span(); -+ let changes = self.changes.clone(); -+ let from = from.to_path_buf(); -+ let to = to.to_path_buf(); -+ Box::pin(async move { -+ operation.await?; -+ record_change(&changes, &from); -+ record_change(&changes, &to); -+ Ok(()) -+ }) - } - - #[tracing::instrument(level = "trace", skip(self))] - fn metadata(&self, path: &std::path::Path) -> virtual_fs::Result { -- self.0.metadata(path) -+ self.fs.metadata(path) - } - - #[tracing::instrument(level = "trace", skip(self))] - fn remove_file(&self, path: &std::path::Path) -> virtual_fs::Result<()> { -- self.0.remove_file(path) -+ self.fs.remove_file(path)?; -+ self.record_change(path); -+ Ok(()) - } - - fn new_open_options(&self) -> virtual_fs::OpenOptions { -@@ -204,18 +300,22 @@ impl FileSystem for Directory { - - #[tracing::instrument(level = "trace", skip(self))] - fn readlink(&self, path: &Path) -> virtual_fs::Result { -- self.0.readlink(path) -+ self.fs.readlink(path) - } - - #[tracing::instrument(level = "trace", skip(self))] - fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result { -- self.0.symlink_metadata(path) -+ self.fs.symlink_metadata(path) - } - - #[tracing::instrument(level = "trace", skip(self))] -- fn mount(&self, name: String, path: &Path, fs: Box) -- -> virtual_fs::Result<()> { -- self.0.mount(name, path, fs) -+ fn mount( -+ &self, -+ name: String, -+ path: &Path, -+ fs: Box, -+ ) -> virtual_fs::Result<()> { -+ self.fs.mount(name, path, fs) - } - } - -@@ -226,9 +326,174 @@ impl virtual_fs::FileOpener for Directory { - path: &std::path::Path, - conf: &virtual_fs::OpenOptionsConfig, - ) -> virtual_fs::Result> { -- self.0.new_open_options().options(conf.clone()).open(path) -+ if !self.track_changes { -+ return self.fs.new_open_options().options(conf.clone()).open(path); -+ } -+ -+ let existed = if conf.create && !conf.truncate && !conf.create_new { -+ self.fs.metadata(path).is_ok() -+ } else { -+ true -+ }; -+ let file = self -+ .fs -+ .new_open_options() -+ .options(conf.clone()) -+ .open(path)?; -+ if conf.write || conf.append || conf.truncate || conf.create || conf.create_new { -+ if conf.truncate || conf.create_new || (conf.create && !existed) { -+ self.record_change(path); -+ } -+ Ok(Box::new(ChangeTrackingFile { -+ file, -+ path: path.to_path_buf(), -+ changes: self.changes.clone(), -+ })) -+ } else { -+ Ok(file) -+ } -+ } -+} -+ -+#[derive(Debug)] -+struct ChangeTrackingFile { -+ file: Box, -+ path: PathBuf, -+ changes: Arc>>, -+} -+ -+impl ChangeTrackingFile { -+ fn record_change(&self) { -+ record_change(&self.changes, &self.path); -+ } -+} -+ -+impl VirtualFile for ChangeTrackingFile { -+ fn last_accessed(&self) -> u64 { -+ self.file.last_accessed() -+ } -+ -+ fn last_modified(&self) -> u64 { -+ self.file.last_modified() -+ } -+ -+ fn created_time(&self) -> u64 { -+ self.file.created_time() -+ } -+ -+ fn set_times(&mut self, atime: Option, mtime: Option) -> virtual_fs::Result<()> { -+ self.file.set_times(atime, mtime) -+ } -+ -+ fn size(&self) -> u64 { -+ self.file.size() -+ } -+ -+ fn set_len(&mut self, new_size: u64) -> virtual_fs::Result<()> { -+ self.file.set_len(new_size)?; -+ self.record_change(); -+ Ok(()) -+ } -+ -+ fn unlink(&mut self) -> virtual_fs::Result<()> { -+ self.file.unlink()?; -+ self.record_change(); -+ Ok(()) -+ } -+ -+ fn is_open(&self) -> bool { -+ self.file.is_open() -+ } -+ -+ fn get_special_fd(&self) -> Option { -+ self.file.get_special_fd() -+ } -+ -+ fn write_from_mmap(&mut self, offset: u64, len: u64) -> std::io::Result<()> { -+ self.file.write_from_mmap(offset, len)?; -+ self.record_change(); -+ Ok(()) -+ } -+ -+ fn poll_read_ready( -+ mut self: Pin<&mut Self>, -+ cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Pin::new(&mut *self.file).poll_read_ready(cx) -+ } -+ -+ fn poll_write_ready( -+ mut self: Pin<&mut Self>, -+ cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Pin::new(&mut *self.file).poll_write_ready(cx) -+ } -+} -+ -+impl AsyncRead for ChangeTrackingFile { -+ fn poll_read( -+ mut self: Pin<&mut Self>, -+ cx: &mut TaskContext<'_>, -+ buffer: &mut ReadBuf<'_>, -+ ) -> Poll> { -+ Pin::new(&mut *self.file).poll_read(cx, buffer) -+ } -+} -+ -+impl AsyncWrite for ChangeTrackingFile { -+ fn poll_write( -+ mut self: Pin<&mut Self>, -+ cx: &mut TaskContext<'_>, -+ buffer: &[u8], -+ ) -> Poll> { -+ let result = Pin::new(&mut *self.file).poll_write(cx, buffer); -+ if matches!(result, Poll::Ready(Ok(written)) if written > 0) { -+ self.record_change(); -+ } -+ result -+ } -+ -+ fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { -+ Pin::new(&mut *self.file).poll_flush(cx) -+ } -+ -+ fn poll_shutdown( -+ mut self: Pin<&mut Self>, -+ cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Pin::new(&mut *self.file).poll_shutdown(cx) -+ } -+} -+ -+impl AsyncSeek for ChangeTrackingFile { -+ fn start_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> { -+ Pin::new(&mut *self.file).start_seek(position) -+ } -+ -+ fn poll_complete( -+ mut self: Pin<&mut Self>, -+ cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Pin::new(&mut *self.file).poll_complete(cx) -+ } -+} -+ -+fn record_change(changes: &Mutex>, path: &Path) { -+ let mut relative = PathBuf::new(); -+ for component in path.components() { -+ match component { -+ std::path::Component::Prefix(_) | std::path::Component::RootDir => {} -+ std::path::Component::CurDir => {} -+ std::path::Component::ParentDir => { -+ relative.pop(); -+ } -+ std::path::Component::Normal(part) => relative.push(part), -+ } -+ } -+ if let Ok(mut journal) = changes.lock() { -+ journal.insert(relative.to_string_lossy().replace('\\', "/")); - } - } - - #[wasm_bindgen(typescript_custom_section)] - const DIRENTRY_TYPE_DEF: &'static str = r#" diff --git a/src/wasix/browser-host/patches/0015-wasmer-js-add-sync-filesystem-bridge.patch b/src/wasix/browser-host/patches/0015-wasmer-js-add-sync-filesystem-bridge.patch deleted file mode 100644 index e67f11450..000000000 --- a/src/wasix/browser-host/patches/0015-wasmer-js-add-sync-filesystem-bridge.patch +++ /dev/null @@ -1,780 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: bridge synchronous filesystem operations safely - -Adapt the browser's synchronous storage provider to virtual-fs, preserving -provider errors and filesystem semantics. Each cross-language transfer uses a -fresh owned JS buffer so callback retention or memory growth cannot alias guest -memory. The bridge protocol is Oliphaunt-specific; its ownership guarantees -are required correctness, not an optional copy-elision experiment. - -diff --git a/src/fs/directory.rs b/src/fs/directory.rs -index 7c28ec4..da777a4 100644 ---- a/src/fs/directory.rs -+++ b/src/fs/directory.rs -@@ -18,6 +18,8 @@ use wasmer_wasix::runtime::task_manager::InlineWaker; - - use crate::{utils::Error, StringOrBytes}; - -+use super::SyncBridgeFileSystem; -+ - /// A directory that can be mounted inside a WASIX instance. - #[derive(Debug, Clone, wasm_bindgen_derive::TryFromJsValue)] - #[wasm_bindgen] -@@ -41,6 +43,16 @@ impl Directory { - } - } - -+ /// Create a directory whose filesystem is served synchronously by a -+ /// caller-realm JavaScript backend. Oliphaunt uses this only from its -+ /// single-backend worker, where OPFS synchronous access handles are legal. -+ #[wasm_bindgen(js_name = "createSync")] -+ pub fn create_sync(backend: JsValue, capacity: usize) -> Result { -+ Ok(Directory::from_untracked_filesystem(Arc::new( -+ SyncBridgeFileSystem::new(backend, capacity)?, -+ ))) -+ } -+ - /// Read the contents of a directory. - #[wasm_bindgen(js_name = "readDir")] - pub async fn read_dir(&self, mut path: String) -> Result { -diff --git a/src/fs/mod.rs b/src/fs/mod.rs -index 5053993..058a657 100644 ---- a/src/fs/mod.rs -+++ b/src/fs/mod.rs -@@ -1,3 +1,5 @@ - mod directory; -+mod sync_bridge; - - pub use self::directory::{Directory, DirectoryInit}; -+pub(crate) use self::sync_bridge::SyncBridgeFileSystem; -diff --git a/src/fs/sync_bridge.rs b/src/fs/sync_bridge.rs -new file mode 100644 -index 0000000..b9c3c12 ---- /dev/null -+++ b/src/fs/sync_bridge.rs -@@ -0,0 +1,724 @@ -+use std::{ -+ cell::RefCell, -+ collections::HashMap, -+ fmt, -+ io::{self, SeekFrom}, -+ path::{Path, PathBuf}, -+ pin::Pin, -+ rc::Rc, -+ sync::{ -+ atomic::{AtomicU32, Ordering}, -+ Arc, Mutex, -+ }, -+ task::{Context, Poll}, -+}; -+ -+use futures::future::BoxFuture; -+use js_sys::{Array, Function, Uint8Array}; -+use virtual_fs::{ -+ AsyncRead, AsyncSeek, AsyncWrite, DirEntry, FileOpener, FileSystem, FileType, FsError, -+ Metadata, OpenOptions, OpenOptionsConfig, ReadBuf, ReadDir, VirtualFile, -+}; -+use wasm_bindgen::{JsCast, JsValue}; -+ -+const OP_METADATA: i32 = 1; -+const OP_READ_DIR: i32 = 2; -+const OP_CREATE_DIR: i32 = 3; -+const OP_REMOVE_DIR: i32 = 4; -+const OP_RENAME: i32 = 5; -+const OP_REMOVE_FILE: i32 = 6; -+const OP_OPEN: i32 = 7; -+const OP_CLOSE: i32 = 8; -+const OP_READ: i32 = 9; -+const OP_WRITE: i32 = 10; -+const OP_FLUSH: i32 = 11; -+const OP_TRUNCATE: i32 = 12; -+const OP_UNLINK: i32 = 13; -+const OP_FILE_SIZE: i32 = 14; -+const READ_DIR_PAGE_CAPACITY: usize = 64 * 1024; -+ -+const RESULT_OK: i32 = 0; -+const RESULT_NOT_FOUND: i32 = 1; -+const RESULT_EXISTS: i32 = 2; -+const RESULT_NOT_DIR: i32 = 3; -+const RESULT_NOT_FILE: i32 = 4; -+const RESULT_DIR_NOT_EMPTY: i32 = 5; -+const RESULT_PERMISSION: i32 = 6; -+const RESULT_INVALID: i32 = 7; -+const RESULT_STORAGE_FULL: i32 = 8; -+const RESULT_UNSUPPORTED: i32 = 9; -+const RESULT_TIMEOUT: i32 = 10; -+ -+const TYPE_FILE: u64 = 1; -+const TYPE_DIRECTORY: u64 = 2; -+ -+const FLAG_READ: i32 = 1 << 0; -+const FLAG_WRITE: i32 = 1 << 1; -+const FLAG_CREATE_NEW: i32 = 1 << 2; -+const FLAG_CREATE: i32 = 1 << 3; -+const FLAG_APPEND: i32 = 1 << 4; -+const FLAG_TRUNCATE: i32 = 1 << 5; -+ -+static NEXT_BACKEND_ID: AtomicU32 = AtomicU32::new(1); -+ -+thread_local! { -+ /// JavaScript references must remain in the realm that created them. -+ /// Rust filesystem objects carry only the corresponding numeric key. -+ static REALM_BACKENDS: RefCell>> = -+ RefCell::new(HashMap::new()); -+} -+ -+struct RealmBackend { -+ backend: JsValue, -+ request: Function, -+} -+ -+/// A synchronous virtual filesystem delegated to a JavaScript object in the -+/// caller realm. The backend owns OPFS access handles while Rust owns the -+/// syscall-facing filesystem. Calls are deliberately single-flight because -+/// Oliphaunt's PostgreSQL runtime is single-backend. -+pub(crate) struct SyncBridgeFileSystem { -+ backend: Arc, -+} -+ -+impl fmt::Debug for SyncBridgeFileSystem { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ formatter -+ .debug_struct("SyncBridgeFileSystem") -+ .finish_non_exhaustive() -+ } -+} -+ -+impl SyncBridgeFileSystem { -+ pub(crate) fn new(backend: JsValue, capacity: usize) -> virtual_fs::Result { -+ Ok(Self { -+ backend: Arc::new(Backend::new(backend, capacity)?), -+ }) -+ } -+ -+ fn metadata_inner(&self, path: &Path) -> virtual_fs::Result { -+ let path = normalize_path(path)?; -+ let response = self.backend.request(OP_METADATA, &path, &[], 0, 0, 0)?; -+ metadata(response.value0, response.value1) -+ } -+} -+ -+impl FileSystem for SyncBridgeFileSystem { -+ fn readlink(&self, _path: &Path) -> virtual_fs::Result { -+ Err(FsError::Unsupported) -+ } -+ -+ fn read_dir(&self, path: &Path) -> virtual_fs::Result { -+ let path = normalize_path(path)?; -+ let mut cursor = 0; -+ let mut entries = Vec::new(); -+ loop { -+ let response = self -+ .backend -+ .request(OP_READ_DIR, &path, &[], cursor, 0, 0)?; -+ entries.extend(decode_directory_entries(&path, &response.bytes)?); -+ match response.value1 { -+ 1 => break, -+ 0 if response.value0 > cursor => cursor = response.value0, -+ _ => return Err(FsError::InvalidData), -+ } -+ } -+ Ok(ReadDir::new(entries)) -+ } -+ -+ fn create_dir(&self, path: &Path) -> virtual_fs::Result<()> { -+ let path = normalize_path(path)?; -+ self.backend.request(OP_CREATE_DIR, &path, &[], 0, 0, 0)?; -+ Ok(()) -+ } -+ -+ fn remove_dir(&self, path: &Path) -> virtual_fs::Result<()> { -+ let path = normalize_path(path)?; -+ self.backend.request(OP_REMOVE_DIR, &path, &[], 0, 0, 0)?; -+ Ok(()) -+ } -+ -+ fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, virtual_fs::Result<()>> { -+ let result = (|| { -+ let from = normalize_path(from)?; -+ let to = normalize_path(to)?; -+ self.backend -+ .request(OP_RENAME, &from, to.as_bytes(), 0, 0, 0)?; -+ Ok(()) -+ })(); -+ Box::pin(async move { result }) -+ } -+ -+ fn metadata(&self, path: &Path) -> virtual_fs::Result { -+ self.metadata_inner(path) -+ } -+ -+ fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result { -+ self.metadata_inner(path) -+ } -+ -+ fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> { -+ let path = normalize_path(path)?; -+ self.backend.request(OP_REMOVE_FILE, &path, &[], 0, 0, 0)?; -+ Ok(()) -+ } -+ -+ fn new_open_options(&self) -> OpenOptions { -+ OpenOptions::new(self) -+ } -+ -+ fn mount( -+ &self, -+ _name: String, -+ _path: &Path, -+ _fs: Box, -+ ) -> virtual_fs::Result<()> { -+ Err(FsError::Unsupported) -+ } -+} -+ -+impl FileOpener for SyncBridgeFileSystem { -+ fn open( -+ &self, -+ path: &Path, -+ configuration: &OpenOptionsConfig, -+ ) -> virtual_fs::Result> { -+ let path = normalize_path(path)?; -+ let flags = (if configuration.read { FLAG_READ } else { 0 }) -+ | (if configuration.write { FLAG_WRITE } else { 0 }) -+ | (if configuration.create_new { -+ FLAG_CREATE_NEW -+ } else { -+ 0 -+ }) -+ | (if configuration.create { FLAG_CREATE } else { 0 }) -+ | (if configuration.append { FLAG_APPEND } else { 0 }) -+ | (if configuration.truncate { -+ FLAG_TRUNCATE -+ } else { -+ 0 -+ }); -+ let response = self.backend.request(OP_OPEN, &path, &[], 0, 0, flags)?; -+ let cursor = if configuration.append { -+ response.value1 -+ } else { -+ 0 -+ }; -+ Ok(Box::new(SyncBridgeFile { -+ backend: self.backend.clone(), -+ descriptor: u32::try_from(response.value0).map_err(|_| FsError::InvalidFd)?, -+ readable: configuration.read, -+ writable: configuration.write || configuration.append, -+ cursor, -+ })) -+ } -+} -+ -+struct Backend { -+ id: u32, -+ origin_thread: u32, -+ capacity: usize, -+ gate: Mutex<()>, -+} -+ -+impl fmt::Debug for Backend { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ formatter -+ .debug_struct("Backend") -+ .field("id", &self.id) -+ .field("origin_thread", &self.origin_thread) -+ .field("capacity", &self.capacity()) -+ .finish() -+ } -+} -+ -+impl Backend { -+ fn new(backend: JsValue, capacity: usize) -> virtual_fs::Result { -+ if capacity < 8 * 1024 || capacity > 4 * 1024 * 1024 { -+ return Err(FsError::InvalidInput); -+ } -+ let request = js_sys::Reflect::get(&backend, &JsValue::from_str("request")) -+ .map_err(|_| FsError::InvalidInput)? -+ .dyn_into::() -+ .map_err(|_| FsError::InvalidInput)?; -+ let origin_thread = wasmer::js::current_thread_id(); -+ let id = REALM_BACKENDS -+ .try_with(|backends| { -+ let mut backends = backends.try_borrow_mut().map_err(|_| FsError::Lock)?; -+ let id = loop { -+ let candidate = NEXT_BACKEND_ID.fetch_add(1, Ordering::Relaxed); -+ if candidate != 0 && !backends.contains_key(&candidate) { -+ break candidate; -+ } -+ }; -+ backends.insert(id, Rc::new(RealmBackend { backend, request })); -+ Ok::(id) -+ }) -+ .map_err(|_| FsError::Lock)??; -+ Ok(Self { -+ id, -+ origin_thread, -+ capacity, -+ gate: Mutex::new(()), -+ }) -+ } -+ -+ fn capacity(&self) -> usize { -+ self.capacity -+ } -+ -+ fn realm_backend(&self) -> virtual_fs::Result> { -+ if wasmer::js::current_thread_id() != self.origin_thread { -+ return Err(FsError::Lock); -+ } -+ REALM_BACKENDS -+ .try_with(|backends| { -+ let backends = backends.try_borrow().map_err(|_| FsError::Lock)?; -+ backends -+ .get(&self.id) -+ .map(Rc::clone) -+ .ok_or(FsError::InvalidFd) -+ }) -+ .map_err(|_| FsError::Lock)? -+ } -+ -+ fn request( -+ &self, -+ opcode: i32, -+ path: &str, -+ payload: &[u8], -+ arg0: u64, -+ arg1: u64, -+ flags: i32, -+ ) -> virtual_fs::Result { -+ let response_capacity = match opcode { -+ OP_READ_DIR => self.capacity().min(READ_DIR_PAGE_CAPACITY), -+ _ => 0, -+ }; -+ let mut bytes = vec![0; response_capacity]; -+ let response = self.request_into(opcode, path, payload, &mut bytes, arg0, arg1, flags)?; -+ bytes.truncate(response.length); -+ Ok(Response { -+ bytes, -+ value0: response.value0, -+ value1: response.value1, -+ }) -+ } -+ -+ fn read(&self, descriptor: u32, offset: u64, output: &mut [u8]) -> virtual_fs::Result { -+ let response = self.request_into( -+ OP_READ, -+ "", -+ &[], -+ output, -+ descriptor.into(), -+ offset, -+ i32::try_from(output.len()).map_err(|_| FsError::StorageFull)?, -+ )?; -+ Ok(response.length) -+ } -+ -+ #[allow(clippy::too_many_arguments)] -+ fn request_into( -+ &self, -+ opcode: i32, -+ path: &str, -+ payload: &[u8], -+ output: &mut [u8], -+ arg0: u64, -+ arg1: u64, -+ flags: i32, -+ ) -> virtual_fs::Result { -+ // A JavaScript callback can synchronously reenter this filesystem. -+ // Never wait for a lock that the current call already holds. -+ let _guard = self.gate.try_lock().map_err(|_| FsError::Lock)?; -+ if payload.len() > self.capacity() || output.len() > self.capacity() { -+ return Err(FsError::StorageFull); -+ } -+ if !payload.is_empty() && !output.is_empty() { -+ return Err(FsError::InvalidInput); -+ } -+ let realm_backend = self.realm_backend()?; -+ let transfer_length = payload.len().max(output.len()); -+ let transfer = Uint8Array::new_with_length( -+ u32::try_from(transfer_length).map_err(|_| FsError::StorageFull)?, -+ ); -+ if !payload.is_empty() { -+ transfer.copy_from(payload); -+ } -+ let arguments = Array::new(); -+ arguments.push(&JsValue::from(opcode)); -+ arguments.push(&JsValue::from_str(path)); -+ // Each callback receives an exact-sized JavaScript-owned allocation. -+ // It may synchronously mutate, retain, or transfer the bytes without -+ // aliasing Rust memory or observing storage from another request. -+ arguments.push(transfer.as_ref()); -+ arguments.push(&JsValue::from_f64(arg0 as f64)); -+ arguments.push(&JsValue::from_f64(arg1 as f64)); -+ arguments.push(&JsValue::from(flags)); -+ let raw = realm_backend -+ .request -+ .apply(&realm_backend.backend, &arguments) -+ .map_err(|_| FsError::IOError)?; -+ let values = Array::from(&raw); -+ if values.length() != 4 { -+ return Err(FsError::InvalidData); -+ } -+ let result = js_i32(&values.get(0))?; -+ let response_len = js_usize(&values.get(1))?; -+ let value0 = js_u64(&values.get(2))?; -+ let value1 = js_u64(&values.get(3))?; -+ decode_result(result)?; -+ if response_len > output.len() { -+ return Err(FsError::InvalidData); -+ } -+ if response_len > 0 { -+ // A callback may transfer the allocation after performing a -+ // side-effecting operation. Detachment is only an error when a -+ // successful response still has bytes that Rust must copy. -+ if transfer.length() as usize != transfer_length { -+ return Err(FsError::InvalidData); -+ } -+ transfer -+ .subarray( -+ 0, -+ u32::try_from(response_len).map_err(|_| FsError::InvalidData)?, -+ ) -+ .copy_to(&mut output[..response_len]); -+ } -+ Ok(ResponseHead { -+ length: response_len, -+ value0, -+ value1, -+ }) -+ } -+} -+ -+impl Drop for Backend { -+ fn drop(&mut self) { -+ // A filesystem may be released on a different worker because the -+ // virtual-fs traits require Send + Sync. Never touch foreign-realm JS -+ // references; in that exceptional case the realm-local entry leaks. -+ if wasmer::js::current_thread_id() != self.origin_thread { -+ return; -+ } -+ let _ = REALM_BACKENDS.try_with(|backends| { -+ if let Ok(mut backends) = backends.try_borrow_mut() { -+ backends.remove(&self.id); -+ } -+ }); -+ } -+} -+ -+struct ResponseHead { -+ length: usize, -+ value0: u64, -+ value1: u64, -+} -+ -+struct Response { -+ bytes: Vec, -+ value0: u64, -+ value1: u64, -+} -+ -+struct SyncBridgeFile { -+ backend: Arc, -+ descriptor: u32, -+ readable: bool, -+ writable: bool, -+ cursor: u64, -+} -+ -+impl fmt::Debug for SyncBridgeFile { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ formatter -+ .debug_struct("SyncBridgeFile") -+ .field("descriptor", &self.descriptor) -+ .field("cursor", &self.cursor) -+ .finish_non_exhaustive() -+ } -+} -+ -+impl Drop for SyncBridgeFile { -+ fn drop(&mut self) { -+ let _ = self -+ .backend -+ .request(OP_CLOSE, "", &[], self.descriptor.into(), 0, 0); -+ } -+} -+ -+impl VirtualFile for SyncBridgeFile { -+ fn last_accessed(&self) -> u64 { -+ 0 -+ } -+ -+ fn last_modified(&self) -> u64 { -+ 0 -+ } -+ -+ fn created_time(&self) -> u64 { -+ 0 -+ } -+ -+ fn size(&self) -> u64 { -+ self.backend -+ .request(OP_FILE_SIZE, "", &[], self.descriptor.into(), 0, 0) -+ .map(|response| response.value0) -+ .unwrap_or(0) -+ } -+ -+ fn set_len(&mut self, new_size: u64) -> virtual_fs::Result<()> { -+ if !self.writable { -+ return Err(FsError::PermissionDenied); -+ } -+ self.backend -+ .request(OP_TRUNCATE, "", &[], self.descriptor.into(), new_size, 0)?; -+ self.cursor = self.cursor.min(new_size); -+ Ok(()) -+ } -+ -+ fn unlink(&mut self) -> virtual_fs::Result<()> { -+ self.backend -+ .request(OP_UNLINK, "", &[], self.descriptor.into(), 0, 0)?; -+ Ok(()) -+ } -+ -+ fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { -+ Poll::Ready(Ok(self.backend.capacity())) -+ } -+ -+ fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { -+ Poll::Ready(Ok(self.backend.capacity())) -+ } -+} -+ -+impl AsyncRead for SyncBridgeFile { -+ fn poll_read( -+ mut self: Pin<&mut Self>, -+ _cx: &mut Context<'_>, -+ buffer: &mut ReadBuf<'_>, -+ ) -> Poll> { -+ if !self.readable { -+ return Poll::Ready(Err(FsError::PermissionDenied.into())); -+ } -+ let requested = buffer.remaining().min(self.backend.capacity()); -+ if requested == 0 { -+ return Poll::Ready(Ok(())); -+ } -+ let read = { -+ let output = buffer.initialize_unfilled_to(requested); -+ match self.backend.read(self.descriptor, self.cursor, output) { -+ Ok(read) => read, -+ Err(error) => return Poll::Ready(Err(error.into())), -+ } -+ }; -+ if read > requested { -+ return Poll::Ready(Err(FsError::InvalidData.into())); -+ } -+ buffer.advance(read); -+ self.cursor = self.cursor.saturating_add(read as u64); -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncWrite for SyncBridgeFile { -+ fn poll_write( -+ mut self: Pin<&mut Self>, -+ _cx: &mut Context<'_>, -+ buffer: &[u8], -+ ) -> Poll> { -+ if !self.writable { -+ return Poll::Ready(Err(FsError::PermissionDenied.into())); -+ } -+ let length = buffer.len().min(self.backend.capacity()); -+ if length == 0 { -+ return Poll::Ready(Ok(0)); -+ } -+ let response = match self.backend.request( -+ OP_WRITE, -+ "", -+ &buffer[..length], -+ self.descriptor.into(), -+ self.cursor, -+ 0, -+ ) { -+ Ok(response) => response, -+ Err(error) => return Poll::Ready(Err(error.into())), -+ }; -+ let written = match usize::try_from(response.value0) { -+ Ok(written) if written <= length => written, -+ _ => return Poll::Ready(Err(FsError::InvalidData.into())), -+ }; -+ self.cursor = response.value1; -+ Poll::Ready(Ok(written)) -+ } -+ -+ fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { -+ let result = self -+ .backend -+ .request(OP_FLUSH, "", &[], self.descriptor.into(), 0, 0) -+ .map(|_| ()) -+ .map_err(Into::into); -+ Poll::Ready(result) -+ } -+ -+ fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncSeek for SyncBridgeFile { -+ fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { -+ let next = match position { -+ SeekFrom::Start(offset) => offset, -+ SeekFrom::Current(delta) => checked_offset(self.cursor, delta)?, -+ SeekFrom::End(delta) => checked_offset(self.size(), delta)?, -+ }; -+ self.cursor = next; -+ Ok(()) -+ } -+ -+ fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { -+ Poll::Ready(Ok(self.cursor)) -+ } -+} -+ -+fn checked_offset(base: u64, delta: i64) -> io::Result { -+ if delta >= 0 { -+ base.checked_add(delta as u64) -+ } else { -+ base.checked_sub(delta.unsigned_abs()) -+ } -+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) -+} -+ -+fn normalize_path(path: &Path) -> virtual_fs::Result { -+ let mut parts = Vec::new(); -+ for component in path.components() { -+ match component { -+ std::path::Component::Prefix(_) | std::path::Component::RootDir => {} -+ std::path::Component::CurDir => {} -+ std::path::Component::ParentDir => { -+ parts.pop().ok_or(FsError::InvalidInput)?; -+ } -+ std::path::Component::Normal(value) => { -+ let value = value.to_str().ok_or(FsError::InvalidInput)?; -+ if value.is_empty() || value.contains('\0') { -+ return Err(FsError::InvalidInput); -+ } -+ parts.push(value); -+ } -+ } -+ } -+ Ok(parts.join("/")) -+} -+ -+fn metadata(kind: u64, size: u64) -> virtual_fs::Result { -+ let ft = match kind { -+ TYPE_FILE => FileType::new_file(), -+ TYPE_DIRECTORY => FileType::new_dir(), -+ _ => return Err(FsError::InvalidData), -+ }; -+ Ok(Metadata { -+ ft, -+ len: size, -+ ..Default::default() -+ }) -+} -+ -+fn decode_directory_entries(parent: &str, bytes: &[u8]) -> virtual_fs::Result> { -+ let mut cursor = 0; -+ let count = read_u32(bytes, &mut cursor)? as usize; -+ let mut entries = Vec::with_capacity(count); -+ for _ in 0..count { -+ let kind = u64::from(read_u8(bytes, &mut cursor)?); -+ let length = read_u32(bytes, &mut cursor)? as usize; -+ let end = cursor.checked_add(length).ok_or(FsError::InvalidData)?; -+ let name = std::str::from_utf8(bytes.get(cursor..end).ok_or(FsError::InvalidData)?) -+ .map_err(|_| FsError::InvalidData)?; -+ if name.is_empty() || name.contains('/') || name.contains('\0') { -+ return Err(FsError::InvalidData); -+ } -+ cursor = end; -+ let path = if parent.is_empty() { -+ PathBuf::from("/").join(name) -+ } else { -+ PathBuf::from("/").join(parent).join(name) -+ }; -+ entries.push(DirEntry { -+ path, -+ metadata: metadata(kind, 0), -+ }); -+ } -+ if cursor != bytes.len() { -+ return Err(FsError::InvalidData); -+ } -+ Ok(entries) -+} -+ -+fn read_u8(bytes: &[u8], cursor: &mut usize) -> virtual_fs::Result { -+ let value = *bytes.get(*cursor).ok_or(FsError::InvalidData)?; -+ *cursor += 1; -+ Ok(value) -+} -+ -+fn read_u32(bytes: &[u8], cursor: &mut usize) -> virtual_fs::Result { -+ let end = cursor.checked_add(4).ok_or(FsError::InvalidData)?; -+ let raw: [u8; 4] = bytes -+ .get(*cursor..end) -+ .ok_or(FsError::InvalidData)? -+ .try_into() -+ .map_err(|_| FsError::InvalidData)?; -+ *cursor = end; -+ Ok(u32::from_le_bytes(raw)) -+} -+ -+fn decode_result(result: i32) -> virtual_fs::Result<()> { -+ let error = match result { -+ RESULT_OK => return Ok(()), -+ RESULT_NOT_FOUND => FsError::EntryNotFound, -+ RESULT_EXISTS => FsError::AlreadyExists, -+ RESULT_NOT_DIR => FsError::BaseNotDirectory, -+ RESULT_NOT_FILE => FsError::NotAFile, -+ RESULT_DIR_NOT_EMPTY => FsError::DirectoryNotEmpty, -+ RESULT_PERMISSION => FsError::PermissionDenied, -+ RESULT_INVALID => FsError::InvalidInput, -+ RESULT_STORAGE_FULL => FsError::StorageFull, -+ RESULT_UNSUPPORTED => FsError::Unsupported, -+ RESULT_TIMEOUT => FsError::TimedOut, -+ _ => FsError::IOError, -+ }; -+ Err(error) -+} -+ -+fn js_number(value: &JsValue) -> virtual_fs::Result { -+ value -+ .as_f64() -+ .filter(|value| value.is_finite() && *value >= 0.0 && value.fract() == 0.0) -+ .ok_or(FsError::InvalidData) -+} -+ -+fn js_i32(value: &JsValue) -> virtual_fs::Result { -+ let value = value.as_f64().ok_or(FsError::InvalidData)?; -+ if !value.is_finite() || value.fract() != 0.0 { -+ return Err(FsError::InvalidData); -+ } -+ i32::try_from(value as i64).map_err(|_| FsError::InvalidData) -+} -+ -+fn js_usize(value: &JsValue) -> virtual_fs::Result { -+ let value = js_number(value)?; -+ if value > usize::MAX as f64 { -+ return Err(FsError::InvalidData); -+ } -+ Ok(value as usize) -+} -+ -+fn js_u64(value: &JsValue) -> virtual_fs::Result { -+ let value = js_number(value)?; -+ if value > 9_007_199_254_740_991.0 { -+ return Err(FsError::InvalidData); -+ } -+ Ok(value as u64) -+} diff --git a/src/wasix/browser-host/patches/0016-wasmer-wasix-expose-direct-memory.patch b/src/wasix/browser-host/patches/0016-wasmer-wasix-expose-direct-memory.patch deleted file mode 100644 index 0e161d8e0..000000000 --- a/src/wasix/browser-host/patches/0016-wasmer-wasix-expose-direct-memory.patch +++ /dev/null @@ -1,36 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: expose the direct instance memory handle - -Expose the instance memory needed by the direct JS protocol adapter without -changing clock policy or guest execution. This small downstream accessor is -paired with the direct PostgreSQL host; upstream should prefer an appropriately -scoped general memory accessor. - -diff --git a/src/lib.rs b/src/lib.rs -index 1064c373..4333c83c 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -279,6 +279,22 @@ pub(crate) fn install_oliphaunt_direct_clock( - Ok(()) - } - -+/// Return the JavaScript WebAssembly memory backing a WASIX environment. -+/// -+/// Oliphaunt's direct pgwire adapter uses this to transfer protocol bytes -+/// without routing them through an unrelated clock-import optimization. -+#[cfg(all(feature = "js", target_arch = "wasm32"))] -+pub fn oliphaunt_direct_memory( -+ env: &WasiFunctionEnv, -+ store: &impl wasmer::AsStoreRef, -+) -> Option { -+ use wasmer::js::AsJs; -+ -+ env.data(store) -+ .try_memory() -+ .map(|memory| memory.as_jsvalue(store)) -+} -+ - pub use crate::{ - fs::{default_fs_backing, Fd, WasiFs, WasiInodes, VIRTUAL_ROOT_FD}, - os::{ diff --git a/src/wasix/browser-host/patches/0017-wasmer-js-direct-pgwire-memory-bridge.patch b/src/wasix/browser-host/patches/0017-wasmer-js-direct-pgwire-memory-bridge.patch deleted file mode 100644 index 8e3abc79b..000000000 --- a/src/wasix/browser-host/patches/0017-wasmer-js-direct-pgwire-memory-bridge.patch +++ /dev/null @@ -1,344 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: copy direct protocol bytes through guest memory - -Use the direct guest input/output exports with JavaScript typed arrays to -avoid redundant Rust staging. Returned protocol bytes remain owned copies; -no borrowed view may escape across guest reentry or memory growth. The copy -boundary is part of the downstream ABI and must remain independently safe. - -diff --git a/src/postgres_direct.rs b/src/postgres_direct.rs -index c45000a..67a600f 100644 ---- a/src/postgres_direct.rs -+++ b/src/postgres_direct.rs -@@ -2,9 +2,11 @@ use std::sync::Arc; - - use anyhow::{ensure, Context}; - use js_sys::{Uint8Array, WebAssembly}; --use wasm_bindgen::prelude::wasm_bindgen; -+use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; - use wasmer::{Instance as WasmerInstance, Store, TypedFunction, Value, WasmTypeList}; --use wasmer_wasix::{Runtime as _, WasiEnvBuilder, WasiError, WasiFunctionEnv}; -+use wasmer_wasix::{ -+ oliphaunt_direct_memory, Runtime as _, WasiEnvBuilder, WasiError, WasiFunctionEnv, -+}; - - use crate::{runtime::Runtime, tasks::CallerRealmTaskManager, utils::Error, RunOptions}; - -@@ -29,6 +31,32 @@ impl MainLoopOutcome { - } - } - -+#[wasm_bindgen(inline_js = r#" -+export function oliphauntCopyToGuest(memory, pointer, input) { -+ new Uint8Array(memory.buffer, pointer, input.byteLength).set(input); -+} -+ -+export function oliphauntCopyFromGuest(memory, pointer, length) { -+ // slice() deliberately returns owned protocol bytes. A view would become -+ // invalid or mutable as soon as PostgreSQL reuses or grows guest memory. -+ return new Uint8Array(memory.buffer, pointer, length).slice(); -+} -+ -+"#)] -+extern "C" { -+ #[wasm_bindgen(catch, js_name = oliphauntCopyToGuest)] -+ fn oliphaunt_copy_to_guest( -+ memory: &JsValue, -+ pointer: u32, -+ input: &Uint8Array, -+ ) -> Result<(), JsValue>; -+ #[wasm_bindgen(catch, js_name = oliphauntCopyFromGuest)] -+ fn oliphaunt_copy_from_guest( -+ memory: &JsValue, -+ pointer: u32, -+ length: u32, -+ ) -> Result; -+} - /// Instantiate the integrated Oliphaunt/PostgreSQL guest in this JS realm. - /// - /// The returned driver is synchronous by design: every guest export runs on -@@ -48,15 +76,15 @@ pub struct OliphauntDirectInstance { - _runtime: Arc, - store: Store, - _instance: WasmerInstance, -- env: WasiFunctionEnv, -- malloc: TypedFunction, -- free: TypedFunction, -+ _env: WasiFunctionEnv, -+ guest_memory: JsValue, - input_reset: TypedFunction<(), i32>, -- input_write: TypedFunction<(i32, i32), i32>, -+ input_reserve: TypedFunction, -+ input_commit: TypedFunction, - input_available: TypedFunction<(), i32>, - output_reset: TypedFunction<(), i32>, - output_len: TypedFunction<(), i32>, -- output_read: TypedFunction<(i32, i32), i32>, -+ output_data: TypedFunction<(), i32>, - set_active: TypedFunction, - wasi_start: TypedFunction<(), ()>, - start_oliphaunt: TypedFunction<(), ()>, -@@ -78,8 +106,8 @@ impl OliphauntDirectInstance { - /// Start PostgreSQL and process one frontend startup packet. - #[wasm_bindgen(js_name = startup)] - pub fn startup(&mut self, packet: Uint8Array) -> Result { -- match self.startup_inner(&packet.to_vec()) { -- Ok(output) => Ok(Uint8Array::from(output.as_slice())), -+ match self.startup_inner(&packet) { -+ Ok(output) => Ok(output), - Err(error) => Err(self.startup_error(error)), - } - } -@@ -87,9 +115,7 @@ impl OliphauntDirectInstance { - /// Execute raw PostgreSQL frontend-protocol bytes synchronously. - #[wasm_bindgen(js_name = execProtocolRaw)] - pub fn exec_protocol_raw(&mut self, input: Uint8Array) -> Result { -- self.exec_protocol_raw_inner(&input.to_vec()) -- .map(|output| Uint8Array::from(output.as_slice())) -- .map_err(Error::from) -+ self.exec_protocol_raw_inner(&input).map_err(Error::from) - } - - /// Shut down the embedded lifecycle. This does not consume the JS object. -@@ -99,7 +125,7 @@ impl OliphauntDirectInstance { - } - - impl OliphauntDirectInstance { -- fn startup_inner(&mut self, packet: &[u8]) -> anyhow::Result> { -+ fn startup_inner(&mut self, packet: &Uint8Array) -> anyhow::Result { - self.ensure_open()?; - ensure!( - !self.protocol_started, -@@ -107,7 +133,7 @@ impl OliphauntDirectInstance { - ); - self.start_backend()?; - self.reset_io()?; -- self.push_input(packet)?; -+ self.push_input_js(packet)?; - - let port = self - .get_port -@@ -128,22 +154,23 @@ impl OliphauntDirectInstance { - .context("oliphaunt_wasix_pq_flush after startup")?; - self.protocol_started = true; - } -- self.take_output() -+ self.take_output_js() - } - -- fn exec_protocol_raw_inner(&mut self, payload: &[u8]) -> anyhow::Result> { -+ fn exec_protocol_raw_inner(&mut self, payload: &Uint8Array) -> anyhow::Result { - self.ensure_open()?; - ensure!( - self.protocol_started, - "Oliphaunt direct startup has not completed" - ); -- if payload.is_empty() { -- return Ok(Vec::new()); -+ if payload.length() == 0 { -+ return Ok(Uint8Array::new_with_length(0)); - } - - self.reset_io()?; -- self.push_input(&payload)?; -- let max_attempts = (payload.len() / 5).saturating_add(2).max(1); -+ self.push_input_js(payload)?; -+ let payload_len = payload.length() as usize; -+ let max_attempts = (payload_len / 5).saturating_add(2).max(1); - let mut attempts = 0usize; - while self.protocol_input_remaining()? > 0 { - attempts += 1; -@@ -162,7 +189,7 @@ impl OliphauntDirectInstance { - } - self.send_ready_after_main_loop()?; - self.flush_after_main_loop()?; -- self.take_output() -+ self.take_output_js() - } - - fn close_inner(&mut self) -> anyhow::Result<()> { -@@ -204,6 +231,8 @@ impl OliphauntDirectInstance { - .instantiate_async(module, &mut store) - .await - .context("instantiate Oliphaunt direct WASIX module")?; -+ let guest_memory = oliphaunt_direct_memory(&env, &store) -+ .context("get WASIX guest memory for direct PGWire")?; - seed_exported_c_string( - &mut store, - &instance, -@@ -212,16 +241,14 @@ impl OliphauntDirectInstance { - DEFAULT_PROGRAM_NAME, - )?; - -- let malloc = typed_export(&mut store, &instance, "malloc")?; -- let free = typed_export(&mut store, &instance, "pg_free") -- .or_else(|_| typed_export(&mut store, &instance, "free"))?; - let input_reset = typed_export(&mut store, &instance, "oliphaunt_wasix_input_reset")?; -- let input_write = typed_export(&mut store, &instance, "oliphaunt_wasix_input_write")?; -+ let input_reserve = typed_export(&mut store, &instance, "oliphaunt_wasix_input_reserve")?; -+ let input_commit = typed_export(&mut store, &instance, "oliphaunt_wasix_input_commit")?; - let input_available = - typed_export(&mut store, &instance, "oliphaunt_wasix_input_available")?; - let output_reset = typed_export(&mut store, &instance, "oliphaunt_wasix_output_reset")?; - let output_len = typed_export(&mut store, &instance, "oliphaunt_wasix_output_len")?; -- let output_read = typed_export(&mut store, &instance, "oliphaunt_wasix_output_read")?; -+ let output_data = typed_export(&mut store, &instance, "oliphaunt_wasix_output_data")?; - let set_active = typed_export(&mut store, &instance, "oliphaunt_wasix_set_active")?; - let wasi_start = typed_export(&mut store, &instance, "_start")?; - let start_oliphaunt = typed_export(&mut store, &instance, "oliphaunt_wasix_start")?; -@@ -244,15 +271,15 @@ impl OliphauntDirectInstance { - _runtime: runtime, - store, - _instance: instance, -- env, -- malloc, -- free, -+ _env: env, -+ guest_memory, - input_reset, -- input_write, -+ input_reserve, -+ input_commit, - input_available, - output_reset, - output_len, -- output_read, -+ output_data, - set_active, - wasi_start, - start_oliphaunt, -@@ -375,68 +402,42 @@ impl OliphauntDirectInstance { - Ok(()) - } - -- fn push_input(&mut self, bytes: &[u8]) -> anyhow::Result<()> { -- if bytes.is_empty() { -+ fn push_input_js(&mut self, bytes: &Uint8Array) -> anyhow::Result<()> { -+ let len = i32::try_from(bytes.length()).context("direct protocol input exceeds i32")?; -+ if len == 0 { - return Ok(()); - } -- let ptr = self.allocate(bytes.len() as i32)?; -- let result = (|| { -- let view = self -- .env -- .data(&self.store) -- .try_memory_view(&self.store) -- .context("get WASIX memory view")?; -- view.write(ptr as u64, bytes) -- .context("write direct protocol input into guest")?; -- let written = self -- .input_write -- .call(&mut self.store, ptr, bytes.len() as i32) -- .context("oliphaunt_wasix_input_write")?; -- ensure!( -- written == bytes.len() as i32, -- "short direct protocol input write" -- ); -- Ok(()) -- })(); -- self.free -- .call(&mut self.store, ptr) -- .context("free direct protocol input")?; -- result -+ let ptr = self -+ .input_reserve -+ .call(&mut self.store, len) -+ .context("oliphaunt_wasix_input_reserve")?; -+ ensure!(ptr > 0, "oliphaunt_wasix_input_reserve returned null"); -+ oliphaunt_copy_to_guest(&self.guest_memory, ptr as u32, bytes) -+ .map_err(|error| anyhow::anyhow!("copy direct protocol input into guest: {error:?}"))?; -+ let committed = self -+ .input_commit -+ .call(&mut self.store, len) -+ .context("oliphaunt_wasix_input_commit")?; -+ ensure!(committed == len, "short direct protocol input commit"); -+ Ok(()) - } - -- fn take_output(&mut self) -> anyhow::Result> { -+ fn take_output_js(&mut self) -> anyhow::Result { - let len = self - .output_len - .call(&mut self.store) - .context("oliphaunt_wasix_output_len")?; - ensure!(len >= 0, "negative direct protocol output length"); - if len == 0 { -- return Ok(Vec::new()); -+ return Ok(Uint8Array::new_with_length(0)); - } -- let ptr = self.allocate(len)?; -- let result = (|| { -- let read = self -- .output_read -- .call(&mut self.store, ptr, len) -- .context("oliphaunt_wasix_output_read")?; -- ensure!( -- read >= 0 && read <= len, -- "invalid direct protocol output read" -- ); -- let mut bytes = vec![0; read as usize]; -- let view = self -- .env -- .data(&self.store) -- .try_memory_view(&self.store) -- .context("get WASIX memory view")?; -- view.read(ptr as u64, &mut bytes) -- .context("read direct protocol output from guest")?; -- Ok(bytes) -- })(); -- self.free -- .call(&mut self.store, ptr) -- .context("free direct protocol output")?; -- let bytes = result?; -+ let ptr = self -+ .output_data -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_output_data")?; -+ ensure!(ptr > 0, "oliphaunt_wasix_output_data returned null"); -+ let output = oliphaunt_copy_from_guest(&self.guest_memory, ptr as u32, len as u32) -+ .map_err(|error| anyhow::anyhow!("copy direct protocol output from guest: {error:?}")); - ensure!( - self.output_reset - .call(&mut self.store) -@@ -444,16 +445,7 @@ impl OliphauntDirectInstance { - == 0, - "oliphaunt_wasix_output_reset after read failed" - ); -- Ok(bytes) -- } -- -- fn allocate(&mut self, len: i32) -> anyhow::Result { -- let ptr = self -- .malloc -- .call(&mut self.store, len) -- .context("malloc direct guest buffer")?; -- ensure!(ptr > 0, "malloc returned null for direct guest buffer"); -- Ok(ptr) -+ output - } - - fn protocol_input_remaining(&mut self) -> anyhow::Result { -@@ -472,8 +464,10 @@ impl OliphauntDirectInstance { - - fn startup_error(&mut self, error: anyhow::Error) -> Error { - let _ = self.pq_flush.call(&mut self.store); -- let protocol = self.take_output().unwrap_or_default(); -- if protocol.is_empty() { -+ let protocol = self -+ .take_output_js() -+ .unwrap_or_else(|_| Uint8Array::new_with_length(0)); -+ if protocol.length() == 0 { - return Error::from(error); - } - -@@ -481,7 +475,7 @@ impl OliphauntDirectInstance { - let _ = js_sys::Reflect::set( - &js_error, - &wasm_bindgen::JsValue::from_str("protocolResponse"), -- &Uint8Array::from(protocol.as_slice()), -+ &protocol, - ); - let _ = js_sys::Reflect::set( - &js_error, diff --git a/src/wasix/browser-host/patches/0018-wasmer-js-bound-direct-stderr.patch b/src/wasix/browser-host/patches/0018-wasmer-js-bound-direct-stderr.patch deleted file mode 100644 index de2a4a9e3..000000000 --- a/src/wasix/browser-host/patches/0018-wasmer-js-bound-direct-stderr.patch +++ /dev/null @@ -1,257 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: bound direct guest stderr diagnostics - -Retain a bounded tail of guest stderr and attach it to direct execution errors. -This preserves useful startup/trap diagnostics without an unbounded output -buffer or asynchronous stream. Truncation here affects diagnostic stderr only, -not PostgreSQL protocol or captured frontend-tool output. - -diff --git a/src/options.rs b/src/options.rs -index d56c81d..d2f8d00 100644 ---- a/src/options.rs -+++ b/src/options.rs -@@ -189,12 +189,13 @@ impl RunOptions { - pub(crate) fn configure_direct_builder( - &self, - builder: &mut WasiEnvBuilder, -+ stderr: Box, - ) -> Result<(), Error> { - self.configure_common_builder(builder)?; - - builder.set_stdin(Box::::default()); - builder.set_stdout(Box::::default()); -- builder.set_stderr(Box::::default()); -+ builder.set_stderr(stderr); - - Ok(()) - } -diff --git a/src/postgres_direct.rs b/src/postgres_direct.rs -index 67a600f..2e090d3 100644 ---- a/src/postgres_direct.rs -+++ b/src/postgres_direct.rs -@@ -1,7 +1,15 @@ --use std::sync::Arc; -+use std::{ -+ collections::VecDeque, -+ io::{self, SeekFrom}, -+ pin::Pin, -+ sync::{Arc, Mutex}, -+ task::{Context as TaskContext, Poll}, -+}; - - use anyhow::{ensure, Context}; - use js_sys::{Uint8Array, WebAssembly}; -+use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; -+use virtual_fs::VirtualFile; - use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; - use wasmer::{Instance as WasmerInstance, Store, TypedFunction, Value, WasmTypeList}; - use wasmer_wasix::{ -@@ -12,6 +20,133 @@ use crate::{runtime::Runtime, tasks::CallerRealmTaskManager, utils::Error, RunOp - - const DEFAULT_PROGRAM_NAME: &str = "/bin/postgres"; - const OLIPHAUNT_EXIT_ALIVE: i32 = 99; -+const STDERR_LIMIT_BYTES: usize = 16 * 1024; -+ -+#[derive(Clone, Debug, Default)] -+struct BoundedStderr { -+ bytes: Arc>>, -+} -+ -+impl BoundedStderr { -+ fn append(&self, input: &[u8]) { -+ let mut bytes = self.bytes.lock().expect("bounded stderr lock poisoned"); -+ if input.len() >= STDERR_LIMIT_BYTES { -+ bytes.clear(); -+ bytes.extend(&input[input.len() - STDERR_LIMIT_BYTES..]); -+ return; -+ } -+ let overflow = bytes -+ .len() -+ .saturating_add(input.len()) -+ .saturating_sub(STDERR_LIMIT_BYTES); -+ bytes.drain(..overflow); -+ bytes.extend(input); -+ } -+ -+ fn attach(&self, error: anyhow::Error) -> anyhow::Error { -+ let mut bytes = self.bytes.lock().expect("bounded stderr lock poisoned"); -+ if bytes.is_empty() { -+ return error; -+ } -+ let stderr = String::from_utf8_lossy(bytes.make_contiguous()); -+ error.context(format!("WASIX stderr (last 16 KiB):\n{stderr}")) -+ } -+} -+ -+impl AsyncWrite for BoundedStderr { -+ fn poll_write( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ input: &[u8], -+ ) -> Poll> { -+ self.append(input); -+ Poll::Ready(Ok(input.len())) -+ } -+ -+ fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+ -+ fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncRead for BoundedStderr { -+ fn poll_read( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ _buffer: &mut ReadBuf<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncSeek for BoundedStderr { -+ fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { -+ Ok(()) -+ } -+ -+ fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+} -+ -+impl VirtualFile for BoundedStderr { -+ fn last_accessed(&self) -> u64 { -+ 0 -+ } -+ -+ fn last_modified(&self) -> u64 { -+ 0 -+ } -+ -+ fn created_time(&self) -> u64 { -+ 0 -+ } -+ -+ fn size(&self) -> u64 { -+ self.bytes -+ .lock() -+ .expect("bounded stderr lock poisoned") -+ .len() as u64 -+ } -+ -+ fn set_len(&mut self, new_size: u64) -> virtual_fs::Result<()> { -+ let target = usize::try_from(new_size) -+ .unwrap_or(usize::MAX) -+ .min(STDERR_LIMIT_BYTES); -+ let mut bytes = self.bytes.lock().expect("bounded stderr lock poisoned"); -+ while bytes.len() > target { -+ bytes.pop_front(); -+ } -+ bytes.resize(target, 0); -+ Ok(()) -+ } -+ -+ fn unlink(&mut self) -> virtual_fs::Result<()> { -+ self.bytes -+ .lock() -+ .expect("bounded stderr lock poisoned") -+ .clear(); -+ Ok(()) -+ } -+ -+ fn get_special_fd(&self) -> Option { -+ Some(2) -+ } -+ -+ fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+ -+ fn poll_write_ready( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(8192)) -+ } -+} - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum MainLoopOutcome { -@@ -67,7 +202,10 @@ pub async fn instantiate_oliphaunt_direct( - module_bytes: Uint8Array, - options: RunOptions, - ) -> Result { -- OliphauntDirectInstance::instantiate(module, module_bytes, options).await -+ let stderr = BoundedStderr::default(); -+ OliphauntDirectInstance::instantiate(module, module_bytes, options, stderr.clone()) -+ .await -+ .map_err(|error| Error::from(stderr.attach(error.into_anyhow()))) - } - - #[wasm_bindgen] -@@ -77,6 +215,7 @@ pub struct OliphauntDirectInstance { - store: Store, - _instance: WasmerInstance, - _env: WasiFunctionEnv, -+ stderr: BoundedStderr, - guest_memory: JsValue, - input_reset: TypedFunction<(), i32>, - input_reserve: TypedFunction, -@@ -115,12 +254,18 @@ impl OliphauntDirectInstance { - /// Execute raw PostgreSQL frontend-protocol bytes synchronously. - #[wasm_bindgen(js_name = execProtocolRaw)] - pub fn exec_protocol_raw(&mut self, input: Uint8Array) -> Result { -- self.exec_protocol_raw_inner(&input).map_err(Error::from) -+ match self.exec_protocol_raw_inner(&input) { -+ Ok(output) => Ok(output), -+ Err(error) => Err(Error::from(self.stderr.attach(error))), -+ } - } - - /// Shut down the embedded lifecycle. This does not consume the JS object. - pub fn close(&mut self) -> Result<(), Error> { -- self.close_inner().map_err(Error::from) -+ match self.close_inner() { -+ Ok(()) => Ok(()), -+ Err(error) => Err(Error::from(self.stderr.attach(error))), -+ } - } - } - -@@ -213,6 +358,7 @@ impl OliphauntDirectInstance { - module: WebAssembly::Module, - module_bytes: Uint8Array, - options: RunOptions, -+ stderr: BoundedStderr, - ) -> Result { - // Direct execution intentionally has no configurable networking or - // worker-backed runtime. RunOptions is reused for args/env/fs mounts. -@@ -223,7 +369,7 @@ impl OliphauntDirectInstance { - .as_string() - .unwrap_or_else(|| DEFAULT_PROGRAM_NAME.to_owned()); - let mut builder = WasiEnvBuilder::new(program_name).runtime(runtime.clone()); -- options.configure_direct_builder(&mut builder)?; -+ options.configure_direct_builder(&mut builder, Box::new(stderr.clone()))?; - - let module = wasmer::Module::from((module, module_bytes.to_vec())); - let mut store = Store::new(runtime.engine()); -@@ -272,6 +418,7 @@ impl OliphauntDirectInstance { - store, - _instance: instance, - _env: env, -+ stderr, - guest_memory, - input_reset, - input_reserve, -@@ -463,6 +610,7 @@ impl OliphauntDirectInstance { - } - - fn startup_error(&mut self, error: anyhow::Error) -> Error { -+ let error = self.stderr.attach(error); - let _ = self.pq_flush.call(&mut self.store); - let protocol = self - .take_output_js() diff --git a/src/wasix/browser-host/patches/0019-wasmer-parse-exception-reference-types.patch b/src/wasix/browser-host/patches/0019-wasmer-parse-exception-reference-types.patch deleted file mode 100644 index 85ab6a135..000000000 --- a/src/wasix/browser-host/patches/0019-wasmer-parse-exception-reference-types.patch +++ /dev/null @@ -1,22 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer: recognize exception reference types in JS metadata - -Map nullable and non-null exception reference types to the engine exception -reference representation. PostgreSQL's Wasm exception-handling build otherwise -fails module metadata conversion before execution. This is a narrow upstream -engine compatibility fix, not a relaxation of module validation. - -diff --git a/src/utils/polyfill.rs b/src/utils/polyfill.rs -index 8b54de4..6660bb3 100644 ---- a/src/utils/polyfill.rs -+++ b/src/utils/polyfill.rs -@@ -372,6 +372,8 @@ pub fn wpreftype_to_type(ty: wasmparser::RefType) -> WasmResult { - Ok(Type::ExternRef) - } else if ty.is_func_ref() { - Ok(Type::FuncRef) -+ } else if ty == wasmparser::RefType::EXNREF || ty == wasmparser::RefType::NULLEXNREF { -+ Ok(Type::ExceptionRef) - } else { - Err(format!("Unsupported ref type: {:?}", ty)) - } diff --git a/src/wasix/browser-host/patches/0020-wasmer-wasix-preserve-posix-close-durability.patch b/src/wasix/browser-host/patches/0020-wasmer-wasix-preserve-posix-close-durability.patch deleted file mode 100644 index cd53f8ef3..000000000 --- a/src/wasix/browser-host/patches/0020-wasmer-wasix-preserve-posix-close-durability.patch +++ /dev/null @@ -1,46 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: distinguish close policy from explicit durability - -Keep the historical flush-before-close default, but let an explicit host -policy select POSIX close semantics when the filesystem writes synchronously. -Do not turn every descriptor close into a durability barrier; PostgreSQL's -explicit sync operations remain authoritative. This policy must never be -selected merely from a guest-controlled environment variable. - -diff --git a/src/syscalls/wasi/fd_close.rs b/src/syscalls/wasi/fd_close.rs -index 41d27119..e97c6eeb 100644 ---- a/src/syscalls/wasi/fd_close.rs -+++ b/src/syscalls/wasi/fd_close.rs -@@ -1,4 +1,5 @@ - use super::*; -+use crate::capabilities::WasiFdClosePolicy; - use crate::syscalls::*; - - /// ### `fd_close()` -@@ -28,13 +29,18 @@ pub fn fd_close(mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Result {} -- Err(e) => { -- return Ok(e); -+ if env.host_policy.fd_close() == WasiFdClosePolicy::FlushBeforeClose { -+ // HACK: upstream uses tokio files to back WASI file handles. Since -+ // tokio does writes in the background, it may miss writes if the file -+ // is closed without flushing first. Hence, flush those hosts here. -+ // Oliphaunt's memory and direct OPFS files complete writes -+ // synchronously; flushing them on close would incorrectly turn POSIX -+ // close into fsync and bypass PostgreSQL's WAL/checkpoint policy. -+ match __asyncify_light(env, None, state.fs.flush(fd))? { -+ Ok(_) | Err(Errno::Isdir) | Err(Errno::Io) | Err(Errno::Access) => {} -+ Err(e) => { -+ return Ok(e); -+ } - } - } - wasi_try_ok!(state.fs.close_fd(fd)); diff --git a/src/wasix/browser-host/patches/0021-wasmer-js-stream-direct-pgwire.patch b/src/wasix/browser-host/patches/0021-wasmer-js-stream-direct-pgwire.patch deleted file mode 100644 index 4f34ecf85..000000000 --- a/src/wasix/browser-host/patches/0021-wasmer-js-stream-direct-pgwire.patch +++ /dev/null @@ -1,1311 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: stream direct protocol and capture tool output - -Add bounded protocol callbacks, direct tool execution and complete in-memory -stdout/stderr capture. The transport follows the shared guest contract and -preserves owned callback bytes; capture failure fails instead of returning a -partial successful result. These are coupled downstream protocol/tool adapters, -not a claim that the bundled patch is ready for upstream as one change. - -diff --git a/src/lib.rs b/src/lib.rs -index 6803f8e..d8bee81 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -18,6 +18,8 @@ mod run; - mod runtime; - mod streams; - mod tasks; -+mod protocol_contract; -+mod tool_direct; - mod utils; - mod wasmer; - mod ws; -@@ -33,6 +35,9 @@ pub use crate::{ - postgres_direct::{instantiate_oliphaunt_direct, OliphauntDirectInstance}, - registry::RegistryConfig, - run::run_wasix, -+ tool_direct::{ -+ prepare_oliphaunt_tool, run_oliphaunt_tool_direct, OliphauntPreparedTool, -+ }, - utils::StringOrBytes, - wasmer::Wasmer, - }; -diff --git a/src/options.rs b/src/options.rs -index d2f8d00..4283c82 100644 ---- a/src/options.rs -+++ b/src/options.rs -@@ -8,10 +8,17 @@ use anyhow::Context; - use js_sys::Array; - use virtual_fs::{random_file::RandomFile, TmpFileSystem}; - use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue, UnwrapThrowExt}; --use wasmer_wasix::WasiEnvBuilder; -+use wasmer_wasix::{ -+ capabilities::{ -+ WasiClockTimeGetMode, WasiFdClosePolicy, WasiGuestExecutionMode, WasiHostPolicy, -+ }, -+ WasiEnvBuilder, -+}; - - use crate::{runtime::Runtime, utils::Error, Directory, DirectoryInit, JsRuntime, StringOrBytes}; - -+const OLIPHAUNT_PROTOCOL_DEVICE: &str = "/dev/oliphaunt-pgwire"; -+ - #[wasm_bindgen] - extern "C" { - /// A proxy for `Option<&Runtime>`. -@@ -189,12 +196,19 @@ impl RunOptions { - pub(crate) fn configure_direct_builder( - &self, - builder: &mut WasiEnvBuilder, -+ stdin: Box, -+ stdout: Box, - stderr: Box, - ) -> Result<(), Error> { -- self.configure_common_builder(builder)?; -- -- builder.set_stdin(Box::::default()); -- builder.set_stdout(Box::::default()); -+ self.configure_common_builder(builder, None)?; -+ builder.set_host_policy(WasiHostPolicy::new( -+ WasiGuestExecutionMode::SingleProgram, -+ WasiClockTimeGetMode::DirectJs, -+ WasiFdClosePolicy::WritesCompleteSynchronously, -+ )); -+ -+ builder.set_stdin(stdin); -+ builder.set_stdout(stdout); - builder.set_stderr(stderr); - - Ok(()) -@@ -213,33 +227,64 @@ impl RunOptions { - ), - Error, - > { -- self.configure_common_builder(builder)?; -+ self.configure_common_builder(builder, None)?; -+ Ok(self.configure_stdio(builder)) -+ } -+ -+ pub(crate) fn configure_tool_direct_builder( -+ &self, -+ builder: &mut WasiEnvBuilder, -+ protocol: Box, -+ stdout: Box, -+ stderr: Box, -+ ) -> Result<(), Error> { -+ self.configure_common_builder(builder, Some(protocol))?; -+ builder.set_host_policy(WasiHostPolicy::new( -+ WasiGuestExecutionMode::SingleProgram, -+ WasiClockTimeGetMode::Canonical, -+ WasiFdClosePolicy::WritesCompleteSynchronously, -+ )); -+ builder.add_env("OLIPHAUNT_DIRECT_PGWIRE", OLIPHAUNT_PROTOCOL_DEVICE); -+ match self.read_stdin() { -+ Some(input) => builder.set_stdin(Box::new(virtual_fs::StaticFile::new(input))), -+ None => builder.set_stdin(Box::::default()), -+ }; -+ builder.set_stdout(stdout); -+ builder.set_stderr(stderr); - -+ Ok(()) -+ } -+ -+ fn configure_stdio( -+ &self, -+ builder: &mut WasiEnvBuilder, -+ ) -> ( -+ Option, -+ web_sys::ReadableStream, -+ web_sys::ReadableStream, -+ ) { - let stdin = match self.read_stdin() { - Some(stdin) => { -- let f = virtual_fs::StaticFile::new(stdin); -- builder.set_stdin(Box::new(f)); -+ builder.set_stdin(Box::new(virtual_fs::StaticFile::new(stdin))); - None - } - None => { -- let (f, stdin) = crate::streams::input_pipe(); -- builder.set_stdin(Box::new(f)); -- Some(stdin) -+ let (file, stream) = crate::streams::input_pipe(); -+ builder.set_stdin(Box::new(file)); -+ Some(stream) - } - }; -- - let (stdout_file, stdout) = crate::streams::output_pipe(); - builder.set_stdout(Box::new(stdout_file)); -- - let (stderr_file, stderr) = crate::streams::output_pipe(); - builder.set_stderr(Box::new(stderr_file)); -- -- Ok((stdin, stdout, stderr)) -+ (stdin, stdout, stderr) - } - - fn configure_common_builder( - &self, - builder: &mut WasiEnvBuilder, -+ protocol_file: Option>, - ) -> Result<(), Error> { - for arg in self.parse_args()? { - builder.add_arg(arg); -@@ -253,6 +298,11 @@ impl RunOptions { - builder.set_current_dir(cwd); - } - let fs = self.filesystem()?; -+ if let Some(protocol_file) = protocol_file { -+ fs.new_open_options_ext() -+ .insert_device_file(PathBuf::from(OLIPHAUNT_PROTOCOL_DEVICE), protocol_file) -+ .with_context(|| format!("Unable to create {OLIPHAUNT_PROTOCOL_DEVICE}"))?; -+ } - builder.set_fs(Box::new(fs)); - builder.add_preopen_dir("/")?; - -diff --git a/src/postgres_direct.rs b/src/postgres_direct.rs -index 2e090d3..3c38b13 100644 ---- a/src/postgres_direct.rs -+++ b/src/postgres_direct.rs -@@ -1,4 +1,5 @@ - use std::{ -+ cell::RefCell, - collections::VecDeque, - io::{self, SeekFrom}, - pin::Pin, -@@ -7,10 +8,10 @@ use std::{ - }; - - use anyhow::{ensure, Context}; --use js_sys::{Uint8Array, WebAssembly}; -+use js_sys::{Function, Uint8Array, WebAssembly}; - use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; - use virtual_fs::VirtualFile; --use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; -+use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; - use wasmer::{Instance as WasmerInstance, Store, TypedFunction, Value, WasmTypeList}; - use wasmer_wasix::{ - oliphaunt_direct_memory, Runtime as _, WasiEnvBuilder, WasiError, WasiFunctionEnv, -@@ -21,6 +22,275 @@ use crate::{runtime::Runtime, tasks::CallerRealmTaskManager, utils::Error, RunOp - const DEFAULT_PROGRAM_NAME: &str = "/bin/postgres"; - const OLIPHAUNT_EXIT_ALIVE: i32 = 99; - const STDERR_LIMIT_BYTES: usize = 16 * 1024; -+use crate::protocol_contract::{PROTOCOL_CALLBACK_CHUNK_BYTES as PROTOCOL_CHUNK_BYTES, -+ PROTOCOL_BUFFERED, PROTOCOL_HYBRID}; -+ -+thread_local! { -+ static PROTOCOL_CALLBACK: RefCell> = const { RefCell::new(None) }; -+ static PROTOCOL_INPUT_CALLBACK: RefCell> = const { RefCell::new(None) }; -+} -+ -+#[derive(Clone, Debug, Default)] -+struct ProtocolStdin; -+ -+impl ProtocolStdin { -+ fn begin(&self, callback: Function) -> anyhow::Result<()> { -+ PROTOCOL_INPUT_CALLBACK.with(|active| { -+ let mut active = active.borrow_mut(); -+ ensure!( -+ active.is_none(), -+ "protocol input callback is already active" -+ ); -+ *active = Some(callback); -+ Ok(()) -+ }) -+ } -+ -+ fn finish(&self) { -+ PROTOCOL_INPUT_CALLBACK.with(|active| *active.borrow_mut() = None); -+ } -+} -+ -+impl AsyncRead for ProtocolStdin { -+ fn poll_read( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ buffer: &mut ReadBuf<'_>, -+ ) -> Poll> { -+ let callback = match PROTOCOL_INPUT_CALLBACK.with(|active| active.borrow().clone()) { -+ Some(callback) => callback, -+ None => { -+ return Poll::Ready(Err(io::Error::new( -+ io::ErrorKind::BrokenPipe, -+ "protocol input callback is inactive", -+ ))) -+ } -+ }; -+ let requested = buffer.remaining().min(PROTOCOL_CHUNK_BYTES); -+ if requested == 0 { -+ return Poll::Ready(Ok(())); -+ } -+ let value = match callback.call1(&JsValue::UNDEFINED, &JsValue::from(requested as u32)) { -+ Ok(value) => value, -+ Err(error) => { -+ return Poll::Ready(Err(io::Error::other(format!( -+ "protocol input callback failed: {error:?}" -+ )))) -+ } -+ }; -+ let input = match value.dyn_into::() { -+ Ok(input) => input, -+ Err(_) => { -+ return Poll::Ready(Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ "protocol input callback must return a Uint8Array", -+ ))) -+ } -+ }; -+ let length = input.length() as usize; -+ if length > requested { -+ return Poll::Ready(Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ "protocol input callback exceeded the requested byte count", -+ ))); -+ } -+ if length > 0 { -+ input.copy_to(buffer.initialize_unfilled_to(length)); -+ buffer.advance(length); -+ } -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncWrite for ProtocolStdin { -+ fn poll_write( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ _input: &[u8], -+ ) -> Poll> { -+ Poll::Ready(Err(io::Error::new( -+ io::ErrorKind::Unsupported, -+ "protocol stdin is read-only", -+ ))) -+ } -+ -+ fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+ -+ fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncSeek for ProtocolStdin { -+ fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { -+ Ok(()) -+ } -+ -+ fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+} -+ -+impl VirtualFile for ProtocolStdin { -+ fn last_accessed(&self) -> u64 { -+ 0 -+ } -+ fn last_modified(&self) -> u64 { -+ 0 -+ } -+ fn created_time(&self) -> u64 { -+ 0 -+ } -+ fn size(&self) -> u64 { -+ 0 -+ } -+ fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { -+ Ok(()) -+ } -+ fn unlink(&mut self) -> virtual_fs::Result<()> { -+ Ok(()) -+ } -+ fn get_special_fd(&self) -> Option { -+ Some(0) -+ } -+ fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) -+ } -+ fn poll_write_ready( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+} -+ -+#[derive(Clone, Debug, Default)] -+struct ProtocolStdout { -+ failure: Arc>>, -+} -+ -+impl ProtocolStdout { -+ fn begin(&self, callback: Function) -> anyhow::Result<()> { -+ PROTOCOL_CALLBACK.with(|active| { -+ let mut active = active.borrow_mut(); -+ ensure!( -+ active.is_none(), -+ "protocol stream callback is already active" -+ ); -+ *active = Some(callback); -+ Ok(()) -+ })?; -+ *self.failure.lock().expect("protocol stdout lock poisoned") = None; -+ Ok(()) -+ } -+ -+ fn finish(&self) -> anyhow::Result<()> { -+ PROTOCOL_CALLBACK.with(|active| *active.borrow_mut() = None); -+ match self -+ .failure -+ .lock() -+ .expect("protocol stdout lock poisoned") -+ .take() -+ { -+ Some(failure) => anyhow::bail!("protocol stream callback failed: {failure}"), -+ None => Ok(()), -+ } -+ } -+ -+ fn deliver(&self, input: &[u8]) -> io::Result<()> { -+ let callback = PROTOCOL_CALLBACK -+ .with(|active| active.borrow().clone()) -+ .ok_or_else(|| { -+ io::Error::new(io::ErrorKind::BrokenPipe, "protocol callback is inactive") -+ })?; -+ for input in input.chunks(PROTOCOL_CHUNK_BYTES) { -+ let chunk = Uint8Array::from(input); -+ if let Err(error) = callback.call1(&JsValue::UNDEFINED, &chunk) { -+ let failure = format!("{error:?}"); -+ *self.failure.lock().expect("protocol stdout lock poisoned") = -+ Some(failure.clone()); -+ return Err(io::Error::other(failure)); -+ } -+ } -+ Ok(()) -+ } -+} -+ -+impl AsyncWrite for ProtocolStdout { -+ fn poll_write( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ input: &[u8], -+ ) -> Poll> { -+ match self.deliver(input) { -+ Ok(()) => Poll::Ready(Ok(input.len())), -+ Err(error) => Poll::Ready(Err(error)), -+ } -+ } -+ -+ fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+ -+ fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncRead for ProtocolStdout { -+ fn poll_read( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ _buffer: &mut ReadBuf<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncSeek for ProtocolStdout { -+ fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { -+ Ok(()) -+ } -+ -+ fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+} -+ -+impl VirtualFile for ProtocolStdout { -+ fn last_accessed(&self) -> u64 { -+ 0 -+ } -+ fn last_modified(&self) -> u64 { -+ 0 -+ } -+ fn created_time(&self) -> u64 { -+ 0 -+ } -+ fn size(&self) -> u64 { -+ 0 -+ } -+ fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { -+ Ok(()) -+ } -+ fn unlink(&mut self) -> virtual_fs::Result<()> { -+ Ok(()) -+ } -+ fn get_special_fd(&self) -> Option { -+ Some(1) -+ } -+ fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+ fn poll_write_ready( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) -+ } -+} - - #[derive(Clone, Debug, Default)] - struct BoundedStderr { -@@ -203,9 +473,18 @@ pub async fn instantiate_oliphaunt_direct( - options: RunOptions, - ) -> Result { - let stderr = BoundedStderr::default(); -- OliphauntDirectInstance::instantiate(module, module_bytes, options, stderr.clone()) -- .await -- .map_err(|error| Error::from(stderr.attach(error.into_anyhow()))) -+ let protocol_stdin = ProtocolStdin; -+ let protocol_stdout = ProtocolStdout::default(); -+ OliphauntDirectInstance::instantiate( -+ module, -+ module_bytes, -+ options, -+ protocol_stdin, -+ protocol_stdout, -+ stderr.clone(), -+ ) -+ .await -+ .map_err(|error| Error::from(stderr.attach(error.into_anyhow()))) - } - - #[wasm_bindgen] -@@ -215,6 +494,8 @@ pub struct OliphauntDirectInstance { - store: Store, - _instance: WasmerInstance, - _env: WasiFunctionEnv, -+ protocol_stdin: ProtocolStdin, -+ protocol_stdout: ProtocolStdout, - stderr: BoundedStderr, - guest_memory: JsValue, - input_reset: TypedFunction<(), i32>, -@@ -224,6 +505,7 @@ pub struct OliphauntDirectInstance { - output_reset: TypedFunction<(), i32>, - output_len: TypedFunction<(), i32>, - output_data: TypedFunction<(), i32>, -+ set_protocol_transport: TypedFunction, - set_active: TypedFunction, - wasi_start: TypedFunction<(), ()>, - start_oliphaunt: TypedFunction<(), ()>, -@@ -254,12 +536,48 @@ impl OliphauntDirectInstance { - /// Execute raw PostgreSQL frontend-protocol bytes synchronously. - #[wasm_bindgen(js_name = execProtocolRaw)] - pub fn exec_protocol_raw(&mut self, input: Uint8Array) -> Result { -- match self.exec_protocol_raw_inner(&input) { -+ if let Err(error) = self.ensure_open() { -+ return Err(Error::from(self.stderr.attach(error))); -+ } -+ if let Err(error) = self -+ .set_protocol_transport -+ .call(&mut self.store, PROTOCOL_BUFFERED) -+ { -+ return Err(Error::from(self.stderr.attach(error.into()))); -+ } -+ match self.exec_protocol_raw_inner(&input, false) { - Ok(output) => Ok(output), - Err(error) => Err(Error::from(self.stderr.attach(error))), - } - } - -+ /// Execute raw PostgreSQL protocol bytes and synchronously deliver owned chunks. -+ #[wasm_bindgen(js_name = execProtocolStream)] -+ pub fn exec_protocol_stream( -+ &mut self, -+ input: Uint8Array, -+ on_chunk: Function, -+ ) -> Result<(), Error> { -+ match self.exec_protocol_stream_inner(&input, on_chunk) { -+ Ok(()) => Ok(()), -+ Err(error) => Err(Error::from(self.stderr.attach(error))), -+ } -+ } -+ -+ /// Execute a protocol exchange with synchronous full-duplex callbacks. -+ #[wasm_bindgen(js_name = execProtocolDuplex)] -+ pub fn exec_protocol_duplex( -+ &mut self, -+ input: Uint8Array, -+ on_read: Function, -+ on_chunk: Function, -+ ) -> Result<(), Error> { -+ match self.exec_protocol_duplex_inner(&input, on_read, on_chunk) { -+ Ok(()) => Ok(()), -+ Err(error) => Err(Error::from(self.stderr.attach(error))), -+ } -+ } -+ - /// Shut down the embedded lifecycle. This does not consume the JS object. - pub fn close(&mut self) -> Result<(), Error> { - match self.close_inner() { -@@ -270,6 +588,64 @@ impl OliphauntDirectInstance { - } - - impl OliphauntDirectInstance { -+ fn exec_protocol_duplex_inner( -+ &mut self, -+ payload: &Uint8Array, -+ on_read: Function, -+ on_chunk: Function, -+ ) -> anyhow::Result<()> { -+ self.ensure_open()?; -+ self.protocol_stdin.begin(on_read)?; -+ let result = self.exec_protocol_stream_inner(payload, on_chunk); -+ self.protocol_stdin.finish(); -+ result -+ } -+ -+ fn exec_protocol_stream_inner( -+ &mut self, -+ payload: &Uint8Array, -+ on_chunk: Function, -+ ) -> anyhow::Result<()> { -+ self.ensure_open()?; -+ self.protocol_stdout.begin(on_chunk.clone())?; -+ let previous = match self -+ .set_protocol_transport -+ .call(&mut self.store, PROTOCOL_HYBRID) -+ .context("enable hybrid protocol transport") -+ { -+ Ok(previous) => previous, -+ Err(error) => { -+ let _ = self.protocol_stdout.finish(); -+ return Err(error); -+ } -+ }; -+ let execution = self.exec_protocol_raw_inner(payload, true); -+ let restore = if self.closed { -+ // A terminal main-loop outcome poisons guest state. Never invoke -+ // another guest export merely to restore the transport selector. -+ Ok(()) -+ } else { -+ self.set_protocol_transport -+ .call(&mut self.store, previous) -+ .context("restore protocol transport") -+ .map(|_| ()) -+ }; -+ let callback = self.protocol_stdout.finish(); -+ -+ let output = execution?; -+ restore?; -+ callback?; -+ let length = output.length() as usize; -+ for start in (0..length).step_by(PROTOCOL_CHUNK_BYTES) { -+ let end = start.saturating_add(PROTOCOL_CHUNK_BYTES).min(length); -+ let chunk = output.slice(start as u32, end as u32); -+ on_chunk -+ .call1(&JsValue::UNDEFINED, &chunk) -+ .map_err(|error| anyhow::anyhow!("protocol stream callback failed: {error:?}"))?; -+ } -+ Ok(()) -+ } -+ - fn startup_inner(&mut self, packet: &Uint8Array) -> anyhow::Result { - self.ensure_open()?; - ensure!( -@@ -302,7 +678,11 @@ impl OliphauntDirectInstance { - self.take_output_js() - } - -- fn exec_protocol_raw_inner(&mut self, payload: &Uint8Array) -> anyhow::Result { -+ fn exec_protocol_raw_inner( -+ &mut self, -+ payload: &Uint8Array, -+ streaming: bool, -+ ) -> anyhow::Result { - self.ensure_open()?; - ensure!( - self.protocol_started, -@@ -317,6 +697,7 @@ impl OliphauntDirectInstance { - let payload_len = payload.length() as usize; - let max_attempts = (payload_len / 5).saturating_add(2).max(1); - let mut attempts = 0usize; -+ let mut input_ended = false; - while self.protocol_input_remaining()? > 0 { - attempts += 1; - ensure!( -@@ -325,6 +706,10 @@ impl OliphauntDirectInstance { - ); - match self.call_main_loop()? { - MainLoopOutcome::Processed | MainLoopOutcome::Recovered => {} -+ MainLoopOutcome::InputEnded if streaming => { -+ input_ended = true; -+ break; -+ } - MainLoopOutcome::InputEnded => { - return Err(self.terminal_main_loop_outcome( - "PostgresMainLoopOnce reported input end while dispatching buffered protocol input", -@@ -332,8 +717,10 @@ impl OliphauntDirectInstance { - } - } - } -- self.send_ready_after_main_loop()?; -- self.flush_after_main_loop()?; -+ if !input_ended { -+ self.send_ready_after_main_loop()?; -+ self.flush_after_main_loop()?; -+ } - self.take_output_js() - } - -@@ -358,6 +745,8 @@ impl OliphauntDirectInstance { - module: WebAssembly::Module, - module_bytes: Uint8Array, - options: RunOptions, -+ protocol_stdin: ProtocolStdin, -+ protocol_stdout: ProtocolStdout, - stderr: BoundedStderr, - ) -> Result { - // Direct execution intentionally has no configurable networking or -@@ -369,7 +758,12 @@ impl OliphauntDirectInstance { - .as_string() - .unwrap_or_else(|| DEFAULT_PROGRAM_NAME.to_owned()); - let mut builder = WasiEnvBuilder::new(program_name).runtime(runtime.clone()); -- options.configure_direct_builder(&mut builder, Box::new(stderr.clone()))?; -+ options.configure_direct_builder( -+ &mut builder, -+ Box::new(protocol_stdin.clone()), -+ Box::new(protocol_stdout.clone()), -+ Box::new(stderr.clone()), -+ )?; - - let module = wasmer::Module::from((module, module_bytes.to_vec())); - let mut store = Store::new(runtime.engine()); -@@ -395,6 +789,11 @@ impl OliphauntDirectInstance { - let output_reset = typed_export(&mut store, &instance, "oliphaunt_wasix_output_reset")?; - let output_len = typed_export(&mut store, &instance, "oliphaunt_wasix_output_len")?; - let output_data = typed_export(&mut store, &instance, "oliphaunt_wasix_output_data")?; -+ let set_protocol_transport = typed_export( -+ &mut store, -+ &instance, -+ "oliphaunt_wasix_set_protocol_transport", -+ )?; - let set_active = typed_export(&mut store, &instance, "oliphaunt_wasix_set_active")?; - let wasi_start = typed_export(&mut store, &instance, "_start")?; - let start_oliphaunt = typed_export(&mut store, &instance, "oliphaunt_wasix_start")?; -@@ -418,6 +817,8 @@ impl OliphauntDirectInstance { - store, - _instance: instance, - _env: env, -+ protocol_stdin, -+ protocol_stdout, - stderr, - guest_memory, - input_reset, -@@ -427,6 +828,7 @@ impl OliphauntDirectInstance { - output_reset, - output_len, - output_data, -+ set_protocol_transport, - set_active, - wasi_start, - start_oliphaunt, -diff --git a/src/tool_direct.rs b/src/tool_direct.rs -new file mode 100644 -index 0000000..fc92239 ---- /dev/null -+++ b/src/tool_direct.rs -@@ -0,0 +1,588 @@ -+use std::{ -+ cell::RefCell, -+ error::Error as StdError, -+ fmt, -+ io::{self, SeekFrom}, -+ pin::Pin, -+ sync::{Arc, Mutex}, -+ task::{Context as TaskContext, Poll}, -+}; -+ -+use anyhow::{ensure, Context}; -+use js_sys::{Function, Uint8Array, WebAssembly}; -+use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; -+use virtual_fs::VirtualFile; -+use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; -+use wasmer::{Store, TypedFunction}; -+use wasmer_types::ModuleHash; -+use wasmer_wasix::{os::TtyBridge, Runtime as _, WasiEnvBuilder, WasiError}; -+ -+use crate::{ -+ runtime::Runtime, -+ tasks::CallerRealmTaskManager, -+ utils::{js_error, Error}, -+ RunOptions, -+}; -+ -+const DEFAULT_PROGRAM_NAME: &str = "wasm"; -+use crate::protocol_contract::PROTOCOL_CALLBACK_CHUNK_BYTES as PROTOCOL_CHUNK_BYTES; -+ -+#[wasm_bindgen] -+extern "C" { -+ #[wasm_bindgen(typescript_type = "OliphauntToolOutput")] -+ pub type JsToolOutput; -+} -+ -+#[wasm_bindgen(typescript_custom_section)] -+const TOOL_OUTPUT_TYPE_DEFINITION: &'static str = r#" -+export type OliphauntToolOutput = { -+ code: number; -+ stdoutBytes: Uint8Array; -+ stderrBytes: Uint8Array; -+} -+"#; -+ -+fn tool_output(code: i32, stdout: Vec, stderr: Vec) -> JsToolOutput { -+ let output = js_sys::Object::new(); -+ let _ = js_sys::Reflect::set(&output, &JsValue::from_str("code"), &JsValue::from(code)); -+ let _ = js_sys::Reflect::set( -+ &output, -+ &JsValue::from_str("stdoutBytes"), -+ &Uint8Array::from(stdout.as_slice()), -+ ); -+ let _ = js_sys::Reflect::set( -+ &output, -+ &JsValue::from_str("stderrBytes"), -+ &Uint8Array::from(stderr.as_slice()), -+ ); -+ output.unchecked_into() -+} -+ -+/// Immutable frontend-program state shared by repeated invocations in one -+/// JavaScript realm. Every invocation still creates fresh WASI process state. -+#[wasm_bindgen] -+pub struct OliphauntPreparedTool { -+ runtime: Arc, -+ module: wasmer::Module, -+ module_hash: ModuleHash, -+} -+ -+#[wasm_bindgen(js_name = prepareOliphauntTool)] -+pub fn prepare_oliphaunt_tool( -+ module: WebAssembly::Module, -+ module_bytes: Uint8Array, -+) -> Result { -+ let module_bytes = module_bytes.to_vec(); -+ let module_hash = ModuleHash::xxhash(&module_bytes); -+ let module = wasmer::Module::from((module, module_bytes)); -+ let runtime = -+ Arc::new(Runtime::with_defaults()?.with_task_manager(Arc::new(CallerRealmTaskManager))); -+ Ok(OliphauntPreparedTool { -+ runtime, -+ module, -+ module_hash, -+ }) -+} -+ -+#[derive(Clone)] -+struct ProtocolCallbacks { -+ read: Function, -+ write: Function, -+} -+ -+thread_local! { -+ static PROTOCOL_CALLBACKS: RefCell> = const { RefCell::new(None) }; -+} -+ -+/// Keeps the caller-realm callbacks active only while the guest is executing. -+struct ActiveProtocolCallbacks; -+ -+impl ActiveProtocolCallbacks { -+ fn begin(read: Function, write: Function) -> anyhow::Result { -+ PROTOCOL_CALLBACKS.with(|active| { -+ let mut active = active.borrow_mut(); -+ ensure!( -+ active.is_none(), -+ "an Oliphaunt tool protocol session is already active" -+ ); -+ *active = Some(ProtocolCallbacks { read, write }); -+ Ok(Self) -+ }) -+ } -+} -+ -+impl Drop for ActiveProtocolCallbacks { -+ fn drop(&mut self) { -+ PROTOCOL_CALLBACKS.with(|active| *active.borrow_mut() = None); -+ } -+} -+ -+#[derive(Clone, Debug, Default)] -+struct ToolProtocolFile; -+ -+impl ToolProtocolFile { -+ fn callbacks() -> io::Result { -+ PROTOCOL_CALLBACKS -+ .with(|active| active.borrow().clone()) -+ .ok_or_else(|| { -+ io::Error::new( -+ io::ErrorKind::BrokenPipe, -+ "Oliphaunt tool protocol callbacks are inactive", -+ ) -+ }) -+ } -+} -+ -+impl AsyncRead for ToolProtocolFile { -+ fn poll_read( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ buffer: &mut ReadBuf<'_>, -+ ) -> Poll> { -+ let requested = buffer.remaining().min(PROTOCOL_CHUNK_BYTES); -+ if requested == 0 { -+ return Poll::Ready(Ok(())); -+ } -+ -+ let callbacks = match Self::callbacks() { -+ Ok(callbacks) => callbacks, -+ Err(error) => return Poll::Ready(Err(error)), -+ }; -+ let value = match callbacks -+ .read -+ .call1(&JsValue::UNDEFINED, &JsValue::from(requested as u32)) -+ { -+ Ok(value) => value, -+ Err(error) => { -+ return Poll::Ready(Err(io::Error::other(format!( -+ "Oliphaunt tool protocol read failed: {}", -+ js_error(error) -+ )))) -+ } -+ }; -+ let input = match value.dyn_into::() { -+ Ok(input) => input, -+ Err(_) => { -+ return Poll::Ready(Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ "Oliphaunt tool protocol read must return a Uint8Array", -+ ))) -+ } -+ }; -+ let length = input.length() as usize; -+ if length > requested { -+ return Poll::Ready(Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ "Oliphaunt tool protocol read exceeded the requested byte count", -+ ))); -+ } -+ if length > 0 { -+ input.copy_to(buffer.initialize_unfilled_to(length)); -+ buffer.advance(length); -+ } -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncWrite for ToolProtocolFile { -+ fn poll_write( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ input: &[u8], -+ ) -> Poll> { -+ if input.is_empty() { -+ return Poll::Ready(Ok(0)); -+ } -+ -+ let callbacks = match Self::callbacks() { -+ Ok(callbacks) => callbacks, -+ Err(error) => return Poll::Ready(Err(error)), -+ }; -+ let length = input.len().min(PROTOCOL_CHUNK_BYTES); -+ // Copy the Rust-owned input into JavaScript-owned storage before the -+ // callback. It may retain or mutate this independent chunk without -+ // aliasing memory that Rust can reuse after this call returns. -+ let chunk = Uint8Array::from(&input[..length]); -+ match callbacks.write.call1(&JsValue::UNDEFINED, &chunk) { -+ Ok(_) => Poll::Ready(Ok(length)), -+ Err(error) => Poll::Ready(Err(io::Error::other(format!( -+ "Oliphaunt tool protocol write failed: {}", -+ js_error(error) -+ )))), -+ } -+ } -+ -+ fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+ -+ fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncSeek for ToolProtocolFile { -+ fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { -+ Ok(()) -+ } -+ -+ fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+} -+ -+impl VirtualFile for ToolProtocolFile { -+ fn last_accessed(&self) -> u64 { -+ 0 -+ } -+ -+ fn last_modified(&self) -> u64 { -+ 0 -+ } -+ -+ fn created_time(&self) -> u64 { -+ 0 -+ } -+ -+ fn size(&self) -> u64 { -+ 0 -+ } -+ -+ fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { -+ Ok(()) -+ } -+ -+ fn unlink(&mut self) -> virtual_fs::Result<()> { -+ Ok(()) -+ } -+ -+ fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) -+ } -+ -+ fn poll_write_ready( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) -+ } -+} -+ -+#[derive(Clone, Copy, Debug)] -+enum CaptureStream { -+ Stdout, -+ Stderr, -+} -+ -+impl fmt::Display for CaptureStream { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ formatter.write_str(match self { -+ Self::Stdout => "stdout", -+ Self::Stderr => "stderr", -+ }) -+ } -+} -+ -+#[derive(Clone, Copy, Debug)] -+enum CaptureFailure { -+ AllocationFailed(CaptureStream), -+ LockPoisoned, -+} -+ -+impl CaptureFailure { -+ fn io_error(self) -> io::Error { -+ let kind = match self { -+ Self::AllocationFailed(_) => io::ErrorKind::OutOfMemory, -+ Self::LockPoisoned => io::ErrorKind::Other, -+ }; -+ io::Error::new(kind, self) -+ } -+} -+ -+impl fmt::Display for CaptureFailure { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ match self { -+ Self::AllocationFailed(stream) => { -+ write!(formatter, "Oliphaunt WASIX tool {stream} capture allocation failed") -+ } -+ Self::LockPoisoned => { -+ formatter.write_str("Oliphaunt WASIX tool output capture lock was poisoned") -+ } -+ } -+ } -+} -+ -+impl StdError for CaptureFailure {} -+ -+#[derive(Debug, Default)] -+struct CaptureState { -+ stdout: Vec, -+ stderr: Vec, -+ failure: Option, -+} -+ -+impl CaptureState { -+ fn output(&self, stream: CaptureStream) -> &Vec { -+ match stream { -+ CaptureStream::Stdout => &self.stdout, -+ CaptureStream::Stderr => &self.stderr, -+ } -+ } -+ -+ fn output_mut(&mut self, stream: CaptureStream) -> &mut Vec { -+ match stream { -+ CaptureStream::Stdout => &mut self.stdout, -+ CaptureStream::Stderr => &mut self.stderr, -+ } -+ } -+ -+ fn discard_output(&mut self) { -+ self.stdout = Vec::new(); -+ self.stderr = Vec::new(); -+ } -+ -+ fn fail(&mut self, failure: CaptureFailure) -> io::Error { -+ let failure = *self.failure.get_or_insert(failure); -+ self.discard_output(); -+ failure.io_error() -+ } -+ -+ fn poison_lock(&mut self) -> CaptureFailure { -+ self.failure = Some(CaptureFailure::LockPoisoned); -+ self.discard_output(); -+ CaptureFailure::LockPoisoned -+ } -+ -+ fn append(&mut self, stream: CaptureStream, input: &[u8]) -> io::Result { -+ if let Some(failure) = self.failure { -+ return Err(failure.io_error()); -+ } -+ if self.output_mut(stream).try_reserve(input.len()).is_err() { -+ return Err(self.fail(CaptureFailure::AllocationFailed(stream))); -+ } -+ self.output_mut(stream).extend_from_slice(input); -+ Ok(input.len()) -+ } -+} -+ -+/// Complete in-memory stdout/stderr capture for a fresh tool process. -+/// The handle refuses to publish either stream after any capture failure. -+#[derive(Clone, Debug)] -+struct CaptureFile { -+ state: Arc>, -+ stream: CaptureStream, -+} -+ -+#[derive(Clone, Debug)] -+struct CaptureHandle { -+ state: Arc>, -+} -+ -+impl CaptureFile { -+ fn pair() -> (Self, Self, CaptureHandle) { -+ let state = Arc::new(Mutex::new(CaptureState::default())); -+ ( -+ Self { -+ state: state.clone(), -+ stream: CaptureStream::Stdout, -+ }, -+ Self { -+ state: state.clone(), -+ stream: CaptureStream::Stderr, -+ }, -+ CaptureHandle { state }, -+ ) -+ } -+ -+ fn with_state( -+ &self, -+ operation: impl FnOnce(&mut CaptureState) -> io::Result, -+ ) -> io::Result { -+ let mut state = match self.state.lock() { -+ Ok(state) => state, -+ Err(error) => { -+ let mut state = error.into_inner(); -+ return Err(state.poison_lock().io_error()); -+ } -+ }; -+ operation(&mut state) -+ } -+} -+ -+impl CaptureHandle { -+ fn failure(&self) -> Option { -+ match self.state.lock() { -+ Ok(state) => state.failure, -+ Err(error) => { -+ let mut state = error.into_inner(); -+ state.poison_lock(); -+ state.failure -+ } -+ } -+ } -+ -+ fn finish(&self) -> anyhow::Result<(Vec, Vec)> { -+ let mut state = match self.state.lock() { -+ Ok(state) => state, -+ Err(error) => { -+ let mut state = error.into_inner(); -+ let failure = state.poison_lock(); -+ return Err(anyhow::Error::new(failure)); -+ } -+ }; -+ if let Some(failure) = state.failure { -+ state.discard_output(); -+ return Err(anyhow::Error::new(failure)); -+ } -+ Ok(( -+ std::mem::take(&mut state.stdout), -+ std::mem::take(&mut state.stderr), -+ )) -+ } -+} -+ -+impl AsyncRead for CaptureFile { -+ fn poll_read( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ _buffer: &mut ReadBuf<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncWrite for CaptureFile { -+ fn poll_write( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ input: &[u8], -+ ) -> Poll> { -+ let stream = self.stream; -+ Poll::Ready(self.with_state(|state| state.append(stream, input))) -+ } -+ -+ fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+ -+ fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(())) -+ } -+} -+ -+impl AsyncSeek for CaptureFile { -+ fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { -+ Ok(()) -+ } -+ -+ fn poll_complete(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ let stream = self.stream; -+ Poll::Ready(self.with_state(|state| Ok(state.output(stream).len() as u64))) -+ } -+} -+ -+impl VirtualFile for CaptureFile { -+ fn last_accessed(&self) -> u64 { -+ 0 -+ } -+ -+ fn last_modified(&self) -> u64 { -+ 0 -+ } -+ -+ fn created_time(&self) -> u64 { -+ 0 -+ } -+ -+ fn size(&self) -> u64 { -+ let stream = self.stream; -+ match self.with_state(|state| Ok(state.output(stream).len() as u64)) { -+ Ok(size) => size, -+ Err(_) => 0, -+ } -+ } -+ -+ fn set_len(&mut self, _new_size: u64) -> virtual_fs::Result<()> { -+ Err(virtual_fs::FsError::PermissionDenied) -+ } -+ -+ fn unlink(&mut self) -> virtual_fs::Result<()> { -+ Ok(()) -+ } -+ -+ fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { -+ Poll::Ready(Ok(0)) -+ } -+ -+ fn poll_write_ready( -+ self: Pin<&mut Self>, -+ _cx: &mut TaskContext<'_>, -+ ) -> Poll> { -+ Poll::Ready(Ok(PROTOCOL_CHUNK_BYTES)) -+ } -+} -+ -+/// Run an Oliphaunt frontend tool synchronously in the caller's JavaScript -+/// realm, while routing its private PGWire device through blocking callbacks. -+/// The protocol write callback receives an owned JavaScript copy that it may retain. -+#[wasm_bindgen(js_name = runOliphauntToolDirect)] -+pub async fn run_oliphaunt_tool_direct( -+ prepared: &OliphauntPreparedTool, -+ options: RunOptions, -+ protocol_read: Function, -+ protocol_write: Function, -+) -> Result { -+ prepared.runtime.reset(); -+ let program_name = options -+ .program() -+ .as_string() -+ .unwrap_or_else(|| DEFAULT_PROGRAM_NAME.to_owned()); -+ let (stdout, stderr, capture) = CaptureFile::pair(); -+ let mut builder = WasiEnvBuilder::new(program_name).runtime(prepared.runtime.clone()); -+ options.configure_tool_direct_builder( -+ &mut builder, -+ Box::new(ToolProtocolFile), -+ Box::new(stdout.clone()), -+ Box::new(stderr.clone()), -+ )?; -+ -+ let mut store = Store::new(prepared.runtime.engine()); -+ let (instance, _env) = builder -+ .instantiate_ext_async(prepared.module.clone(), prepared.module_hash, &mut store) -+ .await -+ .context("instantiate Oliphaunt WASIX tool")?; -+ let start: TypedFunction<(), ()> = instance -+ .exports -+ .get_typed_function(&store, "_start") -+ .context("get Oliphaunt WASIX tool _start export")?; -+ -+ let code = { -+ let _callbacks = ActiveProtocolCallbacks::begin(protocol_read, protocol_write)?; -+ match start.call(&mut store) { -+ Ok(()) => 0, -+ Err(error) => match runtime_exit_code(&error) { -+ Some(code) => code, -+ None => { -+ if let Some(failure) = capture.failure() { -+ return Err(Error::from(anyhow::Error::new(failure))); -+ } -+ return Err(Error::from( -+ anyhow::Error::from(error).context("run Oliphaunt WASIX tool"), -+ )); -+ } -+ }, -+ } -+ }; -+ -+ let (stdout, stderr) = capture.finish().map_err(Error::from)?; -+ Ok(tool_output(code, stdout, stderr)) -+} -+ -+fn runtime_exit_code(error: &wasmer::RuntimeError) -> Option { -+ error -+ .downcast_ref::() -+ .and_then(|error| match error { -+ WasiError::Exit(code) => Some(code.raw()), -+ _ => None, -+ }) -+} diff --git a/src/wasix/browser-host/patches/0023-wasmer-js-prepare-trusted-embedded-session.patch b/src/wasix/browser-host/patches/0023-wasmer-js-prepare-trusted-embedded-session.patch deleted file mode 100644 index 6031d4168..000000000 --- a/src/wasix/browser-host/patches/0023-wasmer-js-prepare-trusted-embedded-session.patch +++ /dev/null @@ -1,57 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: prepare trusted embedded PostgreSQL sessions - -Require and invoke the guest's explicit trusted-embedded-session entrypoint -before client initialization. The embedded host has already selected the -session identity; replaying server authentication/bootstrap paths here can -undo that boundary. This requires the matching PostgreSQL guest patch and is -an Oliphaunt-specific trusted-host ABI, not a generic authentication bypass. - -diff --git a/src/postgres_direct.rs b/src/postgres_direct.rs -index 3c38b13..7b14a8f 100644 ---- a/src/postgres_direct.rs -+++ b/src/postgres_direct.rs -@@ -508,6 +508,7 @@ pub struct OliphauntDirectInstance { - output_data: TypedFunction<(), i32>, - set_protocol_transport: TypedFunction, - set_active: TypedFunction, -+ prepare_trusted_embedded_session: TypedFunction<(), i32>, - wasi_start: TypedFunction<(), ()>, - start_oliphaunt: TypedFunction<(), ()>, - run_atexit_funcs: Option>, -@@ -796,6 +797,11 @@ impl OliphauntDirectInstance { - "oliphaunt_wasix_set_protocol_transport", - )?; - let set_active = typed_export(&mut store, &instance, "oliphaunt_wasix_set_active")?; -+ let prepare_trusted_embedded_session = typed_export( -+ &mut store, -+ &instance, -+ "oliphaunt_wasix_prepare_trusted_embedded_session", -+ )?; - let wasi_start = typed_export(&mut store, &instance, "_start")?; - let start_oliphaunt = typed_export(&mut store, &instance, "oliphaunt_wasix_start")?; - let run_atexit_funcs = -@@ -831,6 +837,7 @@ impl OliphauntDirectInstance { - output_data, - set_protocol_transport, - set_active, -+ prepare_trusted_embedded_session, - wasi_start, - start_oliphaunt, - run_atexit_funcs, -@@ -922,6 +929,14 @@ impl OliphauntDirectInstance { - self.set_active - .call(&mut self.store, 1) - .context("oliphaunt_wasix_set_active(1)")?; -+ let prepare_status = self -+ .prepare_trusted_embedded_session -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_prepare_trusted_embedded_session")?; -+ ensure!( -+ prepare_status == 0, -+ "oliphaunt_wasix_prepare_trusted_embedded_session rejected after PostgreSQL startup began" -+ ); - match self.wasi_start.call(&mut self.store) { - Ok(()) => {} - Err(error) if runtime_exit_code(&error) == Some(OLIPHAUNT_EXIT_ALIVE) => {} diff --git a/src/wasix/browser-host/patches/0024-virtual-fs-propagate-random-errors.patch b/src/wasix/browser-host/patches/0024-virtual-fs-propagate-random-errors.patch deleted file mode 100644 index f05b8bee7..000000000 --- a/src/wasix/browser-host/patches/0024-virtual-fs-propagate-random-errors.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000024 Mon Sep 17 00:00:00 2001 -From: Sid Jain -Date: Wed, 26 Aug 2026 00:00:00 +0000 -Subject: [PATCH] virtual-fs: propagate random device errors - -RandomFile currently ignores getrandom failures and then reports a successful -read of the zero-initialized staging buffer. Predictable bytes must never be -reported as entropy. - -Return the provider error as an I/O error without advancing the destination. -The caller can then preserve its own fail-closed randomness contract. ---- - src/random_file.rs | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/src/random_file.rs b/src/random_file.rs -index 2667d16..ad880c3 100644 ---- a/src/random_file.rs -+++ b/src/random_file.rs -@@ -54,7 +54,9 @@ impl AsyncRead for RandomFile { - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - let mut data = vec![0u8; buf.remaining()]; -- getrandom::getrandom(&mut data).ok(); -+ if let Err(error) = getrandom::getrandom(&mut data) { -+ return Poll::Ready(Err(error.into())); -+ } - buf.put_slice(&data[..]); - Poll::Ready(Ok(())) - } --- -2.51.0 diff --git a/src/wasix/browser-host/patches/0025-wasmer-js-fail-closed-direct-guest-phases.patch b/src/wasix/browser-host/patches/0025-wasmer-js-fail-closed-direct-guest-phases.patch deleted file mode 100644 index e9ee5a51a..000000000 --- a/src/wasix/browser-host/patches/0025-wasmer-js-fail-closed-direct-guest-phases.patch +++ /dev/null @@ -1,1017 +0,0 @@ -From 0000000000000000000000000000000000000025 Mon Sep 17 00:00:00 2001 -From: Sid Jain -Date: Thu, 27 Aug 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: fail closed across direct guest phases - -Treat every error after entering a direct protocol guest phase as terminal. -Centralize the lifecycle transition so transport selection, input bridging, -the protocol pump, post-step exports, output extraction, and transport restore -all poison before returning. Cleanup errors are terminal too, while repeated -close remains idempotent and never re-enters a poisoned guest. - -Validate bridge return values and preserve ProcessStartupPacket rejection as a -cleanup-capable terminal-for-work state. Handle _start rejection atomically: -cache and validate the pending v1 outcome descriptor before _start, classify -only Exit98 as rejection, then copy a complete ErrorResponse-bearing protocol -stream directly from guest memory without invoking another guest export. -Malformed outcomes and every other _start failure poison generically. - -Keep buffered-input streaming (mode 3) distinct from full duplex (mode 2), -require an empty guest output buffer after streamed execution, and validate -every flush status before output extraction. Keep buffered-output copies within -the signed-i32 bridge range and make callback failure sticky so a guest retry -cannot call JavaScript again after the first callback exception. Treat a -poisoned callback state lock as an equally sticky terminal failure without -panicking. Preserve that causal callback failure ahead of the derived guest -flush failure so the public error retains the JavaScript callback diagnostic. - -Keep the causal error at the exported boundary; do not label unrelated failures -as an output-size problem. The signed bridge length is the only output ceiling. ---- - ---- a/src/postgres_direct.rs -+++ b/src/postgres_direct.rs -@@ -20,10 +20,18 @@ - use crate::{runtime::Runtime, tasks::CallerRealmTaskManager, utils::Error, RunOptions}; - - const DEFAULT_PROGRAM_NAME: &str = "/bin/postgres"; -+const OLIPHAUNT_EXIT_STARTUP_REJECTED: i32 = 98; - const OLIPHAUNT_EXIT_ALIVE: i32 = 99; - const STDERR_LIMIT_BYTES: usize = 16 * 1024; - use crate::protocol_contract::{PROTOCOL_CALLBACK_CHUNK_BYTES as PROTOCOL_CHUNK_BYTES, - PROTOCOL_BUFFERED, PROTOCOL_HYBRID}; -+use crate::protocol_contract::PROTOCOL_BUFFERED_INPUT_STREAMED_OUTPUT; -+const PROCESS_STARTUP_OK: i32 = 0; -+const PROCESS_STARTUP_ERROR: i32 = -1; -+const STARTUP_OUTCOME_VERSION: u32 = 1; -+const STARTUP_OUTCOME_DESCRIPTOR_BYTES: u32 = 32; -+const STARTUP_OUTCOME_PENDING: u32 = 0; -+const STARTUP_OUTCOME_REJECTED: u32 = 1; - - thread_local! { - static PROTOCOL_CALLBACK: RefCell> = const { RefCell::new(None) }; -@@ -167,40 +175,105 @@ - } - } - -+#[derive(Clone, Debug)] -+enum ProtocolStdoutFailure { -+ Callback(String), -+ LockPoisoned, -+} -+ -+impl ProtocolStdoutFailure { -+ fn message(&self) -> String { -+ match self { -+ Self::Callback(failure) => format!("protocol stream callback failed: {failure}"), -+ Self::LockPoisoned => "protocol stream state lock was poisoned".to_string(), -+ } -+ } -+ -+ fn into_anyhow(self) -> anyhow::Error { -+ anyhow::anyhow!(self.message()) -+ } -+ -+ fn into_io(self) -> io::Error { -+ io::Error::other(self.message()) -+ } -+} -+ -+#[derive(Debug, Default)] -+struct ProtocolStdoutState { -+ failure: Option, -+} -+ -+impl ProtocolStdoutState { -+ fn poison_lock(&mut self) -> ProtocolStdoutFailure { -+ self.failure = Some(ProtocolStdoutFailure::LockPoisoned); -+ ProtocolStdoutFailure::LockPoisoned -+ } -+ -+ fn record_callback_failure(&mut self, failure: String) -> ProtocolStdoutFailure { -+ match &self.failure { -+ Some(failure) => failure.clone(), -+ None => { -+ let failure = ProtocolStdoutFailure::Callback(failure); -+ self.failure = Some(failure.clone()); -+ failure -+ } -+ } -+ } -+} -+ - #[derive(Clone, Debug, Default)] - struct ProtocolStdout { -- failure: Arc>>, -+ state: Arc>, - } - - impl ProtocolStdout { - fn begin(&self, callback: Function) -> anyhow::Result<()> { - PROTOCOL_CALLBACK.with(|active| { -- let mut active = active.borrow_mut(); - ensure!( -- active.is_none(), -+ active.borrow().is_none(), - "protocol stream callback is already active" - ); -- *active = Some(callback); - Ok(()) - })?; -- *self.failure.lock().expect("protocol stdout lock poisoned") = None; -+ self.with_state(|state| state.failure = None) -+ .map_err(ProtocolStdoutFailure::into_anyhow)?; -+ PROTOCOL_CALLBACK.with(|active| *active.borrow_mut() = Some(callback)); - Ok(()) - } - - fn finish(&self) -> anyhow::Result<()> { - PROTOCOL_CALLBACK.with(|active| *active.borrow_mut() = None); -- match self -- .failure -- .lock() -- .expect("protocol stdout lock poisoned") -- .take() -- { -- Some(failure) => anyhow::bail!("protocol stream callback failed: {failure}"), -+ let failure = match self.with_state(|state| state.failure.clone()) { -+ Ok(failure) => failure, -+ Err(failure) => Some(failure), -+ }; -+ match failure { -+ Some(failure) => Err(failure.into_anyhow()), - None => Ok(()), - } - } - -+ fn with_state( -+ &self, -+ operation: impl FnOnce(&mut ProtocolStdoutState) -> T, -+ ) -> Result { -+ match self.state.lock() { -+ Ok(mut state) => Ok(operation(&mut state)), -+ Err(poisoned) => { -+ let mut state = poisoned.into_inner(); -+ Err(state.poison_lock()) -+ } -+ } -+ } -+ - fn deliver(&self, input: &[u8]) -> io::Result<()> { -+ let failure = match self.with_state(|state| state.failure.clone()) { -+ Ok(failure) => failure, -+ Err(failure) => Some(failure), -+ }; -+ if let Some(failure) = failure { -+ return Err(failure.into_io()); -+ } - let callback = PROTOCOL_CALLBACK - .with(|active| active.borrow().clone()) - .ok_or_else(|| { -@@ -209,10 +282,12 @@ - for input in input.chunks(PROTOCOL_CHUNK_BYTES) { - let chunk = Uint8Array::from(input); - if let Err(error) = callback.call1(&JsValue::UNDEFINED, &chunk) { -- let failure = format!("{error:?}"); -- *self.failure.lock().expect("protocol stdout lock poisoned") = -- Some(failure.clone()); -- return Err(io::Error::other(failure)); -+ let failure = match self -+ .with_state(|state| state.record_callback_failure(format!("{error:?}"))) -+ { -+ Ok(failure) | Err(failure) => failure, -+ }; -+ return Err(failure.into_io()); - } - } - Ok(()) -@@ -426,6 +501,34 @@ - InputEnded, - } - -+#[derive(Debug, Clone, Copy, PartialEq, Eq)] -+enum DirectInstanceState { -+ Open, -+ StartupPhase, -+ GuestPhase, -+ StartupRejected, -+ Closing, -+ Poisoned, -+ Closed, -+} -+ -+enum BackendStartOutcome { -+ Alive, -+ Rejected(Vec), -+} -+ -+enum DirectStartupOutcome { -+ Processed { output: Uint8Array, status: i32 }, -+ Rejected(Vec), -+} -+ -+#[derive(Debug, Clone, Copy, PartialEq, Eq)] -+struct StartupOutcomeDescriptor { -+ kind: u32, -+ data_pointer: u64, -+ data_length: u64, -+} -+ - impl MainLoopOutcome { - fn from_i32(value: i32) -> Option { - match value { -@@ -509,19 +612,20 @@ - set_protocol_transport: TypedFunction, - set_active: TypedFunction, - prepare_trusted_embedded_session: TypedFunction<(), i32>, -+ startup_outcome: TypedFunction<(), i32>, - wasi_start: TypedFunction<(), ()>, - start_oliphaunt: TypedFunction<(), ()>, - run_atexit_funcs: Option>, - get_port: TypedFunction<(), i32>, - process_startup: TypedFunction<(i32, i32, i32), i32>, - send_conn_data: TypedFunction<(), ()>, -- pq_flush: TypedFunction<(), ()>, -+ pq_flush: TypedFunction<(), i32>, - pq_buffer_remaining_data: TypedFunction<(), i32>, - main_loop: TypedFunction<(), i32>, - send_ready: TypedFunction<(), ()>, - backend_started: bool, - protocol_started: bool, -- closed: bool, -+ state: DirectInstanceState, - } - - #[wasm_bindgen] -@@ -530,26 +634,39 @@ - #[wasm_bindgen(js_name = startup)] - pub fn startup(&mut self, packet: Uint8Array) -> Result { - match self.startup_inner(&packet) { -- Ok(output) => Ok(output), -- Err(error) => Err(self.startup_error(error)), -+ Ok(DirectStartupOutcome::Processed { output, .. }) => Ok(output), -+ Ok(DirectStartupOutcome::Rejected(protocol)) => Err(self.startup_rejection_error( -+ anyhow::anyhow!("_start rejected Oliphaunt single-user backend"), -+ protocol, -+ )), -+ Err(error) if self.state == DirectInstanceState::StartupPhase => { -+ self.poison_guest_phase(); -+ Err(Error::from(self.stderr.attach(error))) -+ } -+ Err(error) => Err(Error::from(self.stderr.attach(error))), - } - } - - /// Execute raw PostgreSQL frontend-protocol bytes synchronously. - #[wasm_bindgen(js_name = execProtocolRaw)] - pub fn exec_protocol_raw(&mut self, input: Uint8Array) -> Result { -- if let Err(error) = self.ensure_open() { -+ if let Err(error) = self.ensure_protocol_started() { - return Err(Error::from(self.stderr.attach(error))); - } -- if let Err(error) = self -- .set_protocol_transport -- .call(&mut self.store, PROTOCOL_BUFFERED) -- { -- return Err(Error::from(self.stderr.attach(error.into()))); -- } -- match self.exec_protocol_raw_inner(&input, false) { -+ let result = self.run_guest_phase("buffered protocol exchange", |direct| { -+ let previous = direct -+ .set_protocol_transport -+ .call(&mut direct.store, PROTOCOL_BUFFERED) -+ .context("enable buffered protocol transport")?; -+ ensure!( -+ previous == PROTOCOL_BUFFERED, -+ "enable buffered protocol transport returned previous mode {previous}, expected {PROTOCOL_BUFFERED}" -+ ); -+ direct.exec_protocol_raw_inner(&input, false) -+ }); -+ match result { - Ok(output) => Ok(output), - Err(error) => Err(Error::from(self.stderr.attach(error))), - } - } - -@@ -560,7 +677,13 @@ - input: Uint8Array, - on_chunk: Function, -- ) -> Result<(), Error> { -+ ) -> Result { -- match self.exec_protocol_stream_inner(&input, on_chunk) { -+ if let Err(error) = self.ensure_protocol_started() { -+ return Err(Error::from(self.stderr.attach(error))); -+ } -+ let result = self.run_guest_phase("streaming protocol exchange", |direct| { -+ direct.exec_protocol_stream_inner(&input, on_chunk) -+ }); -+ match result { -- Ok(()) => Ok(()), -+ Ok(()) => Ok(0), - Err(error) => Err(Error::from(self.stderr.attach(error))), - } -@@ -574,7 +697,13 @@ - on_read: Function, - on_chunk: Function, - ) -> Result<(), Error> { -- match self.exec_protocol_duplex_inner(&input, on_read, on_chunk) { -+ if let Err(error) = self.ensure_protocol_started() { -+ return Err(Error::from(self.stderr.attach(error))); -+ } -+ let result = self.run_guest_phase("duplex protocol exchange", |direct| { -+ direct.exec_protocol_duplex_inner(&input, on_read, on_chunk) -+ }); -+ match result { - Ok(()) => Ok(()), - Err(error) => Err(Error::from(self.stderr.attach(error))), - } -@@ -596,11 +725,20 @@ - on_read: Function, - on_chunk: Function, - ) -> anyhow::Result<()> { -- self.ensure_open()?; - self.protocol_stdin.begin(on_read)?; -- let result = self.exec_protocol_stream_inner(payload, on_chunk); -+ let result = -+ self.exec_protocol_callback_inner(payload, on_chunk.clone(), PROTOCOL_HYBRID, true); - self.protocol_stdin.finish(); -- result -+ let output = result?; -+ let length = output.length() as usize; -+ for start in (0..length).step_by(PROTOCOL_CHUNK_BYTES) { -+ let end = start.saturating_add(PROTOCOL_CHUNK_BYTES).min(length); -+ let chunk = output.slice(start as u32, end as u32); -+ on_chunk -+ .call1(&JsValue::UNDEFINED, &chunk) -+ .map_err(|error| anyhow::anyhow!("protocol stream callback failed: {error:?}"))?; -+ } -+ Ok(()) - } - - fn exec_protocol_stream_inner( -@@ -608,53 +746,107 @@ - payload: &Uint8Array, - on_chunk: Function, - ) -> anyhow::Result<()> { -- self.ensure_open()?; -+ let output = self.exec_protocol_callback_inner( -+ payload, -+ on_chunk, -+ PROTOCOL_BUFFERED_INPUT_STREAMED_OUTPUT, -+ false, -+ )?; -+ ensure!( -+ output.length() == 0, -+ "buffered-input/streamed-output transport left {} buffered protocol bytes", -+ output.length() -+ ); -+ Ok(()) -+ } -+ -+ fn exec_protocol_callback_inner( -+ &mut self, -+ payload: &Uint8Array, -+ on_chunk: Function, -+ transport: i32, -+ streaming_input: bool, -+ ) -> anyhow::Result { - self.protocol_stdout.begin(on_chunk.clone())?; - let previous = match self - .set_protocol_transport -- .call(&mut self.store, PROTOCOL_HYBRID) -- .context("enable hybrid protocol transport") -+ .call(&mut self.store, transport) -+ .with_context(|| format!("enable protocol transport mode {transport}")) - { -- Ok(previous) => previous, -+ Ok(previous) if previous == PROTOCOL_BUFFERED => previous, -+ Ok(previous) => { -+ let _ = self.protocol_stdout.finish(); -+ anyhow::bail!( -+ "enable protocol transport mode {transport} returned previous mode {previous}, expected {PROTOCOL_BUFFERED}" -+ ); -+ } - Err(error) => { - let _ = self.protocol_stdout.finish(); - return Err(error); - } - }; -- let execution = self.exec_protocol_raw_inner(payload, true); -- let restore = if self.closed { -- // A terminal main-loop outcome poisons guest state. Never invoke -- // another guest export merely to restore the transport selector. -+ let execution = self.exec_protocol_raw_inner(payload, streaming_input); -+ let restore = if execution.is_err() { -+ // Once any guest-facing step fails, guest state is unknown. The -+ // outer phase boundary will poison the instance; do not re-enter -+ // the guest merely to restore the transport selector. - Ok(()) - } else { - self.set_protocol_transport - .call(&mut self.store, previous) -- .context("restore protocol transport") -- .map(|_| ()) -+ .with_context(|| format!("restore protocol transport from mode {transport}")) -+ .and_then(|replaced| { -+ ensure!( -+ replaced == transport, -+ "restore protocol transport replaced mode {replaced}, expected {transport}" -+ ); -+ Ok(()) -+ }) - }; - let callback = self.protocol_stdout.finish(); - -- let output = execution?; -- restore?; - callback?; -+ let output = execution?; -+ restore?; -- let length = output.length() as usize; -- for start in (0..length).step_by(PROTOCOL_CHUNK_BYTES) { -- let end = start.saturating_add(PROTOCOL_CHUNK_BYTES).min(length); -- let chunk = output.slice(start as u32, end as u32); -- on_chunk -- .call1(&JsValue::UNDEFINED, &chunk) -- .map_err(|error| anyhow::anyhow!("protocol stream callback failed: {error:?}"))?; -- } -- Ok(()) -+ Ok(output) - } - -- fn startup_inner(&mut self, packet: &Uint8Array) -> anyhow::Result { -- self.ensure_open()?; -+ fn startup_inner(&mut self, packet: &Uint8Array) -> anyhow::Result { -+ match self.state { -+ DirectInstanceState::Open => {} -+ DirectInstanceState::StartupRejected => { -+ anyhow::bail!( -+ "Oliphaunt direct startup was already rejected; the direct instance cannot be reused" -+ ) -+ } -+ _ => self.ensure_open()?, -+ } - ensure!( - !self.protocol_started, - "Oliphaunt direct startup already completed" - ); -- self.start_backend()?; -+ self.state = DirectInstanceState::StartupPhase; -+ let outcome = self.startup_guest_inner(packet)?; -+ match outcome { -+ DirectStartupOutcome::Processed { status, .. } => { -+ self.state = if status == PROCESS_STARTUP_ERROR { -+ DirectInstanceState::StartupRejected -+ } else { -+ DirectInstanceState::Open -+ }; -+ } -+ DirectStartupOutcome::Rejected(_) => self.poison_guest_phase(), -+ } -+ Ok(outcome) -+ } -+ -+ fn startup_guest_inner(&mut self, packet: &Uint8Array) -> anyhow::Result { -+ match self.start_backend()? { -+ BackendStartOutcome::Alive => {} -+ BackendStartOutcome::Rejected(protocol) => { -+ return Ok(DirectStartupOutcome::Rejected(protocol)); -+ } -+ } - self.reset_io()?; - self.push_input_js(packet)?; - -@@ -667,17 +859,28 @@ - .process_startup - .call(&mut self.store, port, 1, 1) - .context("ProcessStartupPacket")?; -- self.pq_flush.call(&mut self.store).ok(); -- if status == 0 { -+ ensure!( -+ status == PROCESS_STARTUP_OK || status == PROCESS_STARTUP_ERROR, -+ "ProcessStartupPacket returned invalid status {status}" -+ ); -+ if status == PROCESS_STARTUP_OK { - self.send_conn_data - .call(&mut self.store) - .context("oliphaunt_wasix_send_conn_data")?; -- self.pq_flush -- .call(&mut self.store) -- .context("oliphaunt_wasix_pq_flush after startup")?; - self.protocol_started = true; - } -- self.take_output_js() -+ let flush_status = self -+ .pq_flush -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_pq_flush after startup")?; -+ ensure!( -+ flush_status == 0, -+ "oliphaunt_wasix_pq_flush after startup returned {flush_status}" -+ ); -+ Ok(DirectStartupOutcome::Processed { -+ output: self.take_output_js()?, -+ status, -+ }) - } - - fn exec_protocol_raw_inner( -@@ -685,7 +888,10 @@ - payload: &Uint8Array, - streaming: bool, - ) -> anyhow::Result { -- self.ensure_open()?; -+ ensure!( -+ self.state == DirectInstanceState::GuestPhase, -+ "direct protocol exchange did not enter its guest phase" -+ ); - ensure!( - self.protocol_started, - "Oliphaunt direct startup has not completed" -@@ -727,21 +933,48 @@ - } - - fn close_inner(&mut self) -> anyhow::Result<()> { -- if self.closed { -- return Ok(()); -- } -- self.set_active -- .call(&mut self.store, 0) -- .context("oliphaunt_wasix_set_active(0)")?; -- if let Some(run_atexit_funcs) = &self.run_atexit_funcs { -- run_atexit_funcs -- .call(&mut self.store) -- .context("oliphaunt_wasix_run_atexit_funcs")?; -+ match self.state { -+ DirectInstanceState::Poisoned | DirectInstanceState::Closed => return Ok(()), -+ DirectInstanceState::StartupPhase -+ | DirectInstanceState::GuestPhase -+ | DirectInstanceState::Closing => { -+ self.poison_guest_phase(); -+ anyhow::bail!( -+ "Oliphaunt direct instance cannot close during an active guest phase; the direct instance is closed" -+ ); -+ } -+ DirectInstanceState::Open | DirectInstanceState::StartupRejected => {} - } -- self.closed = true; -+ self.state = DirectInstanceState::Closing; -+ let result = (|| { -+ let expected_active = i32::from(self.backend_started); -+ let previous_active = self -+ .set_active -+ .call(&mut self.store, 0) -+ .context("oliphaunt_wasix_set_active(0)")?; -+ ensure!( -+ previous_active == expected_active, -+ "oliphaunt_wasix_set_active(0) returned previous state {previous_active}, expected {expected_active}" -+ ); -+ if let Some(run_atexit_funcs) = &self.run_atexit_funcs { -+ run_atexit_funcs -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_run_atexit_funcs")?; -+ } -+ Ok(()) -+ })(); - self.backend_started = false; - self.protocol_started = false; -- Ok(()) -+ match result { -+ Ok(()) => { -+ self.state = DirectInstanceState::Closed; -+ Ok(()) -+ } -+ Err(error) => { -+ self.state = DirectInstanceState::Poisoned; -+ Err(error.context("direct instance cleanup failed; the direct instance is closed")) -+ } -+ } - } - async fn instantiate( - module: WebAssembly::Module, -@@ -802,6 +1035,8 @@ - &instance, - "oliphaunt_wasix_prepare_trusted_embedded_session", - )?; -+ let startup_outcome = -+ typed_export(&mut store, &instance, "oliphaunt_wasix_startup_outcome_v1")?; - let wasi_start = typed_export(&mut store, &instance, "_start")?; - let start_oliphaunt = typed_export(&mut store, &instance, "oliphaunt_wasix_start")?; - let run_atexit_funcs = -@@ -838,6 +1073,7 @@ - set_protocol_transport, - set_active, - prepare_trusted_embedded_session, -+ startup_outcome, - wasi_start, - start_oliphaunt, - run_atexit_funcs, -@@ -850,38 +1086,79 @@ - send_ready, - backend_started: false, - protocol_started: false, -- closed: false, -+ state: DirectInstanceState::Open, - }; - direct.reset_io()?; - Ok(direct) - } - - fn ensure_open(&self) -> anyhow::Result<()> { -- ensure!(!self.closed, "Oliphaunt direct instance is closed"); -+ ensure!( -+ self.state == DirectInstanceState::Open, -+ "Oliphaunt direct instance is closed" -+ ); - Ok(()) - } - -- fn poison_main_loop(&mut self) { -- // A trap, invalid typed outcome, or failed post-step export can leave -- // arbitrary guest state behind. Do not call another guest export, -- // including the ordinary close sequence. -- self.closed = true; -+ fn ensure_protocol_started(&self) -> anyhow::Result<()> { -+ match self.state { -+ DirectInstanceState::Open => {} -+ DirectInstanceState::StartupRejected => { -+ anyhow::bail!( -+ "Oliphaunt direct startup was rejected; the direct instance cannot be reused" -+ ) -+ } -+ _ => self.ensure_open()?, -+ } -+ ensure!( -+ self.protocol_started, -+ "Oliphaunt direct startup has not completed" -+ ); -+ Ok(()) -+ } -+ -+ fn run_guest_phase( -+ &mut self, -+ phase: &'static str, -+ operation: impl FnOnce(&mut Self) -> anyhow::Result, -+ ) -> anyhow::Result { -+ self.ensure_open()?; -+ self.state = DirectInstanceState::GuestPhase; -+ match operation(self) { -+ Ok(value) if self.state == DirectInstanceState::GuestPhase => { -+ self.state = DirectInstanceState::Open; -+ Ok(value) -+ } -+ Ok(_) => anyhow::bail!( -+ "{phase} reached a terminal lifecycle state; the direct instance is closed" -+ ), -+ Err(error) => { -+ if self.state == DirectInstanceState::GuestPhase { -+ self.poison_guest_phase(); -+ } -+ Err(error.context(format!( -+ "{phase} failed after entering the guest; the direct instance is closed" -+ ))) -+ } -+ } -+ } -+ -+ fn poison_guest_phase(&mut self) { -+ // Any failure after a guest phase starts can leave arbitrary guest -+ // state behind. No later operation, including close, may call another -+ // guest export. -+ self.state = DirectInstanceState::Poisoned; - self.backend_started = false; - self.protocol_started = false; - } - -- fn terminal_main_loop_outcome(&mut self, failure: impl Into) -> anyhow::Error { -- let failure = failure.into(); -- self.poison_main_loop(); -- anyhow::anyhow!("{failure}; the direct instance is closed") -+ fn terminal_main_loop_outcome(&self, failure: impl Into) -> anyhow::Error { -+ anyhow::anyhow!(failure.into()) - } - -- fn terminal_main_loop_error(&mut self, error: wasmer::RuntimeError) -> anyhow::Error { -- self.poison_main_loop(); -- anyhow::Error::from(error).context( -- "PostgresMainLoopOnce trapped instead of returning a typed outcome; \ -- the direct instance is closed", -- ) -+ fn terminal_main_loop_error(&self, error: wasmer::RuntimeError) -> anyhow::Error { -+ anyhow::Error::from(error) -+ .context("PostgresMainLoopOnce trapped instead of returning a typed outcome") - } - - fn call_main_loop(&mut self) -> anyhow::Result { -@@ -897,38 +1174,35 @@ - } - - fn send_ready_after_main_loop(&mut self) -> anyhow::Result<()> { -- match self.send_ready.call(&mut self.store) { -- Ok(()) => Ok(()), -- Err(error) => { -- self.poison_main_loop(); -- Err(anyhow::Error::from(error).context( -- "PostgresSendReadyForQueryIfNecessary trapped after a typed main-loop outcome; \ -- the direct instance is closed", -- )) -- } -- } -+ self.send_ready -+ .call(&mut self.store) -+ .context("PostgresSendReadyForQueryIfNecessary trapped after a typed main-loop outcome") - } - - fn flush_after_main_loop(&mut self) -> anyhow::Result<()> { -- match self.pq_flush.call(&mut self.store) { -- Ok(()) => Ok(()), -- Err(error) => { -- self.poison_main_loop(); -- Err(anyhow::Error::from(error).context( -- "oliphaunt_wasix_pq_flush trapped after a typed main-loop outcome; \ -- the direct instance is closed", -- )) -- } -- } -+ let status = self -+ .pq_flush -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_pq_flush trapped after a typed main-loop outcome")?; -+ ensure!( -+ status == 0, -+ "protocol output flush returned {status} after a typed main-loop outcome" -+ ); -+ Ok(()) - } - -- fn start_backend(&mut self) -> anyhow::Result<()> { -+ fn start_backend(&mut self) -> anyhow::Result { - if self.backend_started { -- return Ok(()); -+ return Ok(BackendStartOutcome::Alive); - } -- self.set_active -+ let previous_active = self -+ .set_active - .call(&mut self.store, 1) - .context("oliphaunt_wasix_set_active(1)")?; -+ ensure!( -+ previous_active == 0, -+ "oliphaunt_wasix_set_active(1) returned previous state {previous_active}, expected 0" -+ ); - let prepare_status = self - .prepare_trusted_embedded_session - .call(&mut self.store) -@@ -937,16 +1211,107 @@ - prepare_status == 0, - "oliphaunt_wasix_prepare_trusted_embedded_session rejected after PostgreSQL startup began" - ); -+ let startup_outcome_pointer = self -+ .startup_outcome -+ .call(&mut self.store) -+ .context("oliphaunt_wasix_startup_outcome_v1 before _start")?; -+ let pending = self.read_startup_outcome_descriptor(startup_outcome_pointer)?; -+ ensure!( -+ pending.kind == STARTUP_OUTCOME_PENDING, -+ "startup outcome was not pending before _start" -+ ); -+ ensure!( -+ pending.data_pointer == 0 && pending.data_length == 0, -+ "pending startup outcome carried protocol data" -+ ); - match self.wasi_start.call(&mut self.store) { -- Ok(()) => {} - Err(error) if runtime_exit_code(&error) == Some(OLIPHAUNT_EXIT_ALIVE) => {} -+ Err(error) if runtime_exit_code(&error) == Some(OLIPHAUNT_EXIT_STARTUP_REJECTED) => { -+ let protocol = self -+ .read_startup_rejection(startup_outcome_pointer) -+ .context("read rejected _start outcome")?; -+ return Ok(BackendStartOutcome::Rejected(protocol)); -+ } -+ Ok(()) => anyhow::bail!("_start returned without an Oliphaunt lifecycle exit"), - Err(error) => return Err(error).context("_start Oliphaunt single-user backend"), - } - self.start_oliphaunt - .call(&mut self.store) - .context("oliphaunt_wasix_start")?; - self.backend_started = true; -- Ok(()) -+ Ok(BackendStartOutcome::Alive) -+ } -+ -+ fn read_startup_outcome_descriptor( -+ &self, -+ descriptor_pointer: i32, -+ ) -> anyhow::Result { -+ ensure!( -+ descriptor_pointer != 0, -+ "oliphaunt_wasix_startup_outcome_v1 returned null" -+ ); -+ let bytes = oliphaunt_copy_from_guest( -+ &self.guest_memory, -+ descriptor_pointer as u32, -+ STARTUP_OUTCOME_DESCRIPTOR_BYTES, -+ ) -+ .map_err(|error| anyhow::anyhow!("copy startup outcome descriptor: {error:?}"))? -+ .to_vec(); -+ ensure!( -+ bytes.len() == STARTUP_OUTCOME_DESCRIPTOR_BYTES as usize, -+ "short startup outcome descriptor" -+ ); -+ let version = read_u32_le(&bytes, 0); -+ let size = read_u32_le(&bytes, 4); -+ let kind = read_u32_le(&bytes, 8); -+ let reserved = read_u32_le(&bytes, 12); -+ ensure!( -+ version == STARTUP_OUTCOME_VERSION, -+ "unsupported startup outcome version {version}" -+ ); -+ ensure!( -+ size == STARTUP_OUTCOME_DESCRIPTOR_BYTES, -+ "invalid startup outcome descriptor size {size}" -+ ); -+ ensure!(reserved == 0, "startup outcome reserved field was nonzero"); -+ Ok(StartupOutcomeDescriptor { -+ kind, -+ data_pointer: read_u64_le(&bytes, 16), -+ data_length: read_u64_le(&bytes, 24), -+ }) -+ } -+ -+ fn read_startup_rejection(&self, descriptor_pointer: i32) -> anyhow::Result> { -+ let descriptor = self.read_startup_outcome_descriptor(descriptor_pointer)?; -+ ensure!( -+ descriptor.kind == STARTUP_OUTCOME_REJECTED, -+ "_start Exit98 did not publish a rejected startup outcome" -+ ); -+ ensure!( -+ (1..=1_048_576).contains(&descriptor.data_length) && descriptor.data_pointer > 0, -+ "rejected startup outcome carried empty or oversized protocol data" -+ ); -+ let end = descriptor -+ .data_pointer -+ .checked_add(descriptor.data_length) -+ .context("startup rejection protocol bounds overflow")?; -+ ensure!( -+ end <= u32::MAX as u64 + 1, -+ "startup rejection protocol exceeds Wasm32 memory bounds" -+ ); -+ let pointer = u32::try_from(descriptor.data_pointer) -+ .context("startup rejection protocol pointer exceeds u32")?; -+ let length = u32::try_from(descriptor.data_length) -+ .context("startup rejection protocol length exceeds u32")?; -+ let protocol = oliphaunt_copy_from_guest(&self.guest_memory, pointer, length) -+ .map_err(|error| anyhow::anyhow!("copy startup rejection protocol: {error:?}"))? -+ .to_vec(); -+ ensure!( -+ protocol.len() == length as usize, -+ "short startup rejection protocol copy" -+ ); -+ validate_startup_rejection_protocol(&protocol)?; -+ Ok(protocol) - } - - fn reset_io(&mut self) -> anyhow::Result<()> { -@@ -1002,7 +1367,9 @@ - .context("oliphaunt_wasix_output_data")?; - ensure!(ptr > 0, "oliphaunt_wasix_output_data returned null"); - let output = oliphaunt_copy_from_guest(&self.guest_memory, ptr as u32, len as u32) -- .map_err(|error| anyhow::anyhow!("copy direct protocol output from guest: {error:?}")); -+ .map_err(|error| { -+ anyhow::anyhow!("copy direct protocol output from guest: {error:?}") -+ })?; - ensure!( - self.output_reset - .call(&mut self.store) -@@ -1010,7 +1377,7 @@ - == 0, - "oliphaunt_wasix_output_reset after read failed" - ); -- output -+ Ok(output) - } - - fn protocol_input_remaining(&mut self) -> anyhow::Result { -@@ -1022,26 +1389,21 @@ - if available > 0 { - return Ok(available); - } -- self.pq_buffer_remaining_data -+ let buffered = self -+ .pq_buffer_remaining_data - .call(&mut self.store) -- .context("pq_buffer_remaining_data") -+ .context("pq_buffer_remaining_data")?; -+ ensure!(buffered >= 0, "negative buffered protocol input length"); -+ Ok(buffered) - } - -- fn startup_error(&mut self, error: anyhow::Error) -> Error { -+ fn startup_rejection_error(&self, error: anyhow::Error, protocol: Vec) -> Error { - let error = self.stderr.attach(error); -- let _ = self.pq_flush.call(&mut self.store); -- let protocol = self -- .take_output_js() -- .unwrap_or_else(|_| Uint8Array::new_with_length(0)); -- if protocol.length() == 0 { -- return Error::from(error); -- } -- - let js_error = js_sys::Error::new(&error.to_string()); - let _ = js_sys::Reflect::set( - &js_error, - &wasm_bindgen::JsValue::from_str("protocolResponse"), -- &protocol, -+ &Uint8Array::from(protocol.as_slice()), - ); - let _ = js_sys::Reflect::set( - &js_error, -@@ -1052,6 +1414,88 @@ - } - } - -+fn read_u32_le(bytes: &[u8], offset: usize) -> u32 { -+ u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()) -+} -+ -+fn read_u64_le(bytes: &[u8], offset: usize) -> u64 { -+ u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap()) -+} -+ -+fn validate_startup_rejection_protocol(protocol: &[u8]) -> anyhow::Result<()> { -+ let mut offset = 0usize; -+ let mut found_error_response = false; -+ while offset < protocol.len() { -+ ensure!( -+ protocol.len() - offset >= 5, -+ "truncated startup rejection protocol frame" -+ ); -+ let tag = protocol[offset]; -+ let frame_length = -+ u32::from_be_bytes(protocol[offset + 1..offset + 5].try_into().unwrap()) as usize; -+ ensure!( -+ frame_length >= 4, -+ "invalid startup rejection protocol frame length {frame_length}" -+ ); -+ let frame_end = offset -+ .checked_add(1) -+ .and_then(|value| value.checked_add(frame_length)) -+ .context("startup rejection protocol frame bounds overflow")?; -+ ensure!( -+ frame_end <= protocol.len(), -+ "truncated startup rejection protocol frame" -+ ); -+ if tag == b'E' { -+ validate_error_response_fields(&protocol[offset + 5..frame_end])?; -+ found_error_response = true; -+ } -+ offset = frame_end; -+ } -+ ensure!( -+ found_error_response, -+ "startup rejection protocol did not contain an ErrorResponse" -+ ); -+ Ok(()) -+} -+ -+fn validate_error_response_fields(fields: &[u8]) -> anyhow::Result<()> { -+ ensure!( -+ fields.last() == Some(&0), -+ "startup ErrorResponse was not terminated" -+ ); -+ let mut offset = 0usize; -+ let mut found_sqlstate = false; -+ while offset < fields.len() - 1 { -+ ensure!( -+ fields[offset] != 0, -+ "startup ErrorResponse terminated before the end of its frame" -+ ); -+ let field_type = fields[offset]; -+ offset += 1; -+ let terminator = fields[offset..] -+ .iter() -+ .position(|byte| *byte == 0) -+ .context("startup ErrorResponse contained an unterminated field")?; -+ if field_type == b'C' { -+ ensure!( -+ terminator == 5, -+ "startup ErrorResponse carried an invalid SQLSTATE" -+ ); -+ found_sqlstate = true; -+ } -+ offset += terminator + 1; -+ } -+ ensure!( -+ offset == fields.len() - 1, -+ "startup ErrorResponse contained trailing bytes" -+ ); -+ ensure!( -+ found_sqlstate, -+ "startup ErrorResponse did not contain a SQLSTATE" -+ ); -+ Ok(()) -+} -+ - fn typed_export( - store: &mut Store, - instance: &WasmerInstance, diff --git a/src/wasix/browser-host/patches/0027-wasmer-wasix-isolate-thread-local-handle-borrows.patch b/src/wasix/browser-host/patches/0027-wasmer-wasix-isolate-thread-local-handle-borrows.patch deleted file mode 100644 index d4dae9713..000000000 --- a/src/wasix/browser-host/patches/0027-wasmer-wasix-isolate-thread-local-handle-borrows.patch +++ /dev/null @@ -1,176 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: isolate registry and handle borrows - -Clone each realm-local registered handle under a short registry borrow before -borrowing the handle value. This makes the unsafe extended Ref lifetime derive -from the same Rc retained by the returned guard, while registry removal stays -independent from access to the registered value. - -The JS engine can terminate a guest by trapping across a Rust host callback, -and that boundary is not guaranteed to run every Rust destructor. Use fallible -inner borrows so a stranded handle guard becomes absence, and make both -callback_signal handle acquisitions translate that conflict to the existing -terminal WASI Fault outcome. Registry borrow invariant violations still panic; -this does not suppress unrelated reentrant registry mutation. - -diff --git a/src/state/handles/thread_local.rs b/src/state/handles/thread_local.rs -index fd01509b..a6c4939d 100644 ---- a/src/state/handles/thread_local.rs -+++ b/src/state/handles/thread_local.rs -@@ -17,6 +17,20 @@ - = RefCell::new(HashMap::new()); - } - -+fn clone_registered( -+ registry: &RefCell>>>, -+ id: u64, -+) -> Option>> { -+ registry.borrow().get(&id).cloned() -+} -+ -+fn remove_registered( -+ registry: &RefCell>>>, -+ id: u64, -+) -> Option>> { -+ registry.borrow_mut().remove(&id) -+} -+ - /// This non-sendable guard provides memory safe access - /// to the WasiInstance object but only when it is - /// constructed with certain constraints -@@ -75,48 +89,29 @@ - } - impl WasiInstanceHandlesPointer { - pub fn get(&self) -> Option> { -- self.id -- .iter() -- .filter_map(|id| { -- THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { -- let map = map.borrow(); -- if let Some(inner) = map.get(id) { -- let borrow: Ref = inner.borrow(); -- let borrow: Ref<'static, WasiModuleTreeHandles> = -- unsafe { std::mem::transmute(borrow) }; -- Some(WasiInstanceGuard { -- borrow, -- _pointer: self, -- _inner: inner.clone(), -- }) -- } else { -- None -- } -- }) -- }) -- .next() -+ let id = self.id?; -+ // Clone the registry-owned Rc before extending the inner Ref lifetime. -+ // The returned guard retains that same Rc, and registry cleanup never -+ // needs to borrow the registered value. -+ let inner = THREAD_LOCAL_INSTANCE_HANDLES.with(|map| clone_registered(map, id))?; -+ let borrow: Ref = inner.try_borrow().ok()?; -+ let borrow: Ref<'static, WasiModuleTreeHandles> = unsafe { std::mem::transmute(borrow) }; -+ Some(WasiInstanceGuard { -+ borrow, -+ _pointer: self, -+ _inner: inner.clone(), -+ }) - } - pub fn get_mut(&self) -> Option> { -- self.id -- .into_iter() -- .filter_map(|id| { -- THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { -- let map = map.borrow_mut(); -- if let Some(inner) = map.get(&id) { -- let borrow: RefMut = inner.borrow_mut(); -- let borrow: RefMut<'static, WasiModuleTreeHandles> = -- unsafe { std::mem::transmute(borrow) }; -- Some(WasiInstanceGuardMut { -- borrow, -- _pointer: self, -- _inner: inner.clone(), -- }) -- } else { -- None -- } -- }) -- }) -- .next() -+ let id = self.id?; -+ let inner = THREAD_LOCAL_INSTANCE_HANDLES.with(|map| clone_registered(map, id))?; -+ let borrow: RefMut = inner.try_borrow_mut().ok()?; -+ let borrow: RefMut<'static, WasiModuleTreeHandles> = unsafe { std::mem::transmute(borrow) }; -+ Some(WasiInstanceGuardMut { -+ borrow, -+ _pointer: self, -+ _inner: inner.clone(), -+ }) - } - pub fn set(&mut self, val: WasiModuleTreeHandles) { - self.clear(); -@@ -138,12 +133,33 @@ - } - fn destroy(id: u64) { - THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { -- let mut map = map.borrow_mut(); -- map.remove(&id); -+ remove_registered(map, id); - }) - } - } - -+#[cfg(test)] -+mod tests { -+ use super::*; -+ -+ #[test] -+ fn registry_removal_does_not_borrow_the_registered_value() { -+ let registry = RefCell::new(HashMap::new()); -+ registry -+ .borrow_mut() -+ .insert(7, Rc::new(RefCell::new(41_u8))); -+ -+ let inner = clone_registered(®istry, 7).expect("registered value"); -+ let retained = inner.borrow(); -+ assert!(inner.try_borrow_mut().is_err()); -+ -+ let removed = remove_registered(®istry, 7); -+ assert!(removed.is_some()); -+ assert!(registry.borrow().is_empty()); -+ assert_eq!(*retained, 41); -+ } -+} -+ - /// This provides access to the memory inside the instance - pub(crate) struct WasiInstanceGuardMemory<'a> { - // the order is very important as the first value is -diff --git a/src/syscalls/wasix/callback_signal.rs b/src/syscalls/wasix/callback_signal.rs -index 940b34d2..5404ec72 100644 ---- a/src/syscalls/wasix/callback_signal.rs -+++ b/src/syscalls/wasix/callback_signal.rs -@@ -28,7 +28,8 @@ - Span::current().record("name", name.as_str()); - - let funct = env -- .inner() -+ .try_inner() -+ .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))? - .main_module_instance_handles() - .instance - .exports -@@ -37,7 +38,13 @@ - Span::current().record("funct_is_some", funct.is_some()); - - { -- let mut env_inner = ctx.data_mut().inner_mut(); -+ // A JS trap can strand an instance-handle guard. Treat that terminal -+ // lifecycle conflict as a guest fault instead of panicking while -+ // acquiring the RefCell mutably. -+ let mut env_inner = ctx -+ .data_mut() -+ .try_inner_mut() -+ .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?; - let inner = env_inner.main_module_instance_handles_mut(); - inner.signal = funct; - inner.signal_set = true; diff --git a/src/wasix/browser-host/patches/0034-wasmer-js-preserve-seek-end-errors.patch b/src/wasix/browser-host/patches/0034-wasmer-js-preserve-seek-end-errors.patch deleted file mode 100644 index 57a9b6234..000000000 --- a/src/wasix/browser-host/patches/0034-wasmer-js-preserve-seek-end-errors.patch +++ /dev/null @@ -1,116 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-js: preserve seek-from-end provider failures - -Compute seek-from-end using the synchronous filesystem provider's result and -leave the cursor unchanged on provider or arithmetic errors. Do not translate -a failed length lookup into a successful seek. The tests cover positive and -negative offsets, overflow and failed providers; this is a bridge correctness -fix independent of performance claims. - -diff --git a/src/fs/sync_bridge.rs b/src/fs/sync_bridge.rs -index e37f33e..7e79d76 100644 ---- a/src/fs/sync_bridge.rs -+++ b/src/fs/sync_bridge.rs -@@ -356,6 +356,16 @@ impl fmt::Debug for SyncBridgeFile { - } - } - -+impl SyncBridgeFile { -+ fn seek_from_end_sync(&mut self, offset: i64) -> io::Result { -+ let size = self -+ .backend -+ .request(OP_FILE_SIZE, "", &[], self.descriptor.into(), 0, 0) -+ .map(|response| response.value0); -+ update_cursor_from_end(&mut self.cursor, size, offset) -+ } -+} -+ - impl Drop for SyncBridgeFile { - fn drop(&mut self) { - let _ = self -@@ -489,7 +499,7 @@ impl AsyncSeek for SyncBridgeFile { - let next = match position { - SeekFrom::Start(offset) => offset, - SeekFrom::Current(delta) => checked_offset(self.cursor, delta)?, -- SeekFrom::End(delta) => checked_offset(self.size(), delta)?, -+ SeekFrom::End(delta) => return self.seek_from_end_sync(delta).map(|_| ()), - }; - self.cursor = next; - Ok(()) -@@ -500,6 +510,16 @@ impl AsyncSeek for SyncBridgeFile { - } - } - -+fn update_cursor_from_end( -+ cursor: &mut u64, -+ size: virtual_fs::Result, -+ offset: i64, -+) -> io::Result { -+ let next = checked_offset(size.map_err(io::Error::from)?, offset)?; -+ *cursor = next; -+ Ok(next) -+} -+ - fn checked_offset(base: u64, delta: i64) -> io::Result { - if delta >= 0 { - base.checked_add(delta as u64) -@@ -509,6 +529,58 @@ fn checked_offset(base: u64, delta: i64) -> io::Result { - .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput)) - } - -+#[cfg(test)] -+mod seek_tests { -+ use super::*; -+ use wasm_bindgen_test::wasm_bindgen_test; -+ -+ #[wasm_bindgen_test] -+ fn seek_from_end_preserves_bridge_arithmetic_and_cursor() { -+ let mut cursor = 3; -+ assert_eq!(update_cursor_from_end(&mut cursor, Ok(6), 5).unwrap(), 11); -+ assert_eq!(cursor, 11); -+ assert_eq!(update_cursor_from_end(&mut cursor, Ok(6), -5).unwrap(), 1); -+ assert_eq!(cursor, 1); -+ } -+ -+ #[wasm_bindgen_test] -+ fn seek_from_end_preserves_cursor_on_arithmetic_error() { -+ let mut cursor = 3; -+ let error = update_cursor_from_end(&mut cursor, Ok(6), -7).unwrap_err(); -+ assert_eq!(error.kind(), io::ErrorKind::InvalidInput); -+ assert_eq!(cursor, 3); -+ } -+ -+ #[wasm_bindgen_test] -+ fn seek_from_end_propagates_provider_error_without_moving_cursor() { -+ let backend = js_sys::Object::new(); -+ let request = Function::new_with_args( -+ "opcode, _path, _payload, _arg0, _arg1, _flags", -+ r#" -+ switch (opcode) { -+ case 8: return [0, 0, 0, 0]; -+ case 14: return [6, 0, 0, 0]; -+ default: throw new Error(`unexpected filesystem opcode ${opcode}`); -+ } -+ "#, -+ ); -+ js_sys::Reflect::set(&backend, &JsValue::from_str("request"), request.as_ref()).unwrap(); -+ let mut file = SyncBridgeFile { -+ backend: Arc::new(Backend::new(backend.into(), 8 * 1024).unwrap()), -+ descriptor: 7, -+ readable: true, -+ writable: false, -+ cursor: 3, -+ }; -+ -+ let error = Pin::new(&mut file) -+ .start_seek(SeekFrom::End(0)) -+ .unwrap_err(); -+ assert_eq!(error.kind(), io::ErrorKind::PermissionDenied); -+ assert_eq!(file.cursor, 3); -+ } -+} -+ - fn normalize_path(path: &Path) -> virtual_fs::Result { - let mut parts = Vec::new(); - for component in path.components() { diff --git a/src/wasix/browser-host/patches/0035-wasmer-wasix-preserve-shared-seek-offset.patch b/src/wasix/browser-host/patches/0035-wasmer-wasix-preserve-shared-seek-offset.patch deleted file mode 100644 index bf288d9f5..000000000 --- a/src/wasix/browser-host/patches/0035-wasmer-wasix-preserve-shared-seek-offset.patch +++ /dev/null @@ -1,80 +0,0 @@ -From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: retain the original shared offset across seek - -Keep the original open-file-description offset alive while seek suspends. -A reused numeric descriptor must not redirect completion to a different file's -offset. The regression covers suspension plus descriptor reuse. This is a -general WASIX correctness fix suitable for independent upstream review. - -diff --git a/src/syscalls/wasi/fd_seek.rs b/src/syscalls/wasi/fd_seek.rs -index 31cfe43e..18cf012e 100644 ---- a/src/syscalls/wasi/fd_seek.rs -+++ b/src/syscalls/wasi/fd_seek.rs -@@ -95,6 +95,7 @@ pub(crate) fn fd_seek_internal( - #[allow(clippy::await_holding_lock)] - if let Some(handle) = handle { - let handle = handle.clone(); -+ let fd_offset = fd_entry.inner.offset.clone(); - drop(guard); - - wasi_try_ok_ok!(__asyncify(ctx, None, async move { -@@ -104,11 +105,7 @@ pub(crate) fn fd_seek_internal( - .await - .map_err(map_io_err)?; - -- // TODO: handle case if fd_entry.offset uses 64 bits of a u64 -- drop(handle); -- let mut fd_map = state.fs.fd_map.write().unwrap(); -- let fd_entry = fd_map.get_mut(fd).ok_or(Errno::Badf)?; -- fd_entry.offset.store(end, Ordering::Release); -+ fd_offset.store(end, Ordering::Release); - Ok(()) - })?); - } else { -@@ -150,3 +147,45 @@ pub(crate) fn fd_seek_internal( - - Ok(Ok(new_offset)) - } -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use std::future::Future as _; -+ -+ #[test] -+ fn suspended_seek_updates_original_offset_after_descriptor_reuse() { -+ let original_offset = Arc::new(AtomicU64::new(3)); -+ let replacement_offset = Arc::new(AtomicU64::new(7)); -+ let mut fd_map = std::collections::HashMap::from([(4_u32, original_offset.clone())]); -+ let captured_offset = fd_map.get(&4).unwrap().clone(); -+ let ready = Arc::new(AtomicBool::new(false)); -+ let seek_ready = ready.clone(); -+ let seek = futures::future::poll_fn(move |cx| { -+ if seek_ready.load(Ordering::Acquire) { -+ Poll::Ready(41) -+ } else { -+ cx.waker().wake_by_ref(); -+ Poll::Pending -+ } -+ }); -+ let work = async move { -+ let end = seek.await; -+ captured_offset.store(end, Ordering::Release); -+ }; -+ futures::pin_mut!(work); -+ -+ let waker = futures::task::noop_waker(); -+ let mut cx = Context::from_waker(&waker); -+ assert_eq!(work.as_mut().poll(&mut cx), Poll::Pending); -+ -+ assert!(fd_map.remove(&4).is_some()); -+ assert!(fd_map.insert(4, replacement_offset.clone()).is_none()); -+ ready.store(true, Ordering::Release); -+ -+ assert_eq!(work.as_mut().poll(&mut cx), Poll::Ready(())); -+ assert_eq!(original_offset.load(Ordering::Acquire), 41); -+ assert_eq!(replacement_offset.load(Ordering::Acquire), 7); -+ assert!(Arc::ptr_eq(fd_map.get(&4).unwrap(), &replacement_offset)); -+ } -+} diff --git a/src/wasix/browser-host/patches/0037-virtual-mio-retain-inline-wakes.patch b/src/wasix/browser-host/patches/0037-virtual-mio-retain-inline-wakes.patch deleted file mode 100644 index f9133889b..000000000 --- a/src/wasix/browser-host/patches/0037-virtual-mio-retain-inline-wakes.patch +++ /dev/null @@ -1,88 +0,0 @@ -diff --git a/src/waker.rs b/src/waker.rs ---- a/src/waker.rs -+++ b/src/waker.rs -@@ -1,31 +1,29 @@ - #![allow(dead_code, unused)] - use std::{ -- sync::{Arc, Condvar, Mutex}, -+ sync::{atomic::{AtomicBool, Ordering}, Arc}, -+ thread::{self, Thread}, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, - }; - - use futures::Future; - - pub struct InlineWaker { -- lock: Mutex<()>, -- condvar: Condvar, -+ thread: Thread, -+ notified: AtomicBool, - } - impl InlineWaker { - pub fn new() -> Arc { - Arc::new(Self { -- lock: Mutex::new(()), -- condvar: Condvar::new(), -+ thread: thread::current(), -+ notified: AtomicBool::new(false), - }) - } - - fn wake_now(&self) { -- // Note: This guard should be there to prevent race conditions however in the -- // browser it causes a lock up - some strange browser issue. What I suspect -- // is that the Condvar::wait call is not releasing the mutex lock -- #[cfg(not(feature = "js"))] -- let _guard = self.lock.lock().unwrap(); -- -- self.condvar.notify_all(); -+ // Retain a wake delivered during poll, before the worker parks. -+ // No synchronous mutex may block the browser thread delivering a wake. -+ self.notified.store(true, Ordering::Release); -+ self.thread.unpark(); - } - - pub fn as_waker(self: &Arc) -> Waker { -@@ -43,10 +41,11 @@ - // We loop waiting for the waker to be woken, then we poll again - let mut task = Box::pin(task); - loop { -- let lock = inline_waker.lock.lock().unwrap(); - match task.as_mut().poll(&mut cx) { - Poll::Pending => { -- inline_waker.condvar.wait(lock).ok(); -+ while !inline_waker.notified.swap(false, Ordering::Acquire) { -+ thread::park(); -+ } - } - Poll::Ready(ret) => { - return ret; -@@ -75,3 +74,28 @@ - |s| drop(Arc::from_raw(s as *const InlineWaker)), // decrease refcount - ) - }; -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use std::{future::poll_fn, sync::mpsc, time::Duration}; -+ -+ #[test] -+ fn wake_during_poll_is_retained_until_the_next_poll() { -+ let (tx, rx) = mpsc::channel(); -+ thread::spawn(move || { -+ let mut polled = false; -+ InlineWaker::block_on(poll_fn(|cx| { -+ if polled { -+ Poll::Ready(()) -+ } else { -+ polled = true; -+ cx.waker().wake_by_ref(); -+ Poll::Pending -+ } -+ })); -+ tx.send(()).unwrap(); -+ }); -+ rx.recv_timeout(Duration::from_secs(2)).unwrap(); -+ } -+} diff --git a/src/wasix/browser-host/patches/0040-wasmer-js-build-oliphaunt-browser-adapter.patch b/src/wasix/browser-host/patches/0040-wasmer-js-build-oliphaunt-browser-adapter.patch new file mode 100644 index 000000000..4d85433fe --- /dev/null +++ b/src/wasix/browser-host/patches/0040-wasmer-js-build-oliphaunt-browser-adapter.patch @@ -0,0 +1,1253 @@ +From: Sid Jain +Date: Sun, 4 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Build Oliphaunt's browser ABI on the SDK scheduler + +Retain the SDK's Web Worker scheduler and backend handle transfers, while +building only the browser bindings used by Oliphaunt. The direct PostgreSQL, +frontend-tool and filesystem adapter lives in our repository rather than an +incremental upstream patch chain. No upstream PostgreSQL package is consumed. +Use the explicitly pinned shared-memory Rust toolchain and wasm-pack binding +generator. Keep the previous O3 host optimization policy. + +diff --git a/js/bindgen/Cargo.toml b/js/bindgen/Cargo.toml +index 2b5070d..1199fc1 100644 +--- a/js/bindgen/Cargo.toml ++++ b/js/bindgen/Cargo.toml +@@ -33,7 +33,7 @@ virtual-mio = { version = "=0.704.0", default-features = false } + wasm-bindgen = "0.2.120" + wasm-bindgen-futures = "0.4" + wasmer = { version = "=7.4.0", default-features = false, features = ["js", "js-default"] } +-wasmer-sdk = { path = "../../rust", default-features = false, features = ["js-napi"] } ++virtual-fs = { version = "=0.704.0", default-features = false, features = ["js"] } + wasmer-package = { version = "=0.704.0", default-features = false, features = ["execution", "webc-v3"] } + wasmer-wasix = { version = "=0.704.0", default-features = false, features = ["js", "js-default"] } + wasmer-wasix-types = "=0.704.0" +@@ -60,8 +60,18 @@ features = [ + "WorkerNavigator", + "WorkerOptions", + "WorkerType", ++ "QueuingStrategy", ++ "ReadableStream", ++ "ReadableStreamDefaultController", ++ "ReadableStreamDefaultReader", ++ "WritableStream", ++ "WritableStreamDefaultController", ++ "WritableStreamDefaultWriter", + ] + ++[package.metadata.wasm-pack.profile.release] ++wasm-opt = false ++ + [dev-dependencies] + wasm-bindgen-test = "=0.3.76" + +diff --git a/js/bindgen/src/lib.rs b/js/bindgen/src/lib.rs +index 3c8da6a..168f084 100644 +--- a/js/bindgen/src/lib.rs ++++ b/js/bindgen/src/lib.rs +@@ -1,55 +1,16 @@ +-#![allow( +- clippy::cast_possible_truncation, +- clippy::cast_sign_loss, +- clippy::missing_errors_doc, +- clippy::missing_fields_in_debug, +- clippy::must_use_candidate, +- clippy::needless_pass_by_value, +- clippy::type_complexity +-)] +- +-mod browser_http; +-mod browser_tcp; +-mod host_filesystem; +-mod module_cache; +-mod node_network; +-mod package_cache; +-mod package_progress; +-mod task_manager; ++mod oliphaunt; + mod tasks; + mod worker_utils; + +-use std::{ +- collections::BTreeMap, +- sync::{Arc, Mutex as StdMutex}, +- time::Duration, +-}; +- +-use browser_http::{BrowserHttpRequestHandler, BrowserNetworkGroup}; +-use bytes::Bytes; +-use http::{Request as HttpRequest, header::HOST}; +-use js_sys::Uint8Array; +-use node_network::{NodeNetworkBridge, NodeNetworking}; + use once_cell::sync::Lazy; +-use package_cache::{HostPackageCache, NodeCacheBridge}; +-use serde::Deserialize; +-use tokio::{ +- io::{AsyncReadExt, AsyncWriteExt}, +- sync::Mutex, +-}; ++use std::sync::Mutex; + use wasm_bindgen::prelude::*; +-use wasmer_sdk::{ +- Command, NetworkPolicy, Output, Package, PackageSource, Process, ProcessHandle, ProcessStderr, +- ProcessStdin, ProcessStdout, Sandbox, SandboxBuilder, Stdio, TerminalOptions, Wasmer, +- WasmerConfig, +-}; +-use wasmer_wasix::PluggableRuntime; +-use wasmer_wasix::runtime::{DefaultTty, package_loader::PackageCache, resolver::QueryCache}; + ++pub use oliphaunt::*; + pub use tasks::ThreadPoolWorker; + +-pub(crate) static CUSTOM_WORKER_URL: Lazy>> = Lazy::new(StdMutex::default); +-pub(crate) static CUSTOM_SDK_URL: Lazy>> = Lazy::new(StdMutex::default); ++pub(crate) static CUSTOM_WORKER_URL: Lazy>> = Lazy::new(Mutex::default); ++pub(crate) static CUSTOM_SDK_URL: Lazy>> = Lazy::new(Mutex::default); + + #[wasm_bindgen(start)] + fn initialize() { +@@ -65,1019 +26,3 @@ pub fn set_sdk_url(url: String) { + pub fn set_worker_url(url: String) { + *CUSTOM_WORKER_URL.lock().expect("worker URL lock poisoned") = Some(url); + } +- +-#[derive(Debug, Default, Deserialize)] +-#[serde(rename_all = "camelCase")] +-struct ClientOptions { +- output_bytes: Option, +- parallelism: Option, +- cache: Option, +-} +- +-#[derive(Debug, Default, Deserialize)] +-#[serde(rename_all = "camelCase")] +-struct ClientCacheOptions { +- mode: Option, +- namespace: Option, +- read_only: Option, +-} +- +-const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991; +-const MAX_WASM32_SIZE: u64 = u32::MAX as u64; +- +-#[derive(Deserialize)] +-#[serde(deny_unknown_fields)] +-struct PackageDefinition { +- modules: BTreeMap, +- commands: BTreeMap, +- entrypoint: Option, +- #[serde(default)] +- files: BTreeMap, +-} +- +-#[derive(Deserialize)] +-#[serde(deny_unknown_fields)] +-struct PackageCommandDefinition { +- module: String, +-} +- +-impl From for wasmer_sdk::PackageDefinition { +- fn from(value: PackageDefinition) -> Self { +- Self { +- modules: value +- .modules +- .into_iter() +- .map(|(name, bytes)| (name, bytes.into_vec().into())) +- .collect(), +- commands: value +- .commands +- .into_iter() +- .map(|(name, command)| { +- ( +- name, +- wasmer_sdk::PackageCommandDefinition { +- module: command.module, +- }, +- ) +- }) +- .collect(), +- entrypoint: value.entrypoint, +- files: value +- .files +- .into_iter() +- .map(|(path, bytes)| (path, bytes.into_vec().into())) +- .collect(), +- } +- } +-} +- +-#[wasm_bindgen(js_name = WasmerCore)] +-pub struct JsWasmer { +- inner: Wasmer, +- tasks: Arc, +- browser_networks: BrowserNetworkGroup, +-} +- +-#[wasm_bindgen(js_class = WasmerCore)] +-impl JsWasmer { +- #[wasm_bindgen(js_name = create)] +- pub fn create( +- options: JsValue, +- node_network: Option, +- node_cache: Option, +- ) -> Result { +- let options: ClientOptions = if options.is_null() || options.is_undefined() { +- ClientOptions::default() +- } else { +- serde_wasm_bindgen::from_value(options).map_err(js_error)? +- }; +- let parallelism = options +- .parallelism +- .map(|value| validate_usize("parallelism", value, 1)) +- .transpose()? +- .and_then(std::num::NonZeroUsize::new); +- let tasks = Arc::new(tasks::ThreadPool::new(parallelism)); +- let mut runtime = PluggableRuntime::new(Arc::clone(&tasks) as Arc<_>); +- runtime +- .set_tty(Arc::new(DefaultTty::default())) +- .set_module_cache(module_cache::SharedModuleCache::default()); +- if let Some(bridge) = node_network { +- runtime.set_networking_implementation(NodeNetworking::new(bridge)); +- } +- let config = WasmerConfig { +- output_bytes: options +- .output_bytes +- .map(|bytes| validate_usize("outputBytes", bytes, 0)) +- .transpose()? +- .unwrap_or(16 * 1024 * 1024), +- ..WasmerConfig::default() +- }; +- let cache = match options +- .cache +- .as_ref() +- .and_then(|cache| cache.mode.as_deref()) +- { +- Some("disabled" | "memory") => None, +- Some("node") => { +- let bridge = node_cache.as_ref().ok_or_else(|| { +- custom_error( +- "INITIALIZATION_ERROR", +- "the Node package cache bridge was not provided", +- ) +- })?; +- Some(Arc::new(HostPackageCache::node(bridge))) +- } +- Some("browser") | None => Some(Arc::new(HostPackageCache::browser( +- options +- .cache +- .as_ref() +- .and_then(|cache| cache.namespace.as_deref()), +- options +- .cache +- .as_ref() +- .and_then(|cache| cache.read_only) +- .unwrap_or(false), +- ))), +- Some(other) => { +- return Err(custom_error( +- "INVALID_ARGUMENT", +- &format!("unsupported cache mode `{other}`"), +- )); +- } +- }; +- let query_cache = cache.clone().map(|cache| -> Arc { cache }); +- let package_cache = cache.map(|cache| -> Arc { cache }); +- let inner = Wasmer::from_js_runtime(&config, runtime, query_cache, package_cache) +- .map_err(sdk_error)?; +- Ok(Self { +- inner, +- tasks, +- browser_networks: BrowserNetworkGroup::default(), +- }) +- } +- +- #[wasm_bindgen(js_name = createPackage)] +- pub async fn create_package(&self, definition: JsValue) -> Result { +- let definition: PackageDefinition = serde_wasm_bindgen::from_value(definition) +- .map_err(|error| custom_error("INVALID_ARGUMENT", &error.to_string()))?; +- self.inner +- .packages() +- .create(definition.into()) +- .await +- .map(|inner| JsPackage { inner }) +- .map_err(sdk_error) +- } +- +- #[wasm_bindgen(js_name = loadPackage)] +- pub async fn load_package(&self, specifier: String) -> Result { +- self.inner +- .packages() +- .load(specifier) +- .await +- .map(|inner| JsPackage { inner }) +- .map_err(sdk_error) +- } +- +- #[wasm_bindgen(js_name = loadPackageBytes)] +- pub async fn load_package_bytes(&self, bytes: Uint8Array) -> Result { +- self.inner +- .packages() +- .load(PackageSource::bytes(bytes.to_vec())) +- .await +- .map(|inner| JsPackage { inner }) +- .map_err(sdk_error) +- } +- +- #[wasm_bindgen(js_name = sandbox)] +- pub fn sandbox(&self) -> JsSandboxBuilder { +- JsSandboxBuilder { +- inner: Some(self.inner.sandboxes().create()), +- browser_http: None, +- browser_networks: self.browser_networks.clone(), +- } +- } +- +- pub async fn shutdown(&self) -> Result<(), JsValue> { +- self.browser_networks.close(); +- self.inner.shutdown().await.map_err(sdk_error)?; +- self.tasks +- .close_and_wait() +- .await +- .map_err(|error| custom_error("WORKER_FAILED", &error.to_string()))?; +- Ok(()) +- } +-} +- +-#[wasm_bindgen(js_name = PackageCore)] +-pub struct JsPackage { +- inner: Package, +-} +- +-#[wasm_bindgen(js_class = PackageCore)] +-impl JsPackage { +- #[wasm_bindgen(getter)] +- pub fn id(&self) -> String { +- self.inner.id() +- } +- +- #[wasm_bindgen(getter)] +- pub fn commands(&self) -> Vec { +- self.inner.commands() +- } +- +- #[wasm_bindgen(getter)] +- pub fn entrypoint(&self) -> Option { +- self.inner.entrypoint() +- } +- +- #[wasm_bindgen(js_name = hasCommand)] +- pub fn has_command(&self, name: String) -> bool { +- self.inner.command(name).is_ok() +- } +-} +- +-#[wasm_bindgen(js_name = SandboxBuilderCore)] +-pub struct JsSandboxBuilder { +- inner: Option, +- browser_http: Option, +- browser_networks: BrowserNetworkGroup, +-} +- +-#[wasm_bindgen(js_class = SandboxBuilderCore)] +-impl JsSandboxBuilder { +- pub fn package(&mut self, package: &JsPackage) -> Result<(), JsValue> { +- let builder = self.take()?.package(package.inner.clone()); +- self.inner = Some(builder); +- Ok(()) +- } +- +- pub fn file(&mut self, path: String, bytes: Uint8Array) -> Result<(), JsValue> { +- let builder = self.take()?.file(path, bytes.to_vec()); +- self.inner = Some(builder); +- Ok(()) +- } +- +- pub fn env(&mut self, key: String, value: String) -> Result<(), JsValue> { +- let builder = self.take()?.env(key, value); +- self.inner = Some(builder); +- Ok(()) +- } +- +- /// Experimental worker-only native directory mount. The embedder installs +- /// the synchronous `__wasmerHostFileSystem` bridge on every SDK worker. +- #[wasm_bindgen(js_name = mountHost)] +- pub fn mount_host( +- &mut self, +- path: String, +- mount_id: u32, +- read_only: bool, +- ) -> Result<(), JsValue> { +- let mode = if read_only { +- wasmer_sdk::MountMode::ReadOnly +- } else { +- wasmer_sdk::MountMode::ReadWrite +- }; +- self.inner = Some(self.take()?.mount( +- path, +- host_filesystem::HostFileSystem { mount_id }, +- mode, +- )); +- Ok(()) +- } +- +- /// Use an embedder filesystem as `/workspace`, including the SDK file API. +- #[wasm_bindgen(js_name = storageHost)] +- pub fn storage_host(&mut self, mount_id: u32) -> Result<(), JsValue> { +- self.inner = Some( +- self.take()? +- .storage(host_filesystem::HostFileSystem { mount_id }), +- ); +- Ok(()) +- } +- +- /// Configure guest networking from a stable mode string. +- pub fn network(&mut self, mode: String) -> Result<(), JsValue> { +- let builder = self.take()?; +- let builder = match mode.as_str() { +- "disabled" => { +- self.browser_http = None; +- builder.network(NetworkPolicy::Disabled) +- } +- "host" => { +- self.browser_http = None; +- builder.network(NetworkPolicy::Host) +- } +- "http" => { +- let networking = self.browser_networks.create(None); +- self.browser_http = Some(networking.request_handler()); +- builder.network_provider(Arc::new(networking)) +- } +- other => { +- return Err(custom_error( +- "CAPABILITY_UNAVAILABLE", +- &format!("unsupported network mode `{other}`"), +- )); +- } +- }; +- self.inner = Some(builder); +- Ok(()) +- } +- +- /// Configure browser HTTP ingress and WISP-backed TCP/DNS egress. +- #[wasm_bindgen(js_name = networkWisp)] +- pub fn network_wisp(&mut self, bridge: NodeNetworkBridge) -> Result<(), JsValue> { +- let builder = self.take()?; +- let networking = self +- .browser_networks +- .create(Some(NodeNetworking::new(bridge))); +- self.browser_http = Some(networking.request_handler()); +- self.inner = Some(builder.network_provider(Arc::new(networking))); +- Ok(()) +- } +- +- /// Disable automatic localhost discovery before adding explicit peers. +- #[wasm_bindgen(js_name = restrictNetworkPeers)] +- pub fn restrict_network_peers(&self) -> Result<(), JsValue> { +- self.browser_http +- .as_ref() +- .ok_or_else(|| { +- custom_error( +- "CAPABILITY_UNAVAILABLE", +- "network peers require browser networking", +- ) +- })? +- .restrict_peers() +- .map_err(js_error) +- } +- +- #[wasm_bindgen(js_name = addNetworkPeer)] +- pub fn add_network_peer(&self, peer: &JsSandbox) -> Result<(), JsValue> { +- let own = self.browser_http.as_ref().ok_or_else(|| { +- custom_error( +- "CAPABILITY_UNAVAILABLE", +- "network peers require browser networking", +- ) +- })?; +- let peer = peer.browser_http.as_ref().ok_or_else(|| { +- custom_error( +- "CAPABILITY_UNAVAILABLE", +- "the peer requires browser networking", +- ) +- })?; +- own.add_peer(peer).map_err(js_error) +- } +- +- pub async fn start(mut self) -> Result { +- let builder = self.take()?; +- builder +- .await +- .map(|inner| JsSandbox { +- inner, +- browser_http: self.browser_http, +- }) +- .map_err(sdk_error) +- } +-} +- +-impl JsSandboxBuilder { +- fn take(&mut self) -> Result { +- self.inner +- .take() +- .ok_or_else(|| custom_error("TARGET_ERROR", "sandbox builder was already consumed")) +- } +-} +- +-#[derive(serde::Serialize)] +-#[serde(rename_all = "camelCase")] +-struct JsFileStat { +- kind: &'static str, +- size: f64, +-} +- +-#[derive(serde::Serialize)] +-#[serde(rename_all = "camelCase")] +-struct JsDirEntry { +- name: String, +- kind: &'static str, +- size: f64, +-} +- +-fn stat_parts(metadata: &wasmer_sdk::FileMetadata) -> (&'static str, f64) { +- let kind = match metadata.file_type { +- wasmer_sdk::FileType::Directory => "directory", +- wasmer_sdk::FileType::File => "file", +- }; +- #[allow(clippy::cast_precision_loss)] +- (kind, metadata.len as f64) +-} +- +-#[wasm_bindgen(js_name = SandboxCore)] +-pub struct JsSandbox { +- inner: Sandbox, +- browser_http: Option, +-} +- +-#[wasm_bindgen(js_class = SandboxCore)] +-impl JsSandbox { +- pub fn command(&self, name: String) -> JsCommand { +- JsCommand { +- inner: Some(self.inner.command(name)), +- } +- } +- +- #[wasm_bindgen(js_name = commandPackage)] +- pub fn command_package(&self, package: &JsPackage) -> JsCommand { +- JsCommand { +- inner: Some(self.inner.command(package.inner.clone())), +- } +- } +- +- /// A command explicitly qualified by its package, resolving name +- /// collisions between installed packages. +- #[wasm_bindgen(js_name = commandRef)] +- pub fn command_ref(&self, package: &JsPackage, name: String) -> Result { +- let reference = package.inner.command(name).map_err(sdk_error)?; +- Ok(JsCommand { +- inner: Some(self.inner.command(reference)), +- }) +- } +- +- #[wasm_bindgen(js_name = installPackage)] +- pub async fn install_package(&self, specifier: String) -> Result { +- self.inner +- .install_package(specifier) +- .await +- .map(|inner| JsPackage { inner }) +- .map_err(sdk_error) +- } +- +- #[wasm_bindgen(js_name = installPackageBytes)] +- pub async fn install_package_bytes(&self, bytes: Uint8Array) -> Result { +- self.inner +- .install_package(PackageSource::bytes(bytes.to_vec())) +- .await +- .map(|inner| JsPackage { inner }) +- .map_err(sdk_error) +- } +- +- #[wasm_bindgen(js_name = installPackageRef)] +- pub async fn install_package_ref(&self, package: &JsPackage) -> Result { +- self.inner +- .install_package(package.inner.clone()) +- .await +- .map(|inner| JsPackage { inner }) +- .map_err(sdk_error) +- } +- +- #[wasm_bindgen(js_name = writeFile)] +- pub async fn write_file(&self, path: String, bytes: Uint8Array) -> Result<(), JsValue> { +- self.inner +- .fs() +- .write(path, bytes.to_vec()) +- .await +- .map_err(sdk_error) +- } +- +- #[wasm_bindgen(js_name = readFile)] +- pub async fn read_file(&self, path: String) -> Result { +- self.inner +- .fs() +- .read(path) +- .await +- .map(|bytes| Uint8Array::from(bytes.as_slice())) +- .map_err(sdk_error) +- } +- +- pub fn mkdir(&self, path: String, recursive: bool) -> Result<(), JsValue> { +- if recursive { +- self.inner.fs().create_dir_all(path).map_err(sdk_error) +- } else { +- self.inner.fs().create_dir(path).map_err(sdk_error) +- } +- } +- +- #[wasm_bindgen(js_name = readDir)] +- pub fn read_dir(&self, path: String) -> Result { +- let entries: Vec = self +- .inner +- .fs() +- .read_dir(path) +- .map_err(sdk_error)? +- .into_iter() +- .map(|entry| { +- let (kind, size) = stat_parts(&entry.metadata); +- JsDirEntry { +- name: entry.name, +- kind, +- size, +- } +- }) +- .collect(); +- serde_wasm_bindgen::to_value(&entries).map_err(js_error) +- } +- +- pub fn stat(&self, path: String) -> Result { +- let metadata = self.inner.fs().stat(path).map_err(sdk_error)?; +- let (kind, size) = stat_parts(&metadata); +- serde_wasm_bindgen::to_value(&JsFileStat { kind, size }).map_err(js_error) +- } +- +- pub fn remove(&self, path: String, recursive: bool) -> Result<(), JsValue> { +- self.inner.fs().remove(path, recursive).map_err(sdk_error) +- } +- +- pub async fn rename(&self, from: String, to: String) -> Result<(), JsValue> { +- self.inner.fs().rename(from, to).await.map_err(sdk_error) +- } +- +- /// Wait until a guest TCP listener accepts connections on `port`. +- #[wasm_bindgen(js_name = waitForPort)] +- pub async fn wait_for_port(&self, port: f64, timeout_ms: f64) -> Result<(), JsValue> { +- let port = validate_integer("port", port, 1, u64::from(u16::MAX))? as u16; +- let timeout = validate_duration("timeoutMs", timeout_ms)?; +- self.inner +- .ports() +- .wait(port, timeout) +- .await +- .map_err(sdk_error) +- } +- +- /// Whether a browser HTTP ingress listener exists on `port`. +- #[wasm_bindgen(js_name = isHttpPortListening)] +- pub fn is_http_port_listening(&self, port: f64) -> Result { +- let port = validate_integer("port", port, 1, u64::from(u16::MAX))? as u16; +- let handler = self.browser_http.as_ref().ok_or_else(|| { +- custom_error( +- "CAPABILITY_UNAVAILABLE", +- "the sandbox was not created with browser HTTP networking", +- ) +- })?; +- Ok(handler.has_listener(port)) +- } +- +- /// Browser HTTP ingress ports currently owned by guest listeners. +- #[wasm_bindgen(js_name = httpListeningPorts)] +- pub fn http_listening_ports(&self) -> Result>, JsValue> { +- let handler = self.browser_http.as_ref().ok_or_else(|| { +- custom_error( +- "CAPABILITY_UNAVAILABLE", +- "the sandbox was not created with browser HTTP networking", +- ) +- })?; +- Ok(handler.listening_ports()) +- } +- +- /// Open a protocol-independent byte stream to a guest listener. +- #[wasm_bindgen(js_name = connectTcp)] +- pub fn connect_tcp(&self, port: f64) -> Result { +- let port = validate_integer("port", port, 1, u64::from(u16::MAX))? as u16; +- let handler = self.browser_http.as_ref().ok_or_else(|| { +- custom_error( +- "CAPABILITY_UNAVAILABLE", +- "the sandbox has no browser ingress network", +- ) +- })?; +- Ok(browser_tcp::JsTcpConnection::new( +- handler.connect(port).map_err(js_error)?, +- )) +- } +- +- /// Forward one structured HTTP request into a guest TCP listener. +- #[wasm_bindgen(js_name = handleHttpRequest)] +- pub async fn handle_http_request( +- &self, +- port: f64, +- method: String, +- path: String, +- headers: JsValue, +- body: Uint8Array, +- ) -> Result { +- let port = validate_integer("port", port, 1, u64::from(u16::MAX))? as u16; +- let handler = self.browser_http.as_ref().ok_or_else(|| { +- custom_error( +- "CAPABILITY_UNAVAILABLE", +- "the sandbox was not created with browser HTTP networking", +- ) +- })?; +- let headers: Vec<(String, String)> = +- serde_wasm_bindgen::from_value(headers).map_err(js_error)?; +- let mut request = HttpRequest::builder() +- .method(method.as_str()) +- .uri(path.as_str()); +- let request_headers = request +- .headers_mut() +- .ok_or_else(|| custom_error("INVALID_ARGUMENT", "failed to construct HTTP request"))?; +- for (name, value) in headers { +- let name = http::HeaderName::from_bytes(name.as_bytes()).map_err(|_| { +- custom_error("INVALID_ARGUMENT", &format!("invalid HTTP header `{name}`")) +- })?; +- let value = http::HeaderValue::from_str(&value).map_err(|_| { +- custom_error( +- "INVALID_ARGUMENT", +- &format!("invalid value for HTTP header `{name}`"), +- ) +- })?; +- request_headers.append(name, value); +- } +- if !request_headers.contains_key(HOST) { +- request_headers.insert(HOST, http::HeaderValue::from_static("localhost")); +- } +- let response = handler +- .handle( +- request +- .body(Bytes::from(body.to_vec())) +- .map_err(|error| custom_error("INVALID_ARGUMENT", &error.to_string()))?, +- port, +- ) +- .await +- .map_err(|error| { +- custom_error( +- "EXECUTION_ERROR", +- &format!("guest HTTP request failed: {error}"), +- ) +- })?; +- Ok(JsHttpResponse::new(response)) +- } +- +- pub async fn close(&self) -> Result<(), JsValue> { +- if let Some(network) = &self.browser_http { +- network.close(); +- } +- self.inner.close().await.map_err(sdk_error) +- } +-} +- +-#[wasm_bindgen(js_name = HttpResponseCore)] +-pub struct JsHttpResponse { +- status: u16, +- status_text: String, +- headers: Vec<(String, String)>, +- body: Vec, +-} +- +-impl JsHttpResponse { +- fn new(response: http::Response) -> Self { +- let (parts, body) = response.into_parts(); +- Self { +- status: parts.status.as_u16(), +- status_text: parts +- .status +- .canonical_reason() +- .unwrap_or_default() +- .to_owned(), +- headers: parts +- .headers +- .iter() +- .filter_map(|(name, value)| { +- value +- .to_str() +- .ok() +- .map(|value| (name.as_str().to_owned(), value.to_owned())) +- }) +- .collect(), +- body: body.to_vec(), +- } +- } +-} +- +-#[wasm_bindgen(js_class = HttpResponseCore)] +-impl JsHttpResponse { +- #[wasm_bindgen(getter)] +- pub fn status(&self) -> u16 { +- self.status +- } +- +- #[wasm_bindgen(getter, js_name = statusText)] +- pub fn status_text(&self) -> String { +- self.status_text.clone() +- } +- +- #[wasm_bindgen(getter)] +- pub fn headers(&self) -> Result { +- serde_wasm_bindgen::to_value(&self.headers).map_err(js_error) +- } +- +- #[wasm_bindgen(getter)] +- pub fn body(&self) -> Uint8Array { +- Uint8Array::from(self.body.as_slice()) +- } +-} +- +-#[wasm_bindgen(js_name = CommandCore)] +-pub struct JsCommand { +- inner: Option, +-} +- +-#[wasm_bindgen(js_class = CommandCore)] +-impl JsCommand { +- pub fn args(&mut self, args: Vec) -> Result<(), JsValue> { +- self.inner_mut()?.args(args); +- Ok(()) +- } +- +- pub fn env(&mut self, key: String, value: String) -> Result<(), JsValue> { +- self.inner_mut()?.env(key, value); +- Ok(()) +- } +- +- #[wasm_bindgen(js_name = currentDir)] +- pub fn current_dir(&mut self, path: String) -> Result<(), JsValue> { +- self.inner_mut()?.current_dir(path); +- Ok(()) +- } +- +- pub fn input(&mut self, bytes: Uint8Array) -> Result<(), JsValue> { +- self.inner_mut()?.input(bytes.to_vec()); +- Ok(()) +- } +- +- #[wasm_bindgen(js_name = outputBytes)] +- pub fn output_bytes(&mut self, bytes: f64) -> Result<(), JsValue> { +- let bytes = validate_usize("outputBytes", bytes, 0)?; +- self.inner_mut()?.output_bytes(bytes); +- Ok(()) +- } +- +- #[wasm_bindgen(js_name = timeoutMs)] +- pub fn timeout_ms(&mut self, milliseconds: f64) -> Result<(), JsValue> { +- let timeout = validate_duration("timeoutMs", milliseconds)?; +- self.inner_mut()?.timeout(timeout); +- Ok(()) +- } +- +- /// Attach an interactive terminal with the given character dimensions. +- pub fn terminal(&mut self, columns: f64, rows: f64) -> Result<(), JsValue> { +- let columns = validate_integer("terminal.columns", columns, 1, u64::from(u32::MAX))?; +- let rows = validate_integer("terminal.rows", rows, 1, u64::from(u32::MAX))?; +- self.inner_mut()? +- .terminal(TerminalOptions::new(columns as u32, rows as u32)); +- Ok(()) +- } +- +- /// Live stdin mode for `spawn()`: `"pipe"` or `"closed"`. +- #[wasm_bindgen(js_name = stdinMode)] +- pub fn stdin_mode(&mut self, mode: String) -> Result<(), JsValue> { +- let stdio = match mode.as_str() { +- "pipe" => Stdio::Piped, +- "closed" => Stdio::Null, +- other => return Err(invalid_stdio_mode(other)), +- }; +- self.inner_mut()?.stdin(stdio); +- Ok(()) +- } +- +- /// Live stdout mode for `spawn()`: `"pipe"`, `"capture"`, or `"discard"`. +- #[wasm_bindgen(js_name = stdoutMode)] +- pub fn stdout_mode(&mut self, mode: String) -> Result<(), JsValue> { +- let stdio = parse_output_mode(&mode)?; +- self.inner_mut()?.stdout(stdio); +- Ok(()) +- } +- +- /// Live stderr mode for `spawn()`: `"pipe"`, `"capture"`, or `"discard"`. +- #[wasm_bindgen(js_name = stderrMode)] +- pub fn stderr_mode(&mut self, mode: String) -> Result<(), JsValue> { +- let stdio = parse_output_mode(&mode)?; +- self.inner_mut()?.stderr(stdio); +- Ok(()) +- } +- +- pub async fn run(mut self) -> Result { +- self.take()? +- .output() +- .await +- .map(|inner| JsOutput { inner }) +- .map_err(sdk_error) +- } +- +- pub async fn spawn(mut self) -> Result { +- let mut process = self.take()?.spawn().await.map_err(sdk_error)?; +- let handle = process.handle(); +- let stdin = process.take_stdin(); +- let stdout = process.take_stdout(); +- let stderr = process.take_stderr(); +- Ok(JsProcess { +- id: process.id(), +- handle, +- process: Mutex::new(process), +- stdin: Mutex::new(stdin), +- stdout: Mutex::new(stdout), +- stderr: Mutex::new(stderr), +- }) +- } +-} +- +-fn parse_output_mode(mode: &str) -> Result { +- match mode { +- "pipe" => Ok(Stdio::Piped), +- "capture" => Ok(Stdio::Capture), +- "discard" => Ok(Stdio::Null), +- other => Err(invalid_stdio_mode(other)), +- } +-} +- +-fn invalid_stdio_mode(mode: &str) -> JsValue { +- custom_error("TARGET_ERROR", &format!("unsupported stdio mode `{mode}`")) +-} +- +-impl JsCommand { +- fn inner_mut(&mut self) -> Result<&mut Command, JsValue> { +- self.inner +- .as_mut() +- .ok_or_else(|| custom_error("TARGET_ERROR", "command was already consumed")) +- } +- +- fn take(&mut self) -> Result { +- self.inner +- .take() +- .ok_or_else(|| custom_error("TARGET_ERROR", "command was already consumed")) +- } +-} +- +-#[wasm_bindgen(js_name = OutputCore)] +-pub struct JsOutput { +- inner: Output, +-} +- +-#[wasm_bindgen(js_class = OutputCore)] +-impl JsOutput { +- #[wasm_bindgen(getter, js_name = exitCode)] +- pub fn exit_code(&self) -> i32 { +- self.inner.status.code() +- } +- +- #[wasm_bindgen(getter)] +- pub fn ok(&self) -> bool { +- self.inner.ok() +- } +- +- /// Why the process stopped: `"exited"`, `"terminated"`, or `"timeout"`. +- #[wasm_bindgen(getter)] +- pub fn reason(&self) -> String { +- self.inner.reason.as_str().to_owned() +- } +- +- #[wasm_bindgen(getter)] +- pub fn stdout(&self) -> Uint8Array { +- Uint8Array::from(self.inner.stdout.bytes()) +- } +- +- #[wasm_bindgen(getter)] +- pub fn stderr(&self) -> Uint8Array { +- Uint8Array::from(self.inner.stderr.bytes()) +- } +- +- #[wasm_bindgen(getter, js_name = stdoutTruncated)] +- pub fn stdout_truncated(&self) -> bool { +- self.inner.stdout.truncated() +- } +- +- #[wasm_bindgen(getter, js_name = stderrTruncated)] +- pub fn stderr_truncated(&self) -> bool { +- self.inner.stderr.truncated() +- } +-} +- +-#[wasm_bindgen(js_name = ProcessCore)] +-pub struct JsProcess { +- id: u32, +- /// Signals never contend with a concurrent `wait()`: they go through the +- /// lock-free handle, not the mutex that `wait()` holds. +- handle: ProcessHandle, +- process: Mutex, +- stdin: Mutex>, +- stdout: Mutex>, +- stderr: Mutex>, +-} +- +-#[wasm_bindgen(js_class = ProcessCore)] +-impl JsProcess { +- #[wasm_bindgen(getter)] +- pub fn id(&self) -> u32 { +- self.id +- } +- +- #[wasm_bindgen(js_name = writeStdin)] +- pub async fn write_stdin(&self, bytes: Uint8Array) -> Result<(), JsValue> { +- let mut stdin = self.stdin.lock().await; +- stdin +- .as_mut() +- .ok_or_else(|| custom_error("TARGET_ERROR", "stdin is closed"))? +- .write_all(&bytes.to_vec()) +- .await +- .map_err(js_error) +- } +- +- #[wasm_bindgen(js_name = closeStdin)] +- pub async fn close_stdin(&self) -> Result<(), JsValue> { +- let mut stdin = self.stdin.lock().await.take(); +- if let Some(stdin) = stdin.as_mut() { +- stdin.shutdown().await.map_err(js_error)?; +- } +- Ok(()) +- } +- +- #[wasm_bindgen(js_name = readStdout)] +- pub async fn read_stdout(&self, max_bytes: f64) -> Result { +- let max_bytes = validate_usize("maxBytes", max_bytes, 1)?; +- read_stream(&self.stdout, max_bytes).await +- } +- +- #[wasm_bindgen(js_name = readStderr)] +- pub async fn read_stderr(&self, max_bytes: f64) -> Result { +- let max_bytes = validate_usize("maxBytes", max_bytes, 1)?; +- read_stream(&self.stderr, max_bytes).await +- } +- +- pub async fn wait(&self) -> Result { +- self.process +- .lock() +- .await +- .wait() +- .await +- .map(|inner| JsOutput { inner }) +- .map_err(sdk_error) +- } +- +- pub async fn terminate(&self, grace_ms: f64) -> Result<(), JsValue> { +- let grace = validate_duration("gracePeriodMs", grace_ms)?; +- self.handle.terminate(grace).await; +- Ok(()) +- } +- +- pub fn kill(&self) { +- self.handle.kill(); +- } +- +- #[wasm_bindgen(js_name = resizeTerminal)] +- pub fn resize_terminal(&self, columns: f64, rows: f64) -> Result<(), JsValue> { +- let columns = validate_integer("terminal.columns", columns, 1, u64::from(u32::MAX))?; +- let rows = validate_integer("terminal.rows", rows, 1, u64::from(u32::MAX))?; +- self.handle +- .resize_terminal(columns as u32, rows as u32) +- .map_err(sdk_error) +- } +-} +- +-async fn read_stream(stream: &Mutex>, max_bytes: usize) -> Result +-where +- S: tokio::io::AsyncRead + Unpin, +-{ +- let mut stream = stream.lock().await; +- let Some(reader) = stream.as_mut() else { +- return Ok(JsValue::NULL); +- }; +- let mut bytes = vec![0_u8; max_bytes.max(1)]; +- let length = reader.read(&mut bytes).await.map_err(js_error)?; +- if length == 0 { +- stream.take(); +- return Ok(JsValue::NULL); +- } +- bytes.truncate(length); +- Ok(Uint8Array::from(bytes.as_slice()).into()) +-} +- +-/// Convert an SDK error into a JavaScript error carrying its provisional code. +-fn sdk_error(error: wasmer_sdk::Error) -> JsValue { +- custom_error(error.code(), &error.to_string()) +-} +- +-/// A JavaScript error with the SDK name and a machine-readable code. +-fn custom_error(code: &str, message: &str) -> JsValue { +- let error = js_sys::Error::new(message); +- error.set_name("WasmerError"); +- let _ = js_sys::Reflect::set(&error, &JsValue::from_str("code"), &JsValue::from_str(code)); +- error.into() +-} +- +-/// Fallback for non-SDK failures (stream I/O, serialization). +-fn js_error(error: impl std::fmt::Display) -> JsValue { +- custom_error("TARGET_ERROR", &error.to_string()) +-} +- +-fn validate_duration(name: &str, value: f64) -> Result { +- validate_integer(name, value, 0, MAX_SAFE_INTEGER).map(Duration::from_millis) +-} +- +-fn validate_usize(name: &str, value: f64, minimum: u64) -> Result { +- let value = validate_integer(name, value, minimum, MAX_WASM32_SIZE)?; +- usize::try_from(value).map_err(|_| invalid_numeric_argument(name, minimum, MAX_WASM32_SIZE)) +-} +- +-fn validate_integer(name: &str, value: f64, minimum: u64, maximum: u64) -> Result { +- #[allow(clippy::cast_precision_loss)] +- let minimum_f64 = minimum as f64; +- #[allow(clippy::cast_precision_loss)] +- let maximum_f64 = maximum as f64; +- if !value.is_finite() || value.fract() != 0.0 || value < minimum_f64 || value > maximum_f64 { +- return Err(invalid_numeric_argument(name, minimum, maximum)); +- } +- Ok(value as u64) +-} +- +-fn invalid_numeric_argument(name: &str, minimum: u64, maximum: u64) -> JsValue { +- custom_error( +- "INVALID_ARGUMENT", +- &format!("`{name}` must be an integer between {minimum} and {maximum}, inclusive"), +- ) +-} +diff --git a/js/scripts/build-wasm.mjs b/js/scripts/build-wasm.mjs +index 19d210e..51b4cba 100644 +--- a/js/scripts/build-wasm.mjs ++++ b/js/scripts/build-wasm.mjs +@@ -6,44 +6,17 @@ import { + renameSync, + writeFileSync, + } from "node:fs"; +-import { homedir } from "node:os"; + import { dirname, join, resolve } from "node:path"; + import { fileURLToPath } from "node:url"; + + const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + const workspaceRoot = resolve(packageRoot, ".."); + const executableSuffix = process.platform === "win32" ? ".exe" : ""; +-const wasmBindgen = +- process.env.WASM_BINDGEN ?? +- join( +- process.env.CARGO_HOME ?? join(homedir(), ".cargo"), +- "bin", +- `wasm-bindgen${executableSuffix}`, +- ); + const wasmOpt = + process.env.WASM_OPT ?? + join(packageRoot, "node_modules", ".bin", `wasm-opt${executableSuffix}`); + const wasmOutput = join(packageRoot, "pkg", "wasmer_sdk_js_bg.wasm"); + const optimizedWasmOutput = `${wasmOutput}.optimized`; +-const localWasmer = process.env.WASMER_REPO; +-const localWasmerPatches = localWasmer +- ? [ +- ["virtual-fs", "lib/virtual-fs"], +- ["virtual-mio", "lib/virtual-io"], +- ["virtual-net", "lib/virtual-net"], +- ["wasmer", "lib/api"], +- ["wasmer-c-api-imports", "lib/c-api-imports"], +- ["wasmer-config", "lib/config"], +- ["wasmer-napi", "lib/napi"], +- ["wasmer-package", "lib/package"], +- ["wasmer-types", "lib/types"], +- ["wasmer-wasix", "lib/wasix"], +- ["wasmer-wasix-types", "lib/wasi-types"], +- ].flatMap(([name, path]) => [ +- "--config", +- `patch.crates-io.${name}.path=${JSON.stringify(join(localWasmer, path))}`, +- ]) +- : []; + + const rustflags = [ + "-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals", +@@ -59,19 +32,12 @@ const rustflags = [ + ].join(" "); + + run( +- "cargo", ++ "wasm-pack", + [ +- "+nightly", +- "build", +- "-Z", +- "build-std=std,panic_abort", +- ...(localWasmer ? [] : ["--locked"]), +- ...localWasmerPatches, +- "-p", +- "wasmer-sdk-js", +- "--target", +- "wasm32-unknown-unknown", +- "--release", ++ "build", join(packageRoot, "bindgen"), ++ "--release", "--target=web", "--weak-refs", "--no-pack", ++ "--out-dir", join(packageRoot, "pkg"), "--out-name", "wasmer_sdk_js", ++ "--", "--locked", "-Z", "build-std=std,panic_abort", + ], + { + RUSTFLAGS: rustflags, +@@ -80,28 +46,12 @@ run( + CARGO_PROFILE_RELEASE_OPT_LEVEL: "3", + }, + ); +- +-run(wasmBindgen, [ +- join( +- workspaceRoot, +- "target/wasm32-unknown-unknown/release/wasmer_sdk_js.wasm", +- ), +- "--out-dir", +- join(packageRoot, "pkg"), +- "--out-name", +- "wasmer_sdk_js", +- "--target", +- "web", +- "--typescript", +- "--remove-name-section", +- "--remove-producers-section", +-]); + vendorWasmBindgenDependencies(join(packageRoot, "pkg", "snippets")); + patchMemory32Glue(join(packageRoot, "pkg", "wasmer_sdk_js.js")); + + run(wasmOpt, [ + wasmOutput, +- "-Oz", ++ "-O3", + "--enable-bulk-memory", + "--enable-threads", + "--enable-mutable-globals", +@@ -145,7 +95,7 @@ function vendorWasmBindgenDependencies(snippetsRoot) { + }; + + visit(snippetsRoot); +- if (patchedImports !== 1) { ++ if (patchedImports > 1) { + throw new Error( + `expected to vendor one wasm-bindgen acorn import, patched ${patchedImports}`, + ); +@@ -172,9 +122,9 @@ function patchMemory32Glue(path) { + return `${prefix}(arg0 >>> 0)${suffix}`; + }, + ); +- if (viewReplacements !== 3) { ++ if (viewReplacements !== 2) { + throw new Error( +- `expected to patch 3 wasm-bindgen memory views, patched ${viewReplacements}`, ++ `expected to patch 2 wasm-bindgen memory views, patched ${viewReplacements}`, + ); + } + signedReturnPointer.lastIndex = 0; diff --git a/src/wasix/browser-host/patches/0008-wasmer-instantiate-js-modules-async.patch b/src/wasix/browser-host/patches/0041-wasmer-instantiate-browser-modules-async.patch similarity index 53% rename from src/wasix/browser-host/patches/0008-wasmer-instantiate-js-modules-async.patch rename to src/wasix/browser-host/patches/0041-wasmer-instantiate-browser-modules-async.patch index 5d96b7765..427277f1e 100644 --- a/src/wasix/browser-host/patches/0008-wasmer-instantiate-js-modules-async.patch +++ b/src/wasix/browser-host/patches/0041-wasmer-instantiate-browser-modules-async.patch @@ -1,31 +1,18 @@ From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer: instantiate JavaScript modules asynchronously +Date: Sun, 4 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Instantiate large browser modules asynchronously -Expose asynchronous module instantiation for the JavaScript backend. Large -PostgreSQL modules exceed browser main-thread synchronous instantiation limits; -non-JavaScript backends keep their existing instantiation semantics. This is -a general engine API addition suitable for a separate upstream proposal. +WebAssembly.instantiate avoids the synchronous main-realm size limit. Share +import validation and retain table-function annotations for indirect calls. +Enabled non-JavaScript backends keep their synchronous instantiation. -diff --git a/Cargo.toml b/Cargo.toml ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -363,6 +363,9 @@ - [target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] - version = "0.2.100" - -+[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen-futures] -+version = "0.4" -+ - [target.'cfg(target_arch = "wasm32")'.dependencies.wasmer-compiler] - version = "=6.1.0" - -diff --git a/src/backend/js/entities/instance.rs b/src/backend/js/entities/instance.rs ---- a/src/backend/js/entities/instance.rs -+++ b/src/backend/js/entities/instance.rs +diff --git a/lib/api/src/backend/js/entities/instance.rs b/lib/api/src/backend/js/entities/instance.rs +index 22a7a71..e847fd4 100644 +--- a/lib/api/src/backend/js/entities/instance.rs ++++ b/lib/api/src/backend/js/entities/instance.rs @@ -19,6 +19,21 @@ pub struct Instance { // unsafe impl Send for Instance {} - + impl Instance { + #[cfg(target_arch = "wasm32")] + pub(crate) async fn new_async( @@ -45,38 +32,20 @@ diff --git a/src/backend/js/entities/instance.rs b/src/backend/js/entities/insta pub(crate) fn new( mut store: &mut impl AsStoreMut, module: &Module, -@@ -29,7 +44,7 @@ impl Instance { - .instantiate(&mut store, imports) - .map_err(|e| InstantiationError::Start(e))?; - -- Self::from_module_and_instance(store, &module, instance) -+ Self::from_module_and_instance(store, module, instance) - } - - pub(crate) fn new_by_index( -diff --git a/src/backend/js/entities/module.rs b/src/backend/js/entities/module.rs ---- a/src/backend/js/entities/module.rs -+++ b/src/backend/js/entities/module.rs -@@ -3,7 +3,11 @@ use crate::backend::js::vm::{VMExtern, VMInstance}; - use bytes::Bytes; - use js_sys::{Reflect, Uint8Array, WebAssembly}; - use tracing::{debug, warn}; --use wasm_bindgen::{prelude::*, JsValue}; -+use wasm_bindgen::{prelude::*, JsValue}; -+#[cfg(target_arch = "wasm32")] -+use wasm_bindgen::JsCast; -+#[cfg(target_arch = "wasm32")] -+use wasm_bindgen_futures::JsFuture; - use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ExternType, FunctionType, - GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, Mutability, Pages, -@@ -139,6 +143,32 @@ impl Module { +diff --git a/lib/api/src/backend/js/entities/module.rs b/lib/api/src/backend/js/entities/module.rs +index ce0884d..c5e8f2e 100644 +--- a/lib/api/src/backend/js/entities/module.rs ++++ b/lib/api/src/backend/js/entities/module.rs +@@ -230,6 +230,38 @@ impl Module { store: &mut impl AsStoreMut, imports: &Imports, ) -> Result { + let (imports_object, _import_externs) = self.imports_object(store, imports)?; -+ Ok(WebAssembly::Instance::new(&self.module, &imports_object) -+ .map_err(|e: JsValue| -> RuntimeError { e.into() })?) ++ let instance = WebAssembly::Instance::new(&self.local_module(), &imports_object) ++ .map_err(|e: JsValue| -> RuntimeError { e.into() })?; ++ #[cfg(feature = "wasm-types-polyfill")] ++ self.annotate_table_functions(store, imports, &instance); ++ Ok(instance) + } + + #[cfg(target_arch = "wasm32")] @@ -87,12 +56,15 @@ diff --git a/src/backend/js/entities/module.rs b/src/backend/js/entities/module. + ) -> Result { + let (imports_object, _import_externs) = self.imports_object(store, imports)?; + let instance = JsFuture::from(WebAssembly::instantiate_module( -+ &self.module, ++ &self.local_module(), + &imports_object, + )) + .await + .map_err(|e: JsValue| -> RuntimeError { e.into() })?; -+ Ok(instance.unchecked_into()) ++ let instance = instance.unchecked_into(); ++ #[cfg(feature = "wasm-types-polyfill")] ++ self.annotate_table_functions(store, imports, &instance); ++ Ok(instance) + } + + fn imports_object( @@ -103,22 +75,26 @@ diff --git a/src/backend/js/entities/module.rs b/src/backend/js/entities/module. // Ensure all imports come from the same store. if imports .into_iter() -@@ -205,8 +235,7 @@ impl Module { +@@ -296,11 +328,7 @@ impl Module { // in case the import is not found, the JS Wasm VM will handle // the error for us, so we don't need to handle it } -- Ok(WebAssembly::Instance::new(&self.module, &imports_object) -- .map_err(|e: JsValue| -> RuntimeError { e.into() })?) +- let instance = WebAssembly::Instance::new(&self.local_module(), &imports_object) +- .map_err(|e: JsValue| -> RuntimeError { e.into() })?; +- #[cfg(feature = "wasm-types-polyfill")] +- self.annotate_table_functions(store, imports, &instance); +- Ok(instance) + Ok((imports_object, import_externs)) } - - pub fn name(&self) -> Option<&str> { -diff --git a/src/entities/instance.rs b/src/entities/instance.rs ---- a/src/entities/instance.rs -+++ b/src/entities/instance.rs -@@ -20,6 +20,59 @@ pub struct Instance { + + #[cfg(feature = "wasm-types-polyfill")] +diff --git a/lib/api/src/entities/instance.rs b/lib/api/src/entities/instance.rs +index edea32f..ae5033e 100644 +--- a/lib/api/src/entities/instance.rs ++++ b/lib/api/src/entities/instance.rs +@@ -20,6 +20,44 @@ pub struct Instance { } - + impl Instance { + /// Asynchronously creates a new JavaScript-backed WebAssembly instance. + /// @@ -138,16 +114,6 @@ diff --git a/src/entities/instance.rs b/src/entities/instance.rs + let (i, e) = crate::backend::sys::instance::Instance::new(store, module, imports)?; + (crate::BackendInstance::Sys(i), e) + } -+ #[cfg(feature = "wamr")] -+ crate::BackendStore::Wamr(_) => { -+ let (i, e) = crate::backend::wamr::instance::Instance::new(store, module, imports)?; -+ (crate::BackendInstance::Wamr(i), e) -+ } -+ #[cfg(feature = "wasmi")] -+ crate::BackendStore::Wasmi(_) => { -+ let (i, e) = crate::backend::wasmi::instance::Instance::new(store, module, imports)?; -+ (crate::BackendInstance::Wasmi(i), e) -+ } + #[cfg(feature = "v8")] + crate::BackendStore::V8(_) => { + let (i, e) = crate::backend::v8::instance::Instance::new(store, module, imports)?; @@ -159,11 +125,6 @@ diff --git a/src/entities/instance.rs b/src/entities/instance.rs + .await?; + (crate::BackendInstance::Js(i), e) + } -+ #[cfg(feature = "jsc")] -+ crate::BackendStore::Jsc(_) => { -+ let (i, e) = crate::backend::jsc::instance::Instance::new(store, module, imports)?; -+ (crate::BackendInstance::Jsc(i), e) -+ } + }; + + Ok(Self { diff --git a/src/wasix/browser-host/patches/0042-wasmer-wasix-browser-host-policy.patch b/src/wasix/browser-host/patches/0042-wasmer-wasix-browser-host-policy.patch new file mode 100644 index 000000000..524e8ff65 --- /dev/null +++ b/src/wasix/browser-host/patches/0042-wasmer-wasix-browser-host-policy.patch @@ -0,0 +1,629 @@ +From: Sid Jain +Date: Sun, 4 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Preserve typed browser execution and IO policies + +Embedded PostgreSQL has one execution realm and must reject guest process or +thread spawning instead of deadlocking the caller. Keep this per instance, +with canonical WASIX behavior as the default. Synchronous filesystems may +skip the close-time flush barrier; explicit fsync/fdatasync remain intact. +Propagate asynchronous main instantiation through both static and dynamic +linking without bypassing upstream runtime hooks. Browser clock optimizations +are installed by the embedder through those hooks, not by this policy. + +diff --git a/lib/wasix/src/capabilities.rs b/lib/wasix/src/capabilities.rs +index b95f5e5..6dab333 100644 +--- a/lib/wasix/src/capabilities.rs ++++ b/lib/wasix/src/capabilities.rs +@@ -2,6 +2,53 @@ use std::time::Duration; + + use crate::http::HttpClientCapabilityV1; + ++/// Defines how much guest-controlled execution a WASI environment permits. ++#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] ++pub enum WasiGuestExecutionMode { ++ /// Retain the normal WASIX process and thread behavior. ++ #[default] ++ General, ++ /// Keep the initial program as the environment's only process and thread. ++ SingleProgram, ++} ++ ++/// Describes whether a filesystem needs WASIX to flush before closing a file. ++#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] ++pub enum WasiFdClosePolicy { ++ /// Preserve the upstream flush-before-close behavior. ++ #[default] ++ FlushBeforeClose, ++ /// Writes complete synchronously, so POSIX close must not become fsync. ++ WritesCompleteSynchronously, ++} ++ ++/// Immutable host-selected execution policy for one WASI environment. ++/// ++/// This policy is deliberately separate from guest environment variables and ++/// from mergeable [`Capabilities`]. The builder fixes it before instantiation. ++#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] ++pub struct WasiHostPolicy { ++ guest_execution: WasiGuestExecutionMode, ++ fd_close: WasiFdClosePolicy, ++} ++ ++impl WasiHostPolicy { ++ pub const fn new(guest_execution: WasiGuestExecutionMode, fd_close: WasiFdClosePolicy) -> Self { ++ Self { ++ guest_execution, ++ fd_close, ++ } ++ } ++ ++ pub const fn guest_execution(self) -> WasiGuestExecutionMode { ++ self.guest_execution ++ } ++ ++ pub const fn fd_close(self) -> WasiFdClosePolicy { ++ self.fd_close ++ } ++} ++ + /// Defines capabilities for a Wasi environment. + #[derive(Clone, Debug, PartialEq, Eq, Hash)] + pub struct Capabilities { +diff --git a/lib/wasix/src/lib.rs b/lib/wasix/src/lib.rs +index a4df279..fd33c10 100644 +--- a/lib/wasix/src/lib.rs ++++ b/lib/wasix/src/lib.rs +@@ -102,8 +102,22 @@ pub use virtual_net::{ + host::{LocalNetworking, LocalTcpListener, LocalTcpStream, LocalUdpSocket}, + io_err_into_net_error, + }; ++#[cfg(all(feature = "js", target_arch = "wasm32"))] ++use wasm_bindgen::JsValue; + use wasmer_wasix_types::wasi::{Errno, ExitCode}; + ++/// JavaScript memory backing this environment, for same-realm host adapters. ++#[cfg(all(feature = "js", target_arch = "wasm32"))] ++pub fn oliphaunt_direct_memory( ++ env: &WasiFunctionEnv, ++ store: &impl wasmer::AsStoreRef, ++) -> Option { ++ use wasmer::js::AsJs; ++ env.data(store) ++ .try_memory() ++ .map(|memory| memory.as_jsvalue(store)) ++} ++ + pub use crate::{ + fs::{Fd, VIRTUAL_ROOT_FD, WasiFs, WasiInodes, default_fs_backing}, + os::{ +@@ -909,12 +923,8 @@ fn mem_error_to_wasi(err: MemoryAccessError) -> Errno { + /// immediately. + pub(crate) fn block_in_place(thunk: impl FnOnce() -> Ret) -> Ret { + cfg_select! { +- feature = "sys-thread" => { +- tokio::task::block_in_place(thunk) +- } +- _ => { +- thunk() +- } ++ feature = "sys-thread" => tokio::task::block_in_place(thunk), ++ _ => thunk(), + } + } + +@@ -929,12 +939,8 @@ where + R: Send + 'static, + { + cfg_select! { +- target_arch = "wasm32" => { +- Ok(block_in_place(f)) +- } +- _ => { +- tokio::task::spawn_blocking(f).await +- } ++ target_arch = "wasm32" => Ok(block_in_place(f)), ++ _ => tokio::task::spawn_blocking(f).await, + } + } + +diff --git a/lib/wasix/src/os/task/control_plane.rs b/lib/wasix/src/os/task/control_plane.rs +index 18e8ada..ae01e63 100644 +--- a/lib/wasix/src/os/task/control_plane.rs ++++ b/lib/wasix/src/os/task/control_plane.rs +@@ -120,7 +120,7 @@ impl WasiControlPlane { + pub(crate) fn register_task(&self) -> Result { + let count = self.state.task_count.fetch_add(1, Ordering::SeqCst); + if let Some(max) = self.state.config.max_task_count +- && count > max ++ && count >= max + { + self.state.task_count.fetch_sub(1, Ordering::SeqCst); + return Err(ControlPlaneError::TaskLimitReached { max: count }); +@@ -234,6 +234,11 @@ mod tests { + .new_thread(WasiMemoryLayout::default(), ThreadStartType::MainThread) + .unwrap(); + ++ assert_eq!( ++ p1.new_thread(WasiMemoryLayout::default(), ThreadStartType::MainThread) ++ .err(), ++ Some(ControlPlaneError::TaskLimitReached { max: 2 }) ++ ); + assert_eq!( + p.new_process(ModuleHash::random()).unwrap_err(), + ControlPlaneError::TaskLimitReached { max: 2 } +diff --git a/lib/wasix/src/state/builder.rs b/lib/wasix/src/state/builder.rs +index d0416f9..9139d52 100644 +--- a/lib/wasix/src/state/builder.rs ++++ b/lib/wasix/src/state/builder.rs +@@ -6,6 +6,7 @@ use std::{ + sync::Arc, + }; + ++use futures::FutureExt; + use rand::RngExt; + use thiserror::Error; + use virtual_fs::{ +@@ -19,7 +20,7 @@ use crate::journal::{DynJournal, DynReadableJournal, SnapshotTrigger}; + use crate::{ + Runtime, WasiEnv, WasiFunctionEnv, WasiRuntimeError, WasiThreadError, + bin_factory::{BinFactory, BinaryPackage}, +- capabilities::Capabilities, ++ capabilities::{Capabilities, WasiGuestExecutionMode, WasiHostPolicy}, + fs::{WasiFs, WasiFsRoot, WasiInodes}, + os::task::control_plane::{ControlPlaneConfig, ControlPlaneError, WasiControlPlane}, + os::{TtyBridge, command::VirtualCommand}, +@@ -88,6 +89,7 @@ pub struct WasiEnvBuilder { + pub(super) builtin_commands: Vec<(String, Arc)>, + + pub(super) capabilities: Capabilities, ++ pub(super) host_policy: WasiHostPolicy, + + #[cfg(feature = "journal")] + pub(super) snapshot_on: Vec, +@@ -843,6 +845,11 @@ impl WasiEnvBuilder { + &mut self.capabilities + } + ++ /// Configure execution and filesystem guarantees supplied by the host. ++ pub fn set_host_policy(&mut self, policy: WasiHostPolicy) { ++ self.host_policy = policy; ++ } ++ + pub fn set_capabilities(&mut self, capabilities: Capabilities) { + self.capabilities = capabilities; + } +@@ -1069,8 +1076,15 @@ impl WasiEnvBuilder { + + let capabilities = self.capabilities; + ++ let host_policy = self.host_policy; + let plane_config = ControlPlaneConfig { +- max_task_count: capabilities.threading.max_threads, ++ max_task_count: if host_policy.guest_execution() ++ == WasiGuestExecutionMode::SingleProgram ++ { ++ Some(1) ++ } else { ++ capabilities.threading.max_threads ++ }, + enable_asynchronous_threading: capabilities.threading.enable_asynchronous_threading, + enable_exponential_cpu_backoff: capabilities.threading.enable_exponential_cpu_backoff, + }; +@@ -1085,6 +1099,7 @@ impl WasiEnvBuilder { + control_plane, + bin_factory, + capabilities, ++ host_policy, + memory_ty: None, + process: None, + thread: None, +@@ -1149,6 +1164,43 @@ impl WasiEnvBuilder { + module: Module, + module_hash: ModuleHash, + store: &mut impl AsStoreMut, ++ ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { ++ self.instantiate_ext_inner(module, module_hash, store, false) ++ .now_or_never() ++ .expect("synchronous WASI builder instantiation must not yield") ++ } ++ ++ /// Instantiate a JavaScript-backed module through the browser's ++ /// asynchronous `WebAssembly.instantiate()` path. ++ #[cfg(all(feature = "js", target_arch = "wasm32"))] ++ #[allow(clippy::result_large_err)] ++ pub async fn instantiate_async( ++ self, ++ module: Module, ++ store: &mut impl AsStoreMut, ++ ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { ++ self.instantiate_ext_async(module, ModuleHash::random(), store) ++ .await ++ } ++ ++ #[cfg(all(feature = "js", target_arch = "wasm32"))] ++ #[allow(clippy::result_large_err)] ++ pub async fn instantiate_ext_async( ++ self, ++ module: Module, ++ module_hash: ModuleHash, ++ store: &mut impl AsStoreMut, ++ ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { ++ self.instantiate_ext_inner(module, module_hash, store, true) ++ .await ++ } ++ ++ async fn instantiate_ext_inner( ++ self, ++ module: Module, ++ module_hash: ModuleHash, ++ store: &mut impl AsStoreMut, ++ async_instantiation: bool, + ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { + let init = self.build_init()?; + let call_init = init.call_initialize; +@@ -1162,7 +1214,17 @@ impl WasiEnvBuilder { + .map(|ty| wasmer::Memory::new(store, ty)) + .transpose() + .map_err(WasiThreadError::MemoryCreateFailed)?; +- Ok(env.instantiate(module, store, memory, true, call_init, None)?) ++ Ok(env ++ .instantiate_inner( ++ module, ++ store, ++ memory, ++ true, ++ call_init, ++ None, ++ async_instantiation, ++ ) ++ .await?) + } + } + +diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs +index 3eec737..2ea0a2c 100644 +--- a/lib/wasix/src/state/env.rs ++++ b/lib/wasix/src/state/env.rs +@@ -4,7 +4,7 @@ use crate::{ + Runtime, VirtualTaskManager, WasiControlPlane, WasiEnvBuilder, WasiError, WasiFunctionEnv, + WasiResult, WasiRuntimeError, WasiStateCreationError, WasiThreadError, WasiVFork, + bin_factory::{BinFactory, BinaryPackage, BinaryPackageCommand}, +- capabilities::Capabilities, ++ capabilities::{Capabilities, WasiHostPolicy}, + fs::{WasiFsRoot, WasiInodes}, + import_object_for_all_wasi_versions, + os::{ +@@ -18,7 +18,7 @@ use crate::{ + state::PreparedInstanceGroupData, + syscalls::platform_clock_time_get, + }; +-use futures::future::BoxFuture; ++use futures::{FutureExt, future::BoxFuture}; + use rand::RngExt; + use std::{ + collections::{HashMap, HashSet}, +@@ -104,6 +104,7 @@ pub struct WasiEnvInit { + pub mapped_commands: HashMap, + pub bin_factory: BinFactory, + pub capabilities: Capabilities, ++ pub(crate) host_policy: WasiHostPolicy, + + pub control_plane: WasiControlPlane, + pub memory_ty: Option, +@@ -162,6 +163,7 @@ impl WasiEnvInit { + mapped_commands: self.mapped_commands.clone(), + bin_factory: self.bin_factory.clone(), + capabilities: self.capabilities.clone(), ++ host_policy: self.host_policy, + control_plane: self.control_plane.clone(), + memory_ty: None, + process: None, +@@ -191,6 +193,8 @@ pub struct WasiEnv { + pub vfork: Option, + /// Seed used to rotate around the events returned by `poll_oneoff` + pub poll_seed: u64, ++ /// Immutable behavior selected by the host before instantiation. ++ pub(crate) host_policy: WasiHostPolicy, + /// Shared state of the WASI system. Manages all the data that the + /// executing WASI program can see. + pub(crate) state: Arc, +@@ -254,6 +258,7 @@ impl Clone for WasiEnv { + control_plane: self.control_plane.clone(), + process: self.process.clone(), + poll_seed: self.poll_seed, ++ host_policy: self.host_policy, + thread: self.thread.clone(), + layout: self.layout.clone(), + vfork: self.vfork.clone(), +@@ -300,6 +305,7 @@ impl WasiEnv { + layout: self.layout.clone(), + vfork: None, + poll_seed: 0, ++ host_policy: self.host_policy, + bin_factory, + state, + inner: Default::default(), +@@ -458,6 +464,7 @@ impl WasiEnv { + layout, + vfork: None, + poll_seed: 0, ++ host_policy: init.host_policy, + state: Arc::new(init.state), + inner: Default::default(), + owned_handles: Vec::new(), +@@ -502,6 +509,29 @@ impl WasiEnv { + update_layout: bool, + call_initialize: bool, + linker_instance_group_data: Option, ++ ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> { ++ self.instantiate_inner( ++ module, ++ store, ++ memory, ++ update_layout, ++ call_initialize, ++ linker_instance_group_data, ++ false, ++ ) ++ .now_or_never() ++ .expect("synchronous WASI instantiation must not yield") ++ } ++ ++ pub(crate) async fn instantiate_inner( ++ self, ++ module: Module, ++ store: &mut impl AsStoreMut, ++ memory: Option, ++ update_layout: bool, ++ call_initialize: bool, ++ linker_instance_group_data: Option, ++ async_instantiation: bool, + ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> { + let pid = self.process.pid(); + +@@ -538,7 +568,7 @@ impl WasiEnv { + }; + + // TODO: make stack size configurable +- Linker::new( ++ Linker::new_inner( + engine, + &module, + &mut store, +@@ -546,7 +576,9 @@ impl WasiEnv { + &mut func_env, + 8 * 1024 * 1024, + &ld_library_path, ++ async_instantiation, + ) ++ .await + } + }; + +@@ -587,7 +619,19 @@ impl WasiEnv { + }); + + // Construct the instance. +- let instance = match Instance::new(&mut store, &module, &import_object) { ++ let instance_result = if async_instantiation { ++ #[cfg(all(feature = "js", target_arch = "wasm32"))] ++ { ++ Instance::new_async(&mut store, &module, &import_object).await ++ } ++ #[cfg(not(all(feature = "js", target_arch = "wasm32")))] ++ { ++ Instance::new(&mut store, &module, &import_object) ++ } ++ } else { ++ Instance::new(&mut store, &module, &import_object) ++ }; ++ let instance = match instance_result { + Ok(a) => a, + Err(err) => { + tracing::error!( +diff --git a/lib/wasix/src/state/linker/mod.rs b/lib/wasix/src/state/linker/mod.rs +index 6b7f0e9..264607b 100644 +--- a/lib/wasix/src/state/linker/mod.rs ++++ b/lib/wasix/src/state/linker/mod.rs +@@ -337,6 +337,31 @@ impl Linker { + func_env: &mut WasiFunctionEnv, + stack_size: u64, + ld_library_path: &[&Path], ++ ) -> Result<(Self, LinkedMainModule), LinkError> { ++ use futures::FutureExt; ++ Self::new_inner( ++ engine, ++ main_module, ++ store, ++ memory, ++ func_env, ++ stack_size, ++ ld_library_path, ++ false, ++ ) ++ .now_or_never() ++ .expect("synchronous linker construction must not yield") ++ } ++ ++ pub(crate) async fn new_inner( ++ engine: Engine, ++ main_module: &Module, ++ store: &mut StoreMut<'_>, ++ memory: Option, ++ func_env: &mut WasiFunctionEnv, ++ stack_size: u64, ++ ld_library_path: &[&Path], ++ async_instantiation: bool, + ) -> Result<(Self, LinkedMainModule), LinkError> { + let dylink_section = parse_dylink0_section(main_module)?; + +@@ -467,13 +492,15 @@ impl Linker { + // use that ordering. My *guess* is that, since main exports all the libc + // functions and those are called frequently by basically any code, then giving + // stubs to main will be faster, but we need numbers before we decide this. +- let main_instance = instantiate_with_runtime_hooks( ++ let main_instance = runtime_hooks::instantiate_with_runtime_hooks_inner( + &func_env.env, + store, + main_module, + &mut imports, + &memory, +- )?; ++ async_instantiation, ++ ) ++ .await?; + instance_group.main_instance = Some(main_instance.clone()); + + let tls_base = get_tls_base_export(&main_instance, store)?; +diff --git a/lib/wasix/src/state/linker/runtime_hooks.rs b/lib/wasix/src/state/linker/runtime_hooks.rs +index 0e92a93..6a6391e 100644 +--- a/lib/wasix/src/state/linker/runtime_hooks.rs ++++ b/lib/wasix/src/state/linker/runtime_hooks.rs +@@ -10,6 +10,20 @@ pub(super) fn instantiate_with_runtime_hooks( + module: &Module, + imports: &mut Imports, + imported_memory: &Memory, ++) -> Result { ++ use futures::FutureExt; ++ instantiate_with_runtime_hooks_inner(env, store, module, imports, imported_memory, false) ++ .now_or_never() ++ .expect("synchronous runtime hooks must not yield") ++} ++ ++pub(super) async fn instantiate_with_runtime_hooks_inner( ++ env: &FunctionEnv, ++ store: &mut impl AsStoreMut, ++ module: &Module, ++ imports: &mut Imports, ++ imported_memory: &Memory, ++ async_instantiation: bool, + ) -> Result { + let runtime = env.as_ref(store).runtime.clone(); + +@@ -20,7 +34,18 @@ pub(super) fn instantiate_with_runtime_hooks( + .map_err(LinkError::RuntimeHookError)? + }; + +- let instance = Instance::new(store, module, imports)?; ++ let instance = if async_instantiation { ++ #[cfg(all(feature = "js", target_arch = "wasm32"))] ++ { ++ Instance::new_async(store, module, imports).await? ++ } ++ #[cfg(not(all(feature = "js", target_arch = "wasm32")))] ++ { ++ Instance::new(store, module, imports)? ++ } ++ } else { ++ Instance::new(store, module, imports)? ++ }; + + { + let mut store_mut = store.as_store_mut(); +diff --git a/lib/wasix/src/syscalls/wasi/fd_close.rs b/lib/wasix/src/syscalls/wasi/fd_close.rs +index d309644..8dfd32f 100644 +--- a/lib/wasix/src/syscalls/wasi/fd_close.rs ++++ b/lib/wasix/src/syscalls/wasi/fd_close.rs +@@ -53,7 +53,11 @@ pub fn fd_close(mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Result bool { ++ env.host_policy.guest_execution() == crate::capabilities::WasiGuestExecutionMode::SingleProgram ++} ++ + fn propagate_virtual_task_completion( + tasks: &Arc, + mut task: crate::os::task::TaskJoinHandle, +diff --git a/lib/wasix/src/syscalls/wasix/proc_exec3.rs b/lib/wasix/src/syscalls/wasix/proc_exec3.rs +index 39fad1f..88acc61 100644 +--- a/lib/wasix/src/syscalls/wasix/proc_exec3.rs ++++ b/lib/wasix/src/syscalls/wasix/proc_exec3.rs +@@ -32,6 +32,9 @@ pub fn proc_exec3( + path_len: M::Offset, + ) -> Result { + WasiEnv::do_pending_operations(&mut ctx)?; ++ if super::single_program_requested(ctx.data()) { ++ return Ok(Errno::Notsup); ++ } + + // If we were just restored the stack then we were woken after a deep sleep + if let Some(exit_code) = unsafe { handle_rewind::(&mut ctx) } { +diff --git a/lib/wasix/src/syscalls/wasix/proc_fork.rs b/lib/wasix/src/syscalls/wasix/proc_fork.rs +index 6760fc8..c79bda0 100644 +--- a/lib/wasix/src/syscalls/wasix/proc_fork.rs ++++ b/lib/wasix/src/syscalls/wasix/proc_fork.rs +@@ -27,6 +27,11 @@ pub fn proc_fork( + ) -> Result { + WasiEnv::do_pending_operations(&mut ctx)?; + ++ if single_program_requested(ctx.data()) { ++ warn!("guest process fork denied for the Oliphaunt single-backend pgwire runtime"); ++ return Ok(Errno::Notcapable); ++ } ++ + wasi_try_ok!(ctx.data().ensure_static_module().map_err(|_| { + warn!("process forking not supported for dynamically linked modules"); + Errno::Notsup +diff --git a/lib/wasix/src/syscalls/wasix/proc_spawn.rs b/lib/wasix/src/syscalls/wasix/proc_spawn.rs +index d128152..5eb358a 100644 +--- a/lib/wasix/src/syscalls/wasix/proc_spawn.rs ++++ b/lib/wasix/src/syscalls/wasix/proc_spawn.rs +@@ -46,6 +46,11 @@ pub fn proc_spawn( + ) -> Result { + WasiEnv::do_pending_operations(&mut ctx)?; + ++ if single_program_requested(ctx.data()) { ++ warn!("guest process creation denied for the Oliphaunt single-backend pgwire runtime"); ++ return Ok(Errno::Notcapable); ++ } ++ + let env = ctx.data(); + let control_plane = &env.control_plane; + let memory = unsafe { env.memory_view(&ctx) }; +diff --git a/lib/wasix/src/syscalls/wasix/proc_spawn2.rs b/lib/wasix/src/syscalls/wasix/proc_spawn2.rs +index 8d5d09b..475da1b 100644 +--- a/lib/wasix/src/syscalls/wasix/proc_spawn2.rs ++++ b/lib/wasix/src/syscalls/wasix/proc_spawn2.rs +@@ -40,6 +40,9 @@ pub fn proc_spawn2( + ret: WasmPtr, + ) -> Result { + WasiEnv::do_pending_operations(&mut ctx)?; ++ if super::single_program_requested(ctx.data()) { ++ return Ok(Errno::Notsup); ++ } + + let memory = unsafe { ctx.data().memory_view(&ctx) }; + let mut name = unsafe { get_input_str_ok!(&memory, name, name_len) }; +diff --git a/lib/wasix/src/syscalls/wasix/thread_spawn.rs b/lib/wasix/src/syscalls/wasix/thread_spawn.rs +index b9be1aa..8b061ca 100644 +--- a/lib/wasix/src/syscalls/wasix/thread_spawn.rs ++++ b/lib/wasix/src/syscalls/wasix/thread_spawn.rs +@@ -56,6 +56,11 @@ pub fn thread_spawn_internal_from_wasi( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + start_ptr: WasmPtr, M>, + ) -> Result { ++ if single_program_requested(ctx.data()) { ++ warn!("guest thread creation denied for the Oliphaunt single-backend pgwire runtime"); ++ return Err(Errno::Notcapable); ++ } ++ + // Now we use the environment and memory references + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; diff --git a/src/wasix/browser-host/patches/0036-virtual-fs-poll-contended-pipe-reads.patch b/src/wasix/browser-host/patches/0043-wasmer-virtual-fs-preserve-io-errors-and-wakes.patch similarity index 74% rename from src/wasix/browser-host/patches/0036-virtual-fs-poll-contended-pipe-reads.patch rename to src/wasix/browser-host/patches/0043-wasmer-virtual-fs-preserve-io-errors-and-wakes.patch index 7b277fd51..845343ff6 100644 --- a/src/wasix/browser-host/patches/0036-virtual-fs-poll-contended-pipe-reads.patch +++ b/src/wasix/browser-host/patches/0043-wasmer-virtual-fs-preserve-io-errors-and-wakes.patch @@ -1,7 +1,16 @@ -diff --git a/src/pipe.rs b/src/pipe.rs ---- a/src/pipe.rs -+++ b/src/pipe.rs -@@ -46,6 +46,23 @@ +From: Sid Jain +Date: Sun, 4 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Preserve virtual filesystem errors and pending wakes + +Do not report success with zero bytes when host entropy fails. A contended +Pipe read must return Pending and register a wake rather than blocking its +browser execution realm. These retain the existing focused regressions. + +diff --git a/lib/virtual-fs/src/pipe.rs b/lib/virtual-fs/src/pipe.rs +index 88c53ae..51e6279 100644 +--- a/lib/virtual-fs/src/pipe.rs ++++ b/lib/virtual-fs/src/pipe.rs +@@ -46,6 +46,23 @@ pub struct PipeRx { } impl PipeRx { @@ -25,7 +34,7 @@ diff --git a/src/pipe.rs b/src/pipe.rs // Tries to read from the internal buffer if data is available. fn try_read_from_buffer( rx: &mut MutexGuard<'_, PipeReceiver>, -@@ -108,7 +125,7 @@ +@@ -108,7 +125,7 @@ impl PipeRx { ))); }; @@ -34,7 +43,7 @@ diff --git a/src/pipe.rs b/src/pipe.rs loop { { if let Some(inner_buf) = rx.buffer.as_mut() { -@@ -460,7 +477,7 @@ +@@ -460,7 +477,7 @@ impl AsyncRead for PipeRx { ))); }; @@ -43,7 +52,7 @@ diff --git a/src/pipe.rs b/src/pipe.rs loop { if Self::try_read_from_buffer(&mut rx, buf.remaining(), |read_buf| { buf.put_slice(read_buf); -@@ -601,3 +618,63 @@ +@@ -601,3 +618,63 @@ impl DuplexPipe { /// Shared version of BidiPipe for situations where you need /// to emulate the old behaviour of `Pipe` (both send and recv on one channel). pub type WasiBidirectionalSharedPipePair = ArcFile; @@ -107,3 +116,16 @@ diff --git a/src/pipe.rs b/src/pipe.rs + assert!(buf.filled().is_empty()); + } +} +diff --git a/lib/virtual-fs/src/random_file.rs b/lib/virtual-fs/src/random_file.rs +index 89be23c..58819c5 100644 +--- a/lib/virtual-fs/src/random_file.rs ++++ b/lib/virtual-fs/src/random_file.rs +@@ -54,7 +54,7 @@ impl AsyncRead for RandomFile { + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + let mut data = vec![0u8; buf.remaining()]; +- getrandom::fill(&mut data).ok(); ++ getrandom::fill(&mut data).map_err(io::Error::other)?; + buf.put_slice(&data[..]); + Poll::Ready(Ok(())) + } diff --git a/src/wasix/browser-host/patches/0033-wasmer-wasix-poll-ready-asyncify-work.patch b/src/wasix/browser-host/patches/0044-wasmer-wasix-preserve-handle-and-seek-lifetimes.patch similarity index 58% rename from src/wasix/browser-host/patches/0033-wasmer-wasix-poll-ready-asyncify-work.patch rename to src/wasix/browser-host/patches/0044-wasmer-wasix-preserve-handle-and-seek-lifetimes.patch index 3802bcc74..743078e19 100644 --- a/src/wasix/browser-host/patches/0033-wasmer-wasix-poll-ready-asyncify-work.patch +++ b/src/wasix/browser-host/patches/0044-wasmer-wasix-preserve-handle-and-seek-lifetimes.patch @@ -1,21 +1,102 @@ From: Sid Jain -Date: Mon, 7 Sep 2026 00:00:00 +0000 -Subject: [PATCH] wasmer-wasix: poll ready work before asyncify suspension +Date: Sun, 4 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Preserve handle ownership and ready-seek continuation -Complete immediately ready work without unnecessary asyncify setup while -retaining exit checks, signal handling and the pending-work continuation. The -seek path uses this only where its future and shared offset survive suspension. -This general runtime optimization must retain pending/error/signal tests; -ready-only benchmark wins do not justify weakening those paths. +Release the registry borrow before borrowing an instance and keep its Rc alive +for each guard. Conflicting access returns None; removal cannot silently leak +registered handles. Poll ready seeks without suspension setup, preserving +exit checks, work/signal ordering, and the same pinned future after Pending. +The shared seek-offset fix and wake-retaining block_on already exist upstream. -diff --git a/src/syscalls/mod.rs b/src/syscalls/mod.rs -index 03e09d75..9decf21b 100644 ---- a/src/syscalls/mod.rs -+++ b/src/syscalls/mod.rs -@@ -296,6 +296,56 @@ where - InlineWaker::block_on(work) +diff --git a/lib/wasix/src/state/handles/thread_local.rs b/lib/wasix/src/state/handles/thread_local.rs +index 5b1a504..c0610fa 100644 +--- a/lib/wasix/src/state/handles/thread_local.rs ++++ b/lib/wasix/src/state/handles/thread_local.rs +@@ -17,6 +17,20 @@ thread_local! { + = RefCell::new(HashMap::new()); } ++fn clone_registered( ++ registry: &RefCell>>>, ++ id: u64, ++) -> Option>> { ++ registry.borrow().get(&id).cloned() ++} ++ ++fn remove_registered( ++ registry: &RefCell>>>, ++ id: u64, ++) -> Option>> { ++ registry.borrow_mut().remove(&id) ++} ++ + /// This non-sendable guard provides memory safe access + /// to the WasiInstance object but only when it is + /// constructed with certain constraints +@@ -79,9 +93,8 @@ impl WasiInstanceHandlesPointer { + .iter() + .filter_map(|id| { + THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { +- let map = map.borrow(); +- if let Some(inner) = map.get(id) { +- let borrow: Ref = inner.borrow(); ++ if let Some(inner) = clone_registered(map, *id) { ++ let borrow: Ref = inner.try_borrow().ok()?; + let borrow: Ref<'static, WasiModuleTreeHandles> = + unsafe { std::mem::transmute(borrow) }; + Some(WasiInstanceGuard { +@@ -101,8 +114,7 @@ impl WasiInstanceHandlesPointer { + .into_iter() + .filter_map(|id| { + THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { +- let map = map.borrow_mut(); +- if let Some(inner) = map.get(&id) { ++ if let Some(inner) = clone_registered(map, id) { + let borrow: RefMut = inner.try_borrow_mut().ok()?; + let borrow: RefMut<'static, WasiModuleTreeHandles> = + unsafe { std::mem::transmute(borrow) }; +@@ -138,13 +150,30 @@ impl WasiInstanceHandlesPointer { + } + fn destroy(id: u64) { + THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { +- if let Ok(mut map) = map.try_borrow_mut() { +- map.remove(&id); +- } ++ drop(remove_registered(map, id)); + }) + } + } + ++#[cfg(test)] ++mod tests { ++ use super::*; ++ ++ #[test] ++ fn registry_removal_does_not_borrow_the_registered_value() { ++ let registry = RefCell::new(HashMap::new()); ++ registry ++ .borrow_mut() ++ .insert(7, Rc::new(RefCell::new(41_u8))); ++ let inner = clone_registered(®istry, 7).expect("registered value"); ++ let retained = inner.borrow(); ++ assert!(inner.try_borrow_mut().is_err()); ++ assert!(remove_registered(®istry, 7).is_some()); ++ assert!(registry.borrow().is_empty()); ++ assert_eq!(*retained, 41); ++ } ++} ++ + /// This provides access to the memory inside the instance + pub(crate) struct WasiInstanceGuardMemory<'a> { + // the order is very important as the first value is +diff --git a/lib/wasix/src/syscalls/mod.rs b/lib/wasix/src/syscalls/mod.rs +index 1301c06..7f15412 100644 +--- a/lib/wasix/src/syscalls/mod.rs ++++ b/lib/wasix/src/syscalls/mod.rs +@@ -307,6 +307,56 @@ where + /// thus allowed for asynchronous operations to execute. It has built in functionality + /// to (optionally) timeout the IO, force exit the process, callback signals and pump + /// synchronous IO engine +// This poller processes signals while the main working function is idle. +// The work is borrowed through its existing pin so a caller may poll it once +// before entering the blocking path without moving or recreating the future. @@ -66,10 +147,10 @@ index 03e09d75..9decf21b 100644 + work.poll(cx) +} + - /// Asyncify takes the current thread and blocks on the async runtime associated with it - /// thus allowed for asynchronous operations to execute. It has built in functionality - /// to (optionally) timeout the IO, force exit the process, callback signals and pump -@@ -316,38 +366,102 @@ where + pub(crate) fn __asyncify( + ctx: &mut FunctionEnvMut<'_, WasiEnv>, + timeout: Option, +@@ -323,38 +373,102 @@ where return Err(WasiError::Exit(exit_code)); } @@ -80,21 +161,6 @@ index 03e09d75..9decf21b 100644 - { - ctx: &'a mut FunctionEnvMut<'b, WasiEnv>, - pinned_work: Pin>, -- } -- impl Future for SignalPoller<'_, '_, Fut, T> -- where -- Fut: Future>, -- { -- type Output = Result; -- fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { -- if let Poll::Ready(res) = Pin::new(&mut self.pinned_work).poll(cx) { -- return Poll::Ready(Ok(res)); -- } -- if let Some(signals) = self.ctx.data().thread.pop_signals_or_subscribe(cx.waker()) { -- if let Err(err) = WasiEnv::process_signals_internal(self.ctx, signals) { -- return Poll::Ready(Err(err)); -- } -- return Poll::Ready(Ok(Err(Errno::Intr))); + // Block on the work + let mut pinned_work = Box::pin(work); + asyncify_continue(ctx, timeout, pinned_work.as_mut()) @@ -119,7 +185,21 @@ index 03e09d75..9decf21b 100644 + // This must remain the only exit check on both the ready and pending paths. + if let Some(exit_code) = env.should_exit() { + return Err(WasiError::Exit(exit_code)); -+ } + } +- impl Future for SignalPoller<'_, '_, Fut, T> +- where +- Fut: Future>, +- { +- type Output = Result; +- fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { +- if let Poll::Ready(res) = Pin::new(&mut self.pinned_work).poll(cx) { +- return Poll::Ready(Ok(res)); +- } +- if let Some(signals) = self.ctx.data().thread.pop_signals_or_subscribe(cx.waker()) { +- if let Err(err) = WasiEnv::process_signals_internal(self.ctx, signals) { +- return Poll::Ready(Err(err)); +- } +- return Poll::Ready(Ok(Err(Errno::Intr))); + + futures::pin_mut!(work); + let waker = WasiDummyWaker.into_waker(); @@ -190,7 +270,7 @@ index 03e09d75..9decf21b 100644 + let waker = WasiDummyWaker.into_waker(); + let mut cx = Context::from_waker(&waker); + assert_eq!(poll_ready_once(seek.as_mut(), &mut cx), Poll::Pending); -+ assert_eq!(InlineWaker::block_on(seek.as_mut()), Ok(41)); ++ assert_eq!(block_on(seek.as_mut()), Ok(41)); + } + + assert_eq!(file.starts, 1); @@ -200,11 +280,11 @@ index 03e09d75..9decf21b 100644 } /// Future that will be polled by asyncify methods -diff --git a/src/syscalls/wasi/fd_seek.rs b/src/syscalls/wasi/fd_seek.rs -index 2d93ae1c..3173325e 100644 ---- a/src/syscalls/wasi/fd_seek.rs -+++ b/src/syscalls/wasi/fd_seek.rs -@@ -98,7 +98,7 @@ pub(crate) fn fd_seek_internal( +diff --git a/lib/wasix/src/syscalls/wasi/fd_seek.rs b/lib/wasix/src/syscalls/wasi/fd_seek.rs +index 35fdf7a..3a70f51 100644 +--- a/lib/wasix/src/syscalls/wasi/fd_seek.rs ++++ b/lib/wasix/src/syscalls/wasi/fd_seek.rs +@@ -97,7 +97,7 @@ pub(crate) fn fd_seek_internal( let fd_offset = fd_entry.inner.offset.clone(); drop(guard); @@ -213,3 +293,17 @@ index 2d93ae1c..3173325e 100644 let mut handle = handle.write().unwrap(); let end = handle .seek(SeekFrom::End(offset)) +diff --git a/lib/wasix/src/syscalls/wasix/callback_signal.rs b/lib/wasix/src/syscalls/wasix/callback_signal.rs +index 4592979..33cb1a4 100644 +--- a/lib/wasix/src/syscalls/wasix/callback_signal.rs ++++ b/lib/wasix/src/syscalls/wasix/callback_signal.rs +@@ -28,7 +28,8 @@ pub fn callback_signal( + Span::current().record("name", name.as_str()); + + let funct = env +- .inner() ++ .try_inner() ++ .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))? + .main_module_instance_handles() + .instance + .exports diff --git a/src/wasix/browser-host/patches/0045-wasmer-wasix-correct-browser-clock-domains.patch b/src/wasix/browser-host/patches/0045-wasmer-wasix-correct-browser-clock-domains.patch new file mode 100644 index 000000000..cff116ba9 --- /dev/null +++ b/src/wasix/browser-host/patches/0045-wasmer-wasix-correct-browser-clock-domains.patch @@ -0,0 +1,88 @@ +From: Sid Jain +Date: Sun, 4 Oct 2026 00:00:00 +0000 +Subject: [PATCH] wasmer-wasix: preserve browser clock domains across workers + +Realtime is wall time; monotonic time must not follow wall-clock adjustments. +Use the shared browser monotonic timeline so WASIX workers can compare their +clock readings despite different creation times. Report unsupported CPU clocks +rather than returning wall time. Keep these corrections in the platform +provider so canonical syscalls, clock setters and embedder fast paths agree. + +diff --git a/lib/wasix/src/syscalls/wasm.rs b/lib/wasix/src/syscalls/wasm.rs +index dd731f0..e970d08 100644 +--- a/lib/wasix/src/syscalls/wasm.rs ++++ b/lib/wasix/src/syscalls/wasm.rs +@@ -1,6 +1,4 @@ +-use std::mem; +- +-use chrono::prelude::*; ++use wasm_bindgen::prelude::wasm_bindgen; + use wasmer::WasmRef; + + use crate::syscalls::types::{ +@@ -8,15 +6,33 @@ use crate::syscalls::types::{ + *, + }; + ++// WASIX processes share Rust memory across workers, but performance.now() is ++// relative to each worker's creation. Include timeOrigin so every process uses ++// the same monotonic timeline (https://www.w3.org/TR/hr-time-2/). ++#[wasm_bindgen(inline_js = r#" ++export function oliphaunt_monotonic_time_ms() { ++ return performance.timeOrigin + performance.now(); ++} ++"#)] ++extern "C" { ++ fn oliphaunt_monotonic_time_ms() -> f64; ++} ++ ++fn monotonic_time_ns() -> Result { ++ let time = std::time::Duration::try_from_secs_f64(oliphaunt_monotonic_time_ms() / 1000.0) ++ .map_err(|_| Errno::Overflow)?; ++ i64::try_from(time.as_nanos()).map_err(|_| Errno::Overflow) ++} ++ + pub fn platform_clock_res_get( + clock_id: Snapshot0Clockid, + resolution: WasmRef, + ) -> Result { + let t_out = match clock_id { +- Snapshot0Clockid::Monotonic => 10_000_000, +- Snapshot0Clockid::Realtime => 1, +- Snapshot0Clockid::ProcessCputimeId => 1, +- Snapshot0Clockid::ThreadCputimeId => 1, ++ Snapshot0Clockid::Monotonic | Snapshot0Clockid::Realtime => 1_000_000, ++ Snapshot0Clockid::ProcessCputimeId | Snapshot0Clockid::ThreadCputimeId => { ++ return Err(Errno::Notsup); ++ } + _ => return Err(Errno::Inval), + }; + Ok(t_out) +@@ -24,10 +40,22 @@ pub fn platform_clock_res_get( + + pub fn platform_clock_time_get( + clock_id: Snapshot0Clockid, +- precision: Timestamp, ++ _precision: Timestamp, + ) -> Result { +- Local::now() +- .timestamp_nanos_opt() +- .map(|ts| ts as i64) +- .ok_or(Errno::Overflow) ++ match clock_id { ++ Snapshot0Clockid::Realtime => { ++ let millis = js_sys::Date::now(); ++ if !millis.is_finite() || millis < 0.0 || millis > i64::MAX as f64 { ++ return Err(Errno::Overflow); ++ } ++ (millis as i64) ++ .checked_mul(1_000_000) ++ .ok_or(Errno::Overflow) ++ } ++ Snapshot0Clockid::Monotonic => monotonic_time_ns(), ++ Snapshot0Clockid::ProcessCputimeId | Snapshot0Clockid::ThreadCputimeId => { ++ Err(Errno::Notsup) ++ } ++ _ => Err(Errno::Inval), ++ } + } diff --git a/src/wasix/browser-host/prepare-sdk.mts b/src/wasix/browser-host/prepare-sdk.mts new file mode 100644 index 000000000..1aaf46265 --- /dev/null +++ b/src/wasix/browser-host/prepare-sdk.mts @@ -0,0 +1,40 @@ +import { copyFile, cp, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const host = import.meta.dirname; +const sdk = resolve(process.argv[2]); +const engine = resolve(process.argv[3]); +const crates = { + 'virtual-fs': 'virtual-fs', + 'virtual-mio': 'virtual-io', + 'virtual-net': 'virtual-net', + wasmer: 'api', + 'wasmer-c-api-imports': 'c-api-imports', + 'wasmer-config': 'config', + 'wasmer-package': 'package', + 'wasmer-types': 'types', + 'wasmer-wasix': 'wasix', + 'wasmer-wasix-types': 'wasi-types', +}; +// Only the browser binding is built. The SDK's Rust/uniffi/N-API products and +// their dependency graphs are not part of Oliphaunt's browser host. +await writeFile( + resolve(sdk, 'Cargo.toml'), + '[workspace]\nmembers = ["js/bindgen"]\nresolver = "2"\n\n[patch.crates-io]\n' + + Object.entries(crates) + .map( + ([name, path]) => `${name} = { path = ${JSON.stringify(resolve(engine, 'lib', path))} }\n`, + ) + .join(''), +); +await cp(resolve(host, 'adapter'), resolve(sdk, 'js/bindgen/src/oliphaunt'), { recursive: true }); +await copyFile( + resolve(host, 'protocol-contract.generated.rs'), + resolve(sdk, 'js/bindgen/src/oliphaunt/protocol_contract.rs'), +); +await copyFile(resolve(host, 'rust-toolchain.toml'), resolve(sdk, 'rust-toolchain.toml')); +await copyFile(resolve(host, 'index.mts'), resolve(sdk, 'js/index.mts')); +// The lock is kept with our adapter because it has a narrower graph than the +// upstream SDK workspace. Cargo's --locked owns dependency consistency. +const lock = resolve(host, 'Cargo.lock'); +if (await Bun.file(lock).exists()) await copyFile(lock, resolve(sdk, 'Cargo.lock')); diff --git a/src/wasix/browser-host/rust-toolchain.toml b/src/wasix/browser-host/rust-toolchain.toml new file mode 100644 index 000000000..516c82e47 --- /dev/null +++ b/src/wasix/browser-host/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +# Shared-memory browser workers require rebuilding std with atomics. +channel = "nightly-2026-08-11" +profile = "minimal" +components = ["rust-src", "rustfmt"] +targets = ["wasm32-unknown-unknown"] diff --git a/src/wasix/browser-host/source.toml b/src/wasix/browser-host/source.toml index a0001ed0f..c6cb4346c 100644 --- a/src/wasix/browser-host/source.toml +++ b/src/wasix/browser-host/source.toml @@ -1,73 +1,36 @@ [wasmer-js] +# SDK 0.19.0 is Modified MIT, not plain MIT. See the pinned upstream LICENSE +# for its commercial-product UI attribution threshold before adoption. name = "@wasmer/sdk" -version = "0.8.0" -url = "https://github.com/wasmerio/wasmer-js.git" -commit = "93b8b738ebd3ee57e118da0f0eb795b97d5b999e" - -[wasmer-wasix] -# The source commit referenced by the upstream 0.10.0 npm release identifies -# itself as @wasmer/sdk 0.8.0 and carries a coherent Wasmer 6.1 / wasmer-wasix -# 0.601 dependency family. wasmer-wasix 0.702.1 requires Wasmer -# 7.2.1 plus matching virtual-fs/net, config, package, backend, WebC, and -# wasm-bindgen APIs; it is not a single-crate update for this upstream JS host. -name = "wasmer-wasix" -version = "0.601.0" -url = "https://static.crates.io/crates/wasmer-wasix/wasmer-wasix-0.601.0.crate" -sha256 = "a9a804fcfbdbbfe5b2d6fb30e741e22a21e88e964dedfea9e609f13a52823bfe" +version = "0.19.0" +url = "https://github.com/wasmerio/wasmer-sdk.git" +commit = "7e69332b7f65dbc5584d64bb79f547eaf4302b69" [wasmer] +# The coherent engine revision used by SDK 0.19.0, including WASIX and virtual IO. name = "wasmer" -version = "6.1.0" -url = "https://static.crates.io/crates/wasmer/wasmer-6.1.0.crate" -sha256 = "2d85671948f8886a1cc946141c0b688a5617603c103699a5fceeebeb4e75b0b6" +version = "7.4.0" +url = "https://github.com/wasmerio/wasmer.git" +commit = "5bd2b7d3182e816bd0ea330acc88e4e4733e167f" + +[wasmer-wasix] +name = "wasmer-wasix" +version = "0.704.0" [virtual-fs] name = "virtual-fs" -version = "0.601.0" -url = "https://static.crates.io/crates/virtual-fs/virtual-fs-0.601.0.crate" -sha256 = "8defc513ce70576bdbb4305ee67c0cba647af18c0995de8f1fa1271b724c9859" +version = "0.704.0" [virtual-mio] name = "virtual-mio" -version = "0.601.0" -url = "https://static.crates.io/crates/virtual-mio/virtual-mio-0.601.0.crate" -sha256 = "a14806a3f25b70315764760c376d9f68937f5467013891bc7497a33d24105097" +version = "0.704.0" [patches] series = [ - "0001-wasmer-js-run-configured-wasix-process.patch", - "0002-wasmer-wasix-add-0702-compatibility-imports.patch", - "0003-wasmer-js-install-browser-runtime-devices.patch", - "0005-wasmer-js-use-object-wasm-init.patch", - "0006-wasmer-js-reuse-precompiled-wasix-module.patch", - "0007-wasmer-js-run-oliphaunt-direct.patch", - "0008-wasmer-instantiate-js-modules-async.patch", - "0009-wasmer-wasix-instantiate-main-module-async.patch", - "0010-wasmer-js-refresh-npm-lock.patch", - "0011-wasmer-wasix-deny-single-backend-guest-spawn.patch", - "0012-wasmer-js-remove-retired-wasm32-wasi-target.patch", - "0013-wasmer-wasix-fast-single-backend-clock.patch", - "0014-wasmer-js-track-directory-mutations.patch", - "0015-wasmer-js-add-sync-filesystem-bridge.patch", - "0016-wasmer-wasix-expose-direct-memory.patch", - "0017-wasmer-js-direct-pgwire-memory-bridge.patch", - "0018-wasmer-js-bound-direct-stderr.patch", - "0019-wasmer-parse-exception-reference-types.patch", - "0020-wasmer-wasix-preserve-posix-close-durability.patch", - "0021-wasmer-js-stream-direct-pgwire.patch", - # Typed recovery, checked entropy, fail-closed guest phases and independent - # handle ownership fixes. Optional drain/immutable-file experiments stay out. - "0023-wasmer-js-prepare-trusted-embedded-session.patch", - "0024-virtual-fs-propagate-random-errors.patch", - "0025-wasmer-js-fail-closed-direct-guest-phases.patch", - "0027-wasmer-wasix-isolate-thread-local-handle-borrows.patch", - # Independently preserve provider failures and cursor state in the - # synchronous JavaScript filesystem bridge. - "0034-wasmer-js-preserve-seek-end-errors.patch", - # Backport the fd_seek slice of upstream Wasmer 402cbd7ec4772c0b36226f2e5a21c7797614bf1b - # before its dependent first-poll fast path. - "0035-wasmer-wasix-preserve-shared-seek-offset.patch", - "0033-wasmer-wasix-poll-ready-asyncify-work.patch", - "0036-virtual-fs-poll-contended-pipe-reads.patch", - "0037-virtual-mio-retain-inline-wakes.patch", + "0040-wasmer-js-build-oliphaunt-browser-adapter.patch", + "0041-wasmer-instantiate-browser-modules-async.patch", + "0042-wasmer-wasix-browser-host-policy.patch", + "0043-wasmer-virtual-fs-preserve-io-errors-and-wakes.patch", + "0044-wasmer-wasix-preserve-handle-and-seek-lifetimes.patch", + "0045-wasmer-wasix-correct-browser-clock-domains.patch", ] diff --git a/src/wasix/sdks/ts/src/host/index.d.mts b/src/wasix/sdks/ts/src/host/index.d.mts index c4a45ba4b..14bf3d2d2 100644 --- a/src/wasix/sdks/ts/src/host/index.d.mts +++ b/src/wasix/sdks/ts/src/host/index.d.mts @@ -25,7 +25,9 @@ export class Directory { /** Create a filesystem served synchronously by a caller-realm backend. */ static createSync(backend: object, capacity: number): Directory; free(): void; + [Symbol.dispose](): void; __getClassname(): string; + __cloneForMount(): Directory; createDir(path: string): Promise; readDir(path: string): Promise; readFile(path: string): Promise; @@ -70,6 +72,7 @@ export class OliphauntDirectInstance { export class OliphauntPreparedTool { private constructor(); free(): void; + [Symbol.dispose](): void; } export type RunWasixOptions = Readonly<{ diff --git a/src/wasix/sdks/ts/tools/stage-host.mts b/src/wasix/sdks/ts/tools/stage-host.mts index 686135bad..a7b77f901 100644 --- a/src/wasix/sdks/ts/tools/stage-host.mts +++ b/src/wasix/sdks/ts/tools/stage-host.mts @@ -48,7 +48,13 @@ function assertHostDeclarationCompatibility() { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.NodeNext, moduleResolution: ts.ModuleResolutionKind.NodeNext, - lib: ['lib.es2023.d.ts', 'lib.dom.d.ts', 'lib.dom.iterable.d.ts', 'lib.webworker.d.ts'], + lib: [ + 'lib.es2023.d.ts', + 'lib.esnext.disposable.d.ts', + 'lib.dom.d.ts', + 'lib.dom.iterable.d.ts', + 'lib.webworker.d.ts', + ], types: [], }; const defaultHost = ts.createCompilerHost(compilerOptions); From 1ecbf86a380d53f20b693eb33a9297bd5208e26c Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Thu, 8 Oct 2026 13:03:26 +0530 Subject: [PATCH 4/4] feat(wasix): integrate bundled Windows V8 engine and native caches (#255) * feat(wasix): integrate bundled Windows V8 engine and native caches * fix(release): plan embedded engine payloads before source acquisition * fix(ci): qualify Windows engine contracts before cache production * test(wasix): qualify the frozen Windows engine package closure * fix(wasix): remove LLVM inputs from Windows runtime qualification * fix(packaging): close transitive path dependency test sources * test(wasix): run installed Windows SDK against frozen AOT carriers * test(wasix): qualify the full extension lifecycle on Windows V8 * refactor(wasix): keep one Windows cache producer helper * refactor(wasix): name native value ownership explicitly * refactor(wasix): align engine qualification and producer conventions * fix(ci): prepare WASIX engine toolchains and lifecycle sources * fix(ci): execute selected WASIX engine tasks without base lookup * fix(wasix): extract Windows engine archive with a local path * fix(wasix): close installed engine qualification dependencies * fix(packaging): normalize native Windows Cargo file listings * fix(wasix): keep dependency resolution in native qualification * fix(ci): serialize WASIX server executable consumers * fix(wasix): split oversized Windows AOT Cargo payloads * fix(wasix): align qualification and maintainer workflows * perf(packaging): reuse validated Windows payload archives * fix(wasix): align engine patches and qualification inputs --- .github/workflows/ci.yml | 24 +- .github/workflows/wasix-host.yml | 120 +- .gitignore | 3 + .moon/tasks/inputs.yml | 4 + Cargo.lock | 1605 +++++++++++- Cargo.toml | 6 + release-please-config.json | 25 + src/docs/maintainers/development.md | 11 +- .../packages/win32-x64-msvc/package.json | 2 +- .../node-addon/tools/check-release-assets.mts | 15 +- .../node-addon/tools/package-platform.mts | 6 +- .../tools/workspace-runtime-contract.mts | 6 + src/wasix/pgwire-server/moon.yml | 7 + src/wasix/pgwire-server/src/server.rs | 5 +- .../x86_64-pc-windows-msvc/build-support.rs | 22 +- .../tools/package-cargo-artifacts.mts | 43 +- .../x86_64-pc-windows-msvc/build-support.rs | 22 +- src/wasix/runtime/engine/README.md | 40 + src/wasix/runtime/engine/contract.mts | 16 + .../crates/v8-windows-x64-msvc/Cargo.toml | 12 + .../crates/v8-windows-x64-msvc/build.rs | 12 + .../crates/v8-windows-x64-msvc/src/lib.rs | 7 + .../engine/crates/wasmer-journal/Cargo.toml | 120 + .../crates/wasmer-wasix-types/Cargo.toml | 107 + .../engine/crates/wasmer-wasix/Cargo.toml | 491 ++++ .../runtime/engine/crates/wasmer/Cargo.toml | 318 +++ .../runtime/engine/crates/wasmer/build.rs | 14 + .../runtime/engine/licenses/V8-LICENSES.txt | 2153 +++++++++++++++++ .../runtime/engine/licenses/Wasmer-LICENSE | 21 + src/wasix/runtime/engine/package.mts | 192 ++ src/wasix/runtime/engine/package.test.mts | 152 ++ .../0001-optional-runner-features.patch | 86 + .../0002-cached-module-loading.patch | 86 + .../0003-terminal-memory-attachment.patch | 67 + .../engine/patches/wasmer-wasix/series | 3 + .../wasmer/0001-v8-engine-delivery.patch | 51 + .../0002-v8-handle-and-call-ownership.patch | 691 ++++++ .../0003-v8-native-cache-profiles.patch | 309 +++ ...004-v8-memory-and-terminal-lifetimes.patch | 546 +++++ .../runtime/engine/patches/wasmer/series | 4 + src/wasix/runtime/engine/prepare-sources.mts | 100 + src/wasix/runtime/engine/prepare-sources.sh | 14 + .../runtime/engine/prepare-sources.test.mts | 27 + src/wasix/runtime/engine/source.toml | 48 + src/wasix/runtime/engine/test-packaged.mts | 81 + src/wasix/runtime/engine/windows/bindings.rs | 2042 ++++++++++++++++ src/wasix/runtime/engine/windows/bridge.cpp | 65 + .../runtime/engine/windows/build-caches.mts | 109 + src/wasix/runtime/engine/windows/build.mts | 168 ++ src/wasix/runtime/engine/windows/build.sh | 22 + .../runtime/engine/windows/cache-producer.c | 166 ++ src/wasix/runtime/engine/windows/dispatch.mts | 140 ++ .../runtime/engine/windows/dispatch.test.mts | 21 + src/wasix/runtime/engine/windows/loader.rs | 259 ++ src/wasix/runtime/moon.yml | 73 +- src/wasix/runtime/release.toml | 6 + .../tools/check-release-assets.test.mts | 2 +- src/wasix/runtime/tools/download-assets.sh | 3 + src/wasix/runtime/tools/package-carriers.mts | 32 + ...iboliphaunt-wasix-cargo-artifacts.test.mts | 4 +- ...age_liboliphaunt_wasix_cargo_artifacts.mts | 17 +- src/wasix/runtime/tools/serialize-aot.sh | 4 - src/wasix/runtime/tools/serialize-aot.test.sh | 10 +- src/wasix/runtime/tools/test-packaging.sh | 1 + .../runtime/tools/wasix-aot-manifest.mts | 11 +- .../tools/wasix-cargo-artifact-contract.mts | 5 +- .../tools/wasix-cargo-toolchain-policy.mts | 13 +- .../wasix-cargo-toolchain-policy.test.mts | 38 +- src/wasix/runtime/tools/xtask/Cargo.toml | 14 +- src/wasix/runtime/tools/xtask/moon.yml | 1 + .../runtime/tools/xtask/src/aot_serializer.rs | 145 +- .../runtime/tools/xtask/src/asset_pipeline.rs | 33 +- src/wasix/sdks/rust/Cargo.toml | 24 +- src/wasix/sdks/rust/THIRD_PARTY_NOTICES.md | 9 +- src/wasix/sdks/rust/moon.yml | 18 +- src/wasix/sdks/rust/src/oliphaunt/aot.rs | 67 +- .../sdks/rust/src/oliphaunt/postgres_mod.rs | 1 + src/wasix/sdks/rust/src/oliphaunt/tools.rs | 2 +- .../tools/prepare-rust-release-source.mts | 19 +- src/wasix/sdks/rust/tools/test-aot.sh | 15 +- .../sdks/rust/tools/test-packaged-aot.mts | 116 + tools/ci/workflow-moon-transfers.test.mts | 49 +- .../packaging/cargo-package-test-closure.mts | 51 +- .../cargo-package-test-closure.test.mts | 76 +- tools/packaging/cargo-source-package.mts | 33 +- tools/packaging/cargo-source-package.test.mts | 66 + tools/packaging/cargo-source-package.test.sh | 2 +- tools/packaging/moon.yml | 3 +- tools/packaging/release-notices.mts | 62 +- tools/packaging/wasix-cargo-payload.mts | 138 +- tools/packaging/wasix-cargo-payload.test.mts | 82 +- tools/packaging/wasix-cargo-payload.test.sh | 23 + tools/release/artifact-target-matrix.mts | 3 + tools/release/check_artifact_targets.mts | 7 + ...oad-wasix-runtime-build-artifacts.test.mts | 5 +- .../prepare-release-candidate.test.mts | 44 +- tools/release/publication-catalog.mts | 5 +- tools/release/publication-catalog.test.mts | 24 +- tools/release/release-artifact-targets.mts | 10 +- tools/release/release-graph.mts | 9 + 100 files changed, 11764 insertions(+), 297 deletions(-) create mode 100644 src/wasix/runtime/engine/README.md create mode 100644 src/wasix/runtime/engine/contract.mts create mode 100644 src/wasix/runtime/engine/crates/v8-windows-x64-msvc/Cargo.toml create mode 100644 src/wasix/runtime/engine/crates/v8-windows-x64-msvc/build.rs create mode 100644 src/wasix/runtime/engine/crates/v8-windows-x64-msvc/src/lib.rs create mode 100644 src/wasix/runtime/engine/crates/wasmer-journal/Cargo.toml create mode 100644 src/wasix/runtime/engine/crates/wasmer-wasix-types/Cargo.toml create mode 100644 src/wasix/runtime/engine/crates/wasmer-wasix/Cargo.toml create mode 100644 src/wasix/runtime/engine/crates/wasmer/Cargo.toml create mode 100644 src/wasix/runtime/engine/crates/wasmer/build.rs create mode 100644 src/wasix/runtime/engine/licenses/V8-LICENSES.txt create mode 100644 src/wasix/runtime/engine/licenses/Wasmer-LICENSE create mode 100644 src/wasix/runtime/engine/package.mts create mode 100644 src/wasix/runtime/engine/package.test.mts create mode 100644 src/wasix/runtime/engine/patches/wasmer-wasix/0001-optional-runner-features.patch create mode 100644 src/wasix/runtime/engine/patches/wasmer-wasix/0002-cached-module-loading.patch create mode 100644 src/wasix/runtime/engine/patches/wasmer-wasix/0003-terminal-memory-attachment.patch create mode 100644 src/wasix/runtime/engine/patches/wasmer-wasix/series create mode 100644 src/wasix/runtime/engine/patches/wasmer/0001-v8-engine-delivery.patch create mode 100644 src/wasix/runtime/engine/patches/wasmer/0002-v8-handle-and-call-ownership.patch create mode 100644 src/wasix/runtime/engine/patches/wasmer/0003-v8-native-cache-profiles.patch create mode 100644 src/wasix/runtime/engine/patches/wasmer/0004-v8-memory-and-terminal-lifetimes.patch create mode 100644 src/wasix/runtime/engine/patches/wasmer/series create mode 100644 src/wasix/runtime/engine/prepare-sources.mts create mode 100644 src/wasix/runtime/engine/prepare-sources.sh create mode 100644 src/wasix/runtime/engine/prepare-sources.test.mts create mode 100644 src/wasix/runtime/engine/source.toml create mode 100644 src/wasix/runtime/engine/test-packaged.mts create mode 100644 src/wasix/runtime/engine/windows/bindings.rs create mode 100644 src/wasix/runtime/engine/windows/bridge.cpp create mode 100644 src/wasix/runtime/engine/windows/build-caches.mts create mode 100644 src/wasix/runtime/engine/windows/build.mts create mode 100644 src/wasix/runtime/engine/windows/build.sh create mode 100644 src/wasix/runtime/engine/windows/cache-producer.c create mode 100644 src/wasix/runtime/engine/windows/dispatch.mts create mode 100644 src/wasix/runtime/engine/windows/dispatch.test.mts create mode 100644 src/wasix/runtime/engine/windows/loader.rs create mode 100644 src/wasix/sdks/rust/tools/test-packaged-aot.mts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d05739c8..3331d5b6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -543,7 +543,7 @@ jobs: extension-artifacts-native-android: name: Native Extensions (Android Packaging) needs: [affected, extension-artifacts-native-android-static, extension-artifacts-native-linux] - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.extension-artifacts-native-android-static.result == 'success' && needs.extension-artifacts-native-linux.result == 'success' && fromJson(needs.affected.outputs.extension_artifacts_native_matrix_android).include[0] != null }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.extension-artifacts-native-android-static.result == 'success' && needs.extension-artifacts-native-linux.result == 'success' && fromJson(needs.affected.outputs.extension_artifacts_native_matrix_android).include[0] != null }} uses: ./.github/workflows/extension-artifacts-native.yml with: phase: android-package @@ -591,7 +591,7 @@ jobs: needs: - affected - liboliphaunt-wasix-runtime - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.liboliphaunt-wasix-runtime.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'extension-artifacts-wasix') }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.liboliphaunt-wasix-runtime.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'extension-artifacts-wasix') }} strategy: fail-fast: false matrix: ${{ fromJson(needs.affected.outputs.extension_artifacts_wasix_matrix) }} @@ -704,7 +704,7 @@ jobs: mobile-extension-packages-android: name: Extension Packages (Android) needs: [affected, extension-artifacts-native-android] - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.extension-artifacts-native-android.result == 'success' && needs.affected.outputs.mobile_extension_package_native_targets_android_csv != '' }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.extension-artifacts-native-android.result == 'success' && needs.affected.outputs.mobile_extension_package_native_targets_android_csv != '' }} uses: ./.github/workflows/mobile-extension-packages.yml with: cache-save-if: ${{ needs.affected.outputs.cache_save_if == 'true' }} @@ -716,7 +716,7 @@ jobs: mobile-extension-packages-ios: name: Extension Packages (iOS) needs: [affected, extension-artifacts-native-ios] - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.extension-artifacts-native-ios.result == 'success' && needs.affected.outputs.mobile_extension_package_native_targets_ios_csv != '' }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.extension-artifacts-native-ios.result == 'success' && needs.affected.outputs.mobile_extension_package_native_targets_ios_csv != '' }} uses: ./.github/workflows/mobile-extension-packages.yml with: cache-save-if: ${{ needs.affected.outputs.cache_save_if == 'true' }} @@ -1102,7 +1102,7 @@ jobs: - liboliphaunt-native-desktop - liboliphaunt-native-ios - liboliphaunt-native-ios-abi - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.liboliphaunt-native-android.result == 'success' && needs.liboliphaunt-native-android-abi.result == 'success' && needs.liboliphaunt-native-desktop.result == 'success' && needs.liboliphaunt-native-ios.result == 'success' && needs.liboliphaunt-native-ios-abi.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'liboliphaunt-native-release-assets') }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.liboliphaunt-native-android.result == 'success' && needs.liboliphaunt-native-android-abi.result == 'success' && needs.liboliphaunt-native-desktop.result == 'success' && needs.liboliphaunt-native-ios.result == 'success' && needs.liboliphaunt-native-ios-abi.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'liboliphaunt-native-release-assets') }} runs-on: ubuntu-24.04 timeout-minutes: 30 steps: @@ -1515,7 +1515,7 @@ jobs: - affected - liboliphaunt-native-ios-abi - swift-bindings - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.liboliphaunt-native-ios-abi.result == 'success' && needs.swift-bindings.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'swift-sdk-package') }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.liboliphaunt-native-ios-abi.result == 'success' && needs.swift-bindings.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'swift-sdk-package') }} runs-on: ubuntu-24.04 timeout-minutes: 90 steps: @@ -2274,7 +2274,7 @@ jobs: - affected - liboliphaunt-wasix-runtime - liboliphaunt-wasix-aot - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.liboliphaunt-wasix-runtime.result == 'success' && needs.liboliphaunt-wasix-aot.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'liboliphaunt-wasix-release-assets') && (github.event_name != 'workflow_dispatch' || inputs.wasm_target == 'all') }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.liboliphaunt-wasix-runtime.result == 'success' && needs.liboliphaunt-wasix-aot.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'liboliphaunt-wasix-release-assets') && (github.event_name != 'workflow_dispatch' || inputs.wasm_target == 'all') }} runs-on: ubuntu-24.04 timeout-minutes: 30 steps: @@ -2327,6 +2327,12 @@ jobs: rsync -a "$raw_target_dir/" "$destination/" done + - name: Download qualified Windows engine + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: liboliphaunt-wasix-windows-engine + path: . + - name: Package WASIX release assets env: OLIPHAUNT_MOON_TRANSFERRED_DEPS_JSON: '["liboliphaunt-wasix:runtime-portable", "liboliphaunt-wasix:runtime-aot"]' @@ -3018,7 +3024,7 @@ jobs: - liboliphaunt-native-ios-abi - react-native-sdk-package - swift-sdk-package - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.js-sdk-package.result == 'success' && needs.mobile-extension-packages-ios.result == 'success' && needs.liboliphaunt-native-ios.result == 'success' && needs.liboliphaunt-native-ios-abi.result == 'success' && needs.react-native-sdk-package.result == 'success' && needs.swift-sdk-package.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'mobile-build-ios') }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.js-sdk-package.result == 'success' && needs.mobile-extension-packages-ios.result == 'success' && needs.liboliphaunt-native-ios.result == 'success' && needs.liboliphaunt-native-ios-abi.result == 'success' && needs.react-native-sdk-package.result == 'success' && needs.swift-sdk-package.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'mobile-build-ios') }} runs-on: macos-26 timeout-minutes: 180 env: @@ -3344,7 +3350,7 @@ jobs: needs: - affected - mobile-build-ios - if: ${{ !cancelled() && needs.affected.result == 'success' && needs.mobile-build-ios.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'mobile-build-ios') }} + if: ${{ !cancelled() && !failure() && needs.affected.result == 'success' && needs.mobile-build-ios.result == 'success' && contains(fromJson(needs.affected.outputs.jobs), 'mobile-build-ios') }} runs-on: macos-26 timeout-minutes: 45 steps: diff --git a/.github/workflows/wasix-host.yml b/.github/workflows/wasix-host.yml index 060b1f665..7cb6b56c6 100644 --- a/.github/workflows/wasix-host.yml +++ b/.github/workflows/wasix-host.yml @@ -28,9 +28,95 @@ defaults: run: shell: bash jobs: + windows-engine: + name: WASIX Windows engine + if: ${{ contains(inputs.matrix, 'windows-x64-msvc') }} + runs-on: windows-2025-vs2026 + timeout-minutes: 40 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - uses: ./.github/actions/setup-moon + with: + cache-partition: wasix-windows-engine + - uses: ./.github/actions/setup-rust + with: + cache-save-if: ${{ inputs.cache-save-if }} + tools: cargo-nextest@0.9.137 + - uses: ./.github/actions/setup-msvc + - name: Produce and test pinned Windows engine + run: .github/scripts/run-moon-targets.sh liboliphaunt-wasix:engine-test + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: liboliphaunt-wasix-windows-engine + include-hidden-files: true + path: | + target/oliphaunt-wasix/engine/windows-x64-msvc/oliphaunt_wee8.dll + target/oliphaunt-wasix/engine/windows-x64-msvc/cache-producer.exe + target/oliphaunt-wasix/engine/windows-x64-msvc/source.json + target/oliphaunt-wasix/engine/windows-x64-msvc/LICENSE.v8 + src/wasix/runtime/engine/crates/*/upstream/** + src/wasix/runtime/engine/crates/wasmer/prebuilt/embedded_bindings.rs + if-no-files-found: error + windows-caches: + name: WASIX Windows baseline caches + needs: windows-engine + if: ${{ contains(inputs.matrix, 'windows-x64-msvc') }} + runs-on: ubuntu-24.04 + timeout-minutes: 90 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - uses: ./.github/actions/setup-moon + with: + cache-partition: wasix-windows-caches + - uses: ./.github/actions/setup-rust + with: + cache-save-if: ${{ inputs.cache-save-if }} + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: liboliphaunt-wasix-windows-engine + path: . + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: liboliphaunt-wasix-runtime-portable + path: . + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + if: ${{ contains(fromJson(inputs.job-targets)['liboliphaunt-wasix-aot'], 'postgres-tools-wasix:build-aot') }} + with: + name: postgres-tools-wasix-compiler-output + path: target/postgres-tools/wasix/assets + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + if: ${{ contains(fromJson(inputs.job-targets)['liboliphaunt-wasix-aot'], 'extension-artifacts-wasix:build-aot') }} + with: + name: extensions-wasix-compiler-output + path: target/extensions/wasix/assets + - name: Set up CPU profile producer + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y wine64 libz-mingw-w64 qemu-user + - name: Produce and read genuine SSE4.1 native caches + env: + BUILD_TOOLS: ${{ contains(fromJson(inputs.job-targets)['liboliphaunt-wasix-aot'], 'postgres-tools-wasix:build-aot') }} + BUILD_EXTENSIONS: ${{ contains(fromJson(inputs.job-targets)['liboliphaunt-wasix-aot'], 'extension-artifacts-wasix:build-aot') }} + run: | + products=(runtime) + if [ "$BUILD_TOOLS" = true ]; then products+=(tools); fi + if [ "$BUILD_EXTENSIONS" = true ]; then products+=(extensions); fi + bun src/wasix/runtime/engine/windows/build-caches.mts "${products[@]}" + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: liboliphaunt-wasix-windows-baseline-caches + path: target/oliphaunt-wasix/engine/windows-x64-msvc/cache/sse41 + if-no-files-found: error build: name: WASIX host (${{ matrix.target_id }}) runs-on: ${{ matrix.os }} + needs: [windows-engine, windows-caches] + # Linux/macOS must still run when Windows producers are skipped or fail. + if: ${{ !cancelled() }} timeout-minutes: 240 strategy: fail-fast: false @@ -52,11 +138,34 @@ jobs: uses: ./.github/actions/setup-rust with: cache-save-if: ${{ inputs.cache-save-if }} + tools: ${{ runner.os == 'Windows' && 'cargo-nextest@0.9.137' || '' }} - name: Set up MSVC if: ${{ runner.os == 'Windows' }} uses: ./.github/actions/setup-msvc + - name: Download same-run Windows engine + if: ${{ runner.os == 'Windows' }} + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: liboliphaunt-wasix-windows-engine + path: . + - name: Download same-run Windows baseline caches + if: ${{ runner.os == 'Windows' }} + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: liboliphaunt-wasix-windows-baseline-caches + path: target/oliphaunt-wasix/engine/windows-x64-msvc/cache/sse41 + - name: Set up macOS acquisition and smoke timeout + if: ${{ runner.os == 'macOS' }} + run: | + command -v bash + bash --version + brew list coreutils >/dev/null 2>&1 || HOMEBREW_NO_AUTO_UPDATE=1 brew install coreutils + + - name: Prepare engine source + run: .github/scripts/run-moon-targets.sh liboliphaunt-wasix:engine-sources + - name: Download portable WASIX build outputs uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: @@ -78,6 +187,7 @@ jobs: path: target/extensions/wasix/assets - name: Install Wasmer LLVM 22.1 for AOT generation + if: ${{ runner.os != 'Windows' }} uses: ./.github/actions/setup-wasmer-llvm with: cache-save-if: ${{ inputs.cache-save-if }} @@ -89,7 +199,8 @@ jobs: - name: Build, validate, and smoke target AOT artifacts env: AOT_TARGET: ${{ matrix.target }} - OLIPHAUNT_MOON_TRANSFERRED_DEPS_JSON: '["liboliphaunt-wasix:runtime-portable", "postgres-tools-wasix:compiler-output", "extension-artifacts-wasix:compiler-output"]' + # The uploaded engine already passed engine-test in its producer job. + OLIPHAUNT_MOON_TRANSFERRED_DEPS_JSON: '["liboliphaunt-wasix:runtime-portable", "postgres-tools-wasix:compiler-output", "extension-artifacts-wasix:compiler-output", "liboliphaunt-wasix:engine-test", "liboliphaunt-wasix:engine-build"]' run: .github/scripts/run-planned-moon-job.sh liboliphaunt-wasix-aot - name: Upload target PostgreSQL WASIX tools @@ -150,11 +261,6 @@ jobs: path: target/extensions/wasix/release-assets merge-multiple: true - - name: Set up macOS smoke timeout - if: ${{ contains(fromJson(inputs.napi-targets), matrix.target_id) && runner.os == 'macOS' }} - shell: bash - run: brew list coreutils >/dev/null 2>&1 || HOMEBREW_NO_AUTO_UPDATE=1 brew install coreutils - - name: Build WASIX Node-API release assets if: ${{ contains(fromJson(inputs.napi-targets), matrix.target_id) }} shell: bash @@ -165,7 +271,7 @@ jobs: OLIPHAUNT_WASIX_EXTENSION_ARTIFACT_ROOT: ${{ github.workspace }}/target/extension-artifacts OLIPHAUNT_WASIX_GENERATED_ASSETS_DIR: ${{ github.workspace }}/target/oliphaunt-wasix/assets OLIPHAUNT_WASIX_NAPI_ARTIFACT_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - OLIPHAUNT_MOON_TRANSFERRED_DEPS_JSON: '["extension-artifacts-wasix:build-target", "extension-artifacts-wasix:build-aot", "liboliphaunt-wasix:runtime-aot"]' + OLIPHAUNT_MOON_TRANSFERRED_DEPS_JSON: '["liboliphaunt-wasix:engine-build", "extension-artifacts-wasix:build-target", "extension-artifacts-wasix:build-aot", "liboliphaunt-wasix:runtime-aot"]' run: .github/scripts/run-planned-moon-job.sh wasix-napi - name: Upload WASIX Node-API release assets diff --git a/.gitignore b/.gitignore index 43cebf661..46f037d34 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ node_modules/ /src/docs/out/ /src/wasix/runtime/crates/assets/assets/ /src/wasix/runtime/crates/aot/*/artifacts/ +/src/wasix/runtime/engine/crates/*/upstream/ +/src/wasix/runtime/engine/crates/*/.prepared.json +/src/wasix/runtime/engine/crates/*/prebuilt/ /src/native/sdks/react-native/ios/vendor/oliphaunt-swift/ /src/native/sdks/react-native/ios/vendor/liboliphaunt.xcframework/ **/.DS_Store diff --git a/.moon/tasks/inputs.yml b/.moon/tasks/inputs.yml index ef06d28b5..46fac3aa9 100644 --- a/.moon/tasks/inputs.yml +++ b/.moon/tasks/inputs.yml @@ -22,6 +22,8 @@ fileGroups: - "/src/third-party/postgres/COPYRIGHT" - "/src/third-party/icu/LICENSE" - "/src/third-party/openssl/LICENSE.txt" + - "/src/wasix/runtime/engine/licenses/Wasmer-LICENSE" + - "/src/wasix/runtime/engine/licenses/V8-LICENSES.txt" packaging-source: - "/tools/packaging/*.{mjs,mts,sh}" - "!/tools/packaging/*.test.{mjs,mts,sh}" @@ -42,6 +44,8 @@ fileGroups: - "/src/extensions/contrib/*.toml" - "/src/extensions/external/**/{recipe,source}.toml" - "/src/third-party/postgres/source.toml" + - "/src/wasix/runtime/engine/source.toml" + - "/src/wasix/runtime/engine/contract.mts" - "/tools/release/release-graph.mts" - "/tools/release/release-history.mts" - "/src/extensions/artifacts/packages/tools/contrib-carriers.mts" diff --git a/Cargo.lock b/Cargo.lock index 4a57d0495..b598ffd29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,15 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "1.0.0" @@ -116,6 +125,12 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arrayref" version = "0.3.9" @@ -207,12 +222,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -249,6 +293,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "2.0.1" @@ -352,6 +405,9 @@ name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +dependencies = [ + "allocator-api2", +] [[package]] name = "bumpalo-herd" @@ -518,7 +574,11 @@ version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ + "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -644,6 +704,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "370c83b49aedf022ee27942e8ae1d9de1cf40dc9653ee6550e4455d08f6406f9" +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -700,6 +770,22 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "corosensei" version = "0.3.4" @@ -749,6 +835,132 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-assembler-x64" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658e8623bea025d142fb11c299b8c6572ffe9fd663b52887c4dc124735cc130b" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b12b8c55c31c3d8598c2de9a657c3b978f905f6886c43e02216e0b15fe45a3" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.136.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c9b7896b744283c68051c6fa53dff5919bdca5f3ad30f2502cbe03346fc27c" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.136.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d44fd93e9b7d5d76846a5d7e96f4f31c84e7c84079bf69e2bf17e5d08f8d607" +dependencies = [ + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.136.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cca39a8327756e489ee65c7f2ff79a6f7c8f2837f83fef046d1899c781c59d" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli 0.33.0", + "hashbrown 0.17.1", + "libm", + "log", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26de7849112293681a19f7525c689e1d73dc6eb2687355170fb7458c4da8d353" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck 0.5.0", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df315a8b1cfd6e620971e3d4249bd2c4528167717e8f48a6317a87c55891bcb8" + +[[package]] +name = "cranelift-control" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3fff7610fb17f228f16598e1a044db79a12806ece7496ff2c69cf1a27a79a5" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.136.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589be6584f586f8b21fe17e68cb10505d1cb9a3aa42af1173b8024a12f3ec96d" +dependencies = [ + "cranelift-bitset", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.136.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413f3f1d97d6157bd2c4e2edf1f22966a849b8d524834558e563c5a4a3bc5791" +dependencies = [ + "cranelift-codegen", + "hashbrown 0.17.1", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.136.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b46ea04636aa506eb49e5a61c54270314912de7f1c33ad7f7c2c57cb0784e23" + +[[package]] +name = "cranelift-srcgen" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4944ef05513e1c1a85a945cf8f29d95ddea8bcbb19e0bfd4e71063450967420" + [[package]] name = "crc" version = "3.4.0" @@ -1125,6 +1337,45 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dynasm" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd358e74d2f8d71a11b7a2c51c67ad5df3de06003b0596875e47bc09126c894e" +dependencies = [ + "bitflags 2.13.2", + "byteorder", + "lazy_static", + "proc-macro-error3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dynasmrt" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6efe03f6bd356ae85eeea7ee14c2bf54e664a949d089d238364b5a99afbc4116" +dependencies = [ + "byteorder", + "dynasm", + "fnv", + "memmap2 0.9.10", +] + +[[package]] +name = "educe" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "either" version = "1.16.0" @@ -1172,6 +1423,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "enumset" version = "1.1.13" @@ -1312,6 +1583,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1473,8 +1759,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1513,6 +1801,18 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.34.0" @@ -1592,12 +1892,20 @@ dependencies = [ "foldhash 0.1.5", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", "serde", "serde_core", @@ -1696,78 +2004,223 @@ dependencies = [ ] [[package]] -name = "hybrid-array" -version = "0.4.12" +name = "http-body" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ - "typenum", + "bytes", + "http", ] [[package]] -name = "icu_collections" -version = "2.2.0" +name = "http-body-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "icu_locale_core" -version = "2.2.0" +name = "http-serde" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "http", + "serde", ] [[package]] -name = "icu_normalizer" -version = "2.2.0" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "icu_normalizer_data" -version = "2.2.0" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "icu_properties" -version = "2.2.0" +name = "hybrid-array" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", + "typenum", ] [[package]] -name = "icu_properties_data" -version = "2.2.0" +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" @@ -1952,6 +2405,55 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -2027,6 +2529,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "liboliphaunt-native-bindings" version = "0.2.0" @@ -2278,6 +2786,33 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +[[package]] +name = "loupe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de4e09ccbef442225e81339e930ed93dc339d4f1b2eaf479f89590ebeb9bf13" +dependencies = [ + "indexmap", + "loupe-derive", + "rustversion", +] + +[[package]] +name = "loupe-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8362a975c18ea799abfcb1902140370acd73d239f8c2f74d6e2c5f6a30929f" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + [[package]] name = "lz4_flex" version = "0.14.0" @@ -2403,6 +2938,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" +[[package]] +name = "msvc-demangler" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" +dependencies = [ + "bitflags 2.13.2", + "itoa", +] + [[package]] name = "munge" version = "0.4.7" @@ -2481,6 +3026,23 @@ dependencies = [ "libloading 0.9.0", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.30.1" @@ -2790,6 +3352,10 @@ dependencies = [ "oliphaunt-wasix-tools-aot-aarch64-unknown-linux-gnu", "oliphaunt-wasix-tools-aot-x86_64-pc-windows-msvc", "oliphaunt-wasix-tools-aot-x86_64-unknown-linux-gnu", + "oliphaunt-wasmer", + "oliphaunt-wasmer-journal", + "oliphaunt-wasmer-wasix", + "oliphaunt-wasmer-wasix-types", "serde", "serde_json", "sha2 0.10.9", @@ -2801,13 +3367,9 @@ dependencies = [ "virtual-fs", "virtual-mio", "virtual-net", - "wasmer", "wasmer-config", - "wasmer-journal", "wasmer-package", "wasmer-types", - "wasmer-wasix", - "wasmer-wasix-types", "webc", "zstd 0.13.3", ] @@ -2881,34 +3443,256 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +name = "oliphaunt-wasmer" +version = "0.3.1" +dependencies = [ + "bytes", + "corosensei", + "dashmap", + "derive_more", + "futures", + "hashbrown 0.17.1", + "indexmap", + "itertools 0.15.0", + "js-sys", + "loupe", + "more-asserts", + "oliphaunt-wasmer-v8-windows-x64-msvc", + "paste", + "rkyv", + "serde", + "serde-wasm-bindgen", + "shared-buffer", + "symbolic-demangle", + "target-lexicon", + "tempfile", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-compiler-llvm", + "wasmer-compiler-singlepass", + "wasmer-derive", + "wasmer-types", + "wasmer-vm", + "wasmparser 0.259.0", + "wat", + "windows-sys 0.61.2", +] [[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +name = "oliphaunt-wasmer-journal" +version = "0.3.1" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.23.1", + "bincode 2.0.1", + "bytecheck", + "bytes", + "derive_more", + "lz4_flex", + "num_enum", + "oliphaunt-wasmer", + "oliphaunt-wasmer-wasix-types", + "rkyv", + "serde", + "serde_json", + "shared-buffer", + "thiserror", + "tracing", + "virtual-fs", + "virtual-net", + "wasmer-config", +] + +[[package]] +name = "oliphaunt-wasmer-v8-windows-x64-msvc" +version = "0.3.1" + +[[package]] +name = "oliphaunt-wasmer-wasix" +version = "0.3.1" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.23.1", + "bincode 2.0.1", + "blake3", + "bus", + "bytecheck", + "bytes", + "chrono", + "cooked-waker", + "crossbeam-channel", + "dashmap", + "derive_more", + "flate2", + "fnv", + "fs_extra", + "futures", + "getrandom 0.3.4", + "getrandom 0.4.2", + "heapless", + "hex", + "http", + "http-body-util", + "hyper", + "hyper-util", + "itertools 0.15.0", + "js-sys", + "libc", + "libtest-mimic", + "linked_hash_set", + "lz4_flex", + "num_enum", + "oliphaunt-wasmer", + "oliphaunt-wasmer-journal", + "oliphaunt-wasmer-wasix-types", + "once_cell", + "petgraph", + "pin-project", + "pin-utils", + "rand 0.10.1", + "rayon", + "reqwest", + "rkyv", + "rusty_pool", + "semver", + "serde", + "serde_derive", + "serde_json", + "serde_yaml", + "sha2 0.11.0", + "shared-buffer", + "tempfile", + "terminal_size", + "termios", + "thiserror", + "tokio", + "tokio-stream", + "toml 1.1.2+spec-1.1.0", + "tower", + "tower-http 0.7.1", + "tracing", + "url", + "urlencoding", + "virtual-fs", + "virtual-mio", + "virtual-net", + "waker-fn", + "walkdir", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-encoder 0.259.0", + "wasmer-config", + "wasmer-package", + "wasmer-types", + "wasmparser 0.259.0", + "wcgi", + "wcgi-host", + "web-sys", + "webc", + "windows-sys 0.61.2", + "xxhash-rust", + "zstd 0.14.0", +] + +[[package]] +name = "oliphaunt-wasmer-wasix-types" +version = "0.3.1" +dependencies = [ + "anyhow", + "bitflags 2.13.2", + "byteorder", + "num_enum", + "oliphaunt-wasmer", + "serde", + "time", + "tracing", + "wai-bindgen-gen-core", + "wai-bindgen-gen-rust", + "wai-bindgen-gen-rust-wasm", + "wai-bindgen-rust", + "wai-parser", + "wasmer-derive", + "wasmer-types", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -3189,6 +3973,16 @@ dependencies = [ "quote", ] +[[package]] +name = "proc-macro-error-attr3" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e564d14133360e1ae169ffde5da25881b5fa47261665b8e5713c212c27799da" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro-error2" version = "2.0.1" @@ -3201,6 +3995,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "proc-macro-error3" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0d4471b3436c22106b21913b1dda531558918ae9b7ec55d58aa84b43552233" +dependencies = [ + "proc-macro-error-attr3", + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -3264,6 +4070,63 @@ dependencies = [ "unicase", ] +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.2", + "lru-slab", + "rand 0.10.1", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -3370,6 +4233,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rangemap" version = "1.7.1" @@ -3445,6 +4317,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "regalloc2" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.17.1", + "log", + "rustc-hash", + "smallvec", +] + [[package]] name = "regex" version = "1.12.3" @@ -3507,6 +4393,64 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http 0.6.11", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rkyv" version = "0.8.16" @@ -3595,6 +4539,81 @@ dependencies = [ "rustix", ] +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -3648,6 +4667,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "1.2.1" @@ -3701,6 +4729,29 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -3738,6 +4789,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3872,12 +4933,32 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -4134,6 +5215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1245acf80236b4a0d99e9216532102a1670950e79c70b980b607c2040966e83d" dependencies = [ "cpp_demangle 0.4.5", + "msvc-demangler", "rustc-demangle", "symbolic-common", ] @@ -4171,6 +5253,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -4336,6 +5427,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -4352,6 +5444,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-postgres" version = "0.7.18" @@ -4378,6 +5480,31 @@ dependencies = [ "whoami 2.1.2", ] +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-serde" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf600e7036b17782571dd44fa0a5cea3c82f60db5137f774a325a76a0d6852b" +dependencies = [ + "bincode 1.3.3", + "bytes", + "educe", + "futures-core", + "futures-sink", + "pin-project", + "serde", +] + [[package]] name = "tokio-stream" version = "0.1.18" @@ -4478,6 +5605,71 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-http" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -4526,6 +5718,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "twox-hash" version = "2.1.2" @@ -4720,6 +5918,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "unty" version = "0.0.4" @@ -4805,6 +6009,7 @@ dependencies = [ "tracing", "virtual-mio", "wasmer-package", + "web-time", "webc", ] @@ -4834,7 +6039,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.23.1", - "bincode", + "bincode 2.0.1", "bytecheck", "bytes", "derive_more", @@ -4850,6 +6055,8 @@ dependencies = [ "socket2", "thiserror", "tokio", + "tokio-serde", + "tokio-util", "tracing", "virtual-mio", ] @@ -4942,6 +6149,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -5003,6 +6219,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.122" @@ -5064,6 +6290,16 @@ dependencies = [ "wasmparser 0.259.0", ] +[[package]] +name = "wasm-encoder" +version = "0.261.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2608e8bb6d67fd68f5a8d0eb1363d6e7bcbc1f8ded5a0bd3a1e382462b876b22" +dependencies = [ + "leb128fmt", + "wasmparser 0.261.0", +] + [[package]] name = "wasm-metadata" version = "0.244.0" @@ -5076,6 +6312,19 @@ dependencies = [ "wasmparser 0.244.0", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmer" version = "7.5.0" @@ -5129,6 +6378,7 @@ dependencies = [ "leb128", "libc", "libwild", + "loupe", "macho-unwind-info", "memmap2 0.9.10", "more-asserts", @@ -5139,6 +6389,8 @@ dependencies = [ "region", "rkyv", "self_cell", + "serde", + "serde_bytes", "shared-buffer", "smallvec", "strum", @@ -5152,6 +6404,29 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "wasmer-compiler-cranelift" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3554998433d7f02c7154b663e3f8f2337b3476968ef341ce1d527eeef2bb6" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "indexmap", + "itertools 0.15.0", + "leb128", + "more-asserts", + "object 0.40.0", + "rayon", + "regalloc2", + "smallvec", + "target-lexicon", + "tracing", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-compiler-llvm" version = "7.5.0" @@ -5180,6 +6455,28 @@ dependencies = [ "wasmer-vm", ] +[[package]] +name = "wasmer-compiler-singlepass" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece6505e2270f1083d4e677a7cf36d11870f682c3b05fd0a4687094ef85fdd5b" +dependencies = [ + "byteorder", + "dynasm", + "dynasmrt", + "enumset", + "fixedbitset", + "gimli 0.34.0", + "itertools 0.15.0", + "more-asserts", + "object 0.40.0", + "rayon", + "smallvec", + "target-lexicon", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-config" version = "0.705.0" @@ -5224,7 +6521,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.23.1", - "bincode", + "bincode 2.0.1", "bytecheck", "bytes", "derive_more", @@ -5284,10 +6581,12 @@ dependencies = [ "hex", "indexmap", "itertools 0.15.0", + "loupe", "more-asserts", "num_enum", "rkyv", "serde", + "serde_bytes", "sha2 0.11.0", "target-lexicon", "thiserror", @@ -5313,12 +6612,14 @@ dependencies = [ "itertools 0.15.0", "libc", "libunwind", + "loupe", "mach2", "more-asserts", "parking_lot", "region", "rustversion", "scopeguard", + "serde", "thiserror", "wasmer-types", "windows-sys 0.61.2", @@ -5333,7 +6634,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.23.1", - "bincode", + "bincode 2.0.1", "blake3", "bus", "bytecheck", @@ -5469,6 +6770,63 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmtime-internal-core" +version = "49.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f24a558f82f07e4e76a8b848288901d9f663e4e95c25f6d73627daca5f4ce93" +dependencies = [ + "hashbrown 0.17.1", + "libm", +] + +[[package]] +name = "wast" +version = "261.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "776443145731a4062e5b0d392892a2005909b6ab72d9fdc3cad53dd1a714e44a" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.261.0", +] + +[[package]] +name = "wat" +version = "1.261.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b4d1a49ea73a8f3326e74e3a05db667001b16bd1035ed3356fc1a0ed05ca7f" +dependencies = [ + "wast", +] + +[[package]] +name = "wcgi" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3977728575e8a79833db34f835b810dd7e1affdec0fa18cf141f060421cade6a" +dependencies = [ + "http", + "http-serde", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "wcgi-host" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d78a0358eebb970afcacc158f4172d2b7355d37572b350f1ed46dc44fe2e464" +dependencies = [ + "http", + "serde", + "tokio", + "wcgi", +] + [[package]] name = "web-sys" version = "0.3.99" @@ -5479,6 +6837,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webc" version = "12.0.1" @@ -5507,6 +6875,15 @@ dependencies = [ "url", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weedle2" version = "5.0.0" @@ -5579,12 +6956,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -5594,6 +7024,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -5874,13 +7313,13 @@ version = "0.0.0" dependencies = [ "anyhow", "async-trait", + "oliphaunt-wasmer", "serde", "serde_json", "sha2 0.10.9", "tar", "toml 0.9.12+spec-1.1.0", "walkdir", - "wasmer", "wasmer-types", "wasmparser 0.261.0", "zstd 0.13.3", @@ -5956,6 +7395,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zerotrie" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 36dee7f08..7ff63baff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,12 @@ members = [ "src/native/runtime/packaging", "src/native/postgres-tools/tests", "src/wasix/runtime/tools/xtask", + "src/wasix/runtime/engine/crates/wasmer", + "src/wasix/runtime/engine/crates/wasmer-wasix-types", + "src/wasix/runtime/engine/crates/wasmer-journal", + "src/wasix/runtime/engine/crates/wasmer-wasix", + "src/wasix/runtime/engine/crates/v8-windows-x64-msvc", + ] resolver = "3" diff --git a/release-please-config.json b/release-please-config.json index 7d7334c6e..eb54d8d29 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -311,6 +311,31 @@ "type": "toml", "path": "crates/aot/x86_64-unknown-linux-gnu/Cargo.toml", "jsonpath": "$.package.version" + }, + { + "type": "toml", + "path": "engine/crates/wasmer/Cargo.toml", + "jsonpath": "$.package.version" + }, + { + "type": "toml", + "path": "engine/crates/wasmer-wasix/Cargo.toml", + "jsonpath": "$.package.version" + }, + { + "type": "toml", + "path": "engine/crates/wasmer-wasix-types/Cargo.toml", + "jsonpath": "$.package.version" + }, + { + "type": "toml", + "path": "engine/crates/wasmer-journal/Cargo.toml", + "jsonpath": "$.package.version" + }, + { + "type": "toml", + "path": "engine/crates/v8-windows-x64-msvc/Cargo.toml", + "jsonpath": "$.package.version" } ] }, diff --git a/src/docs/maintainers/development.md b/src/docs/maintainers/development.md index 734b1c3ba..581d1cb1d 100644 --- a/src/docs/maintainers/development.md +++ b/src/docs/maintainers/development.md @@ -296,13 +296,20 @@ bash src/wasix/runtime/tools/build-aot-target.sh moon run liboliphaunt-wasix:smoke ``` -Local AOT generation requires the Wasmer LLVM 22.1.x build for the -maintainer-only serializer. That build includes the LLVM target set Wasmer's +On Linux and macOS, local AOT generation requires the Wasmer LLVM 22.1.x build +for the maintainer-only serializer. That build includes the LLVM target set Wasmer's LLVM backend expects, including LoongArch and WebAssembly. Set `LLVM_SYS_221_PREFIX` to an extracted `wasmerio/llvm-custom-builds` 22.x archive, or use downloaded-artifact mode to avoid local LLVM setup. +Windows AOT uses the bundled V8 engine and same-commit native cache profiles. +The [WASIX host workflow](../../../.github/workflows/wasix-host.yml) builds and +tests the engine on Windows, produces the SSE4.1 baseline cache on Linux, and +transfers both outputs into Windows AOT generation. Use downloaded-artifact +mode for local Windows runtime testing; the serialization commands below +require those producer outputs. + When the portable WASIX assets are already current and only the host AOT crate needs to be refreshed, skip the source/Docker build and generate host AOT from the existing generated portable assets: diff --git a/src/wasix/node-addon/packages/win32-x64-msvc/package.json b/src/wasix/node-addon/packages/win32-x64-msvc/package.json index 1d3477ad7..949c82549 100644 --- a/src/wasix/node-addon/packages/win32-x64-msvc/package.json +++ b/src/wasix/node-addon/packages/win32-x64-msvc/package.json @@ -2,7 +2,7 @@ "name": "@oliphaunt/wasix-napi-win32-x64-msvc", "version": "0.2.0", "description": "Windows x64 MSVC prebuilt Oliphaunt WASIX Node-API runtime.", - "license": "MIT AND PostgreSQL AND Unicode-3.0 AND Apache-2.0", + "license": "MIT AND PostgreSQL AND Unicode-3.0 AND Apache-2.0 AND BSD-3-Clause AND BSD-2-Clause AND SunPro AND CC0-1.0 AND Zlib AND Python-2.0", "type": "commonjs", "repository": { "type": "git", diff --git a/src/wasix/node-addon/tools/check-release-assets.mts b/src/wasix/node-addon/tools/check-release-assets.mts index c479b8699..33aecb6a9 100644 --- a/src/wasix/node-addon/tools/check-release-assets.mts +++ b/src/wasix/node-addon/tools/check-release-assets.mts @@ -4,7 +4,7 @@ import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { workspaceCarrierManifest } from './workspace-runtime-contract.mts'; +import { wasixNapiNoticeProfile, workspaceCarrierManifest } from './workspace-runtime-contract.mts'; import { finalizeHelperAssets } from '../../../../tools/packaging/finalize-helper-assets.mts'; import { inspectPlatformBinaryEntries } from '../../../../tools/packaging/platform-binary-contract.mts'; import { readPortableArchiveEntries } from '../../../../tools/packaging/portable-archive.mts'; @@ -37,8 +37,6 @@ import { const PREFIX = 'check-wasix-napi-release-assets.mts'; const PRODUCT = 'oliphaunt-wasix-napi'; const KIND = 'wasix-napi-addon'; -const PROFILE = 'wasix-napi-addon'; -const PACKAGE_LICENSE = releaseProfilePackageLicense(PROFILE).spdx; const BINARY = 'oliphaunt_wasix_napi.node'; const PRODUCT_MANIFEST = JSON.parse( readFileSync(path.join(ROOT, 'src/wasix/node-addon/package.json'), 'utf8'), @@ -217,9 +215,10 @@ export function assertWasixNapiCarrierManifest( if (manifest.name !== target.npmPackage || manifest.version !== version) { throw new Error(`${label} must identify ${target.npmPackage}@${version}`); } - if (manifest.license !== PACKAGE_LICENSE) { + const packageLicense = releaseProfilePackageLicense(wasixNapiNoticeProfile(target.target)).spdx; + if (manifest.license !== packageLicense) { throw new Error( - `${label} package license must be ${PACKAGE_LICENSE}, got ${JSON.stringify(manifest.license)}`, + `${label} package license must be ${packageLicense}, got ${JSON.stringify(manifest.license)}`, ); } if ( @@ -328,7 +327,11 @@ export function assertWasixNapiPlatformEntries( } function assertPayload(entries, { prefix = '', label, target, version, npm = false, contract }) { - assertReleaseNoticesInEntries(entries, { profile: PROFILE, prefix, label }); + assertReleaseNoticesInEntries(entries, { + profile: wasixNapiNoticeProfile(target.target), + prefix, + label, + }); const member = (name) => (prefix ? `${prefix}/${name}` : name); const provenance = archiveJson(entries, member('artifact-provenance.json'), label); if ( diff --git a/src/wasix/node-addon/tools/package-platform.mts b/src/wasix/node-addon/tools/package-platform.mts index 19165f3e9..4a3249eec 100755 --- a/src/wasix/node-addon/tools/package-platform.mts +++ b/src/wasix/node-addon/tools/package-platform.mts @@ -4,7 +4,7 @@ import { createHash } from 'node:crypto'; import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { workspaceCarrierManifest } from './workspace-runtime-contract.mts'; +import { wasixNapiNoticeProfile, workspaceCarrierManifest } from './workspace-runtime-contract.mts'; import { archiveDirectory } from '../../../../tools/packaging/archive-directory.mts'; import { readPortableArchiveEntries } from '../../../../tools/packaging/portable-archive.mts'; @@ -129,7 +129,7 @@ async function main() { const packagePrebuilds = path.join(packageWork, 'prebuilds'); mkdirSync(packagePrebuilds, { recursive: true }); cpSync(path.join(prebuildDirectory, BINARY), path.join(packagePrebuilds, BINARY)); - stageReleaseNotices(packageWork, { profile: 'wasix-napi-addon' }); + stageReleaseNotices(packageWork, { profile: wasixNapiNoticeProfile(target) }); const windowsRuntimeNames = target === 'windows-x64-msvc' ? stageWindowsVcRuntime({ @@ -175,7 +175,7 @@ async function main() { mkdirSync(releaseStage, { recursive: true }); mkdirSync(releaseAssets, { recursive: true }); cpSync(path.join(prebuildDirectory, BINARY), path.join(releaseStage, BINARY)); - stageReleaseNotices(releaseStage, { profile: 'wasix-napi-addon' }); + stageReleaseNotices(releaseStage, { profile: wasixNapiNoticeProfile(target) }); cpSync( path.join(packageWork, 'artifact-provenance.json'), path.join(releaseStage, 'artifact-provenance.json'), diff --git a/src/wasix/node-addon/tools/workspace-runtime-contract.mts b/src/wasix/node-addon/tools/workspace-runtime-contract.mts index f3af2e276..32fd20e8b 100644 --- a/src/wasix/node-addon/tools/workspace-runtime-contract.mts +++ b/src/wasix/node-addon/tools/workspace-runtime-contract.mts @@ -1,10 +1,15 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { releaseProfilePackageLicense } from '../../../../tools/packaging/release-notices.mts'; import { readCargoPackageNameVersion } from '../../../../tools/packaging/cargo-source-package.mts'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..'); +export function wasixNapiNoticeProfile(target) { + return target === 'windows-x64-msvc' ? 'wasix-napi-addon-windows' : 'wasix-napi-addon'; +} + export function workspaceRuntimeVersion(root = ROOT) { const version = readFileSync(path.join(root, 'src/wasix/runtime/VERSION'), 'utf8').trim(); if (!/^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/u.test(version)) { @@ -23,6 +28,7 @@ export function workspaceCarrierManifest(manifest, declaredContract, root = ROOT ).version; return { ...manifest, + license: releaseProfilePackageLicense(wasixNapiNoticeProfile(manifest.oliphaunt.target)).spdx, oliphaunt: { ...manifest.oliphaunt, runtimeVersion, diff --git a/src/wasix/pgwire-server/moon.yml b/src/wasix/pgwire-server/moon.yml index b5bec2db1..3e6ec8f32 100644 --- a/src/wasix/pgwire-server/moon.yml +++ b/src/wasix/pgwire-server/moon.yml @@ -39,6 +39,8 @@ tasks: tags: ["build", "requires-rust"] inputs: ["@group(sources)", "/Cargo.toml", "/Cargo.lock"] outputs: ["/target/debug/oliphaunt-pgwire-server*", "/target/debug/liboliphaunt_pgwire_server.rlib"] + options: + mutex: "oliphaunt-pgwire-server" test: deps: - target: cargo-sources @@ -46,6 +48,8 @@ tasks: command: "cargo test --locked" tags: ["quality", "unit", "requires-rust"] inputs: ["@group(sources)", "README.md", "/Cargo.toml", "/Cargo.lock"] + options: + mutex: "oliphaunt-pgwire-server" test-integration: command: "cargo test --locked -- --ignored --test-threads=1" tags: ["runtime", "integration", "requires-rust", "ci-liboliphaunt-wasix-aot"] @@ -53,6 +57,8 @@ tasks: inputs: ["@group(sources)", "/Cargo.toml", "/Cargo.lock", "/src/extensions/catalog/extensions.source.json", "/src/test-fixtures/extensions/**/*"] options: cache: false + # Cargo releases its build lock while the CLI smoke test runs the executable. + mutex: "oliphaunt-pgwire-server" test-aot: command: "bash src/wasix/pgwire-server/tools/test-aot.sh" tags: ["runtime", "integration", "requires-rust", "ci-liboliphaunt-wasix-aot"] @@ -60,6 +66,7 @@ tasks: inputs: ["@group(sources)", "tools/test-aot.sh", "/Cargo.toml", "/Cargo.lock"] options: cache: false + mutex: "oliphaunt-pgwire-server" runFromWorkspaceRoot: true package: command: "bash src/wasix/pgwire-server/tools/package.sh" diff --git a/src/wasix/pgwire-server/src/server.rs b/src/wasix/pgwire-server/src/server.rs index 8e1a12bcf..f89baf429 100644 --- a/src/wasix/pgwire-server/src/server.rs +++ b/src/wasix/pgwire-server/src/server.rs @@ -7,7 +7,10 @@ use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(unix)] use std::os::unix::net::{UnixListener, UnixStream}; -use std::path::{Path, PathBuf}; +#[cfg(unix)] +use std::path::Path; +#[cfg(any(unix, all(test, feature = "icu")))] +use std::path::PathBuf; use std::sync::{ Arc, atomic::{AtomicBool, Ordering}, diff --git a/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs b/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs index e8ca99b64..6e138809d 100644 --- a/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs +++ b/src/wasix/postgres-tools/crates/aot/x86_64-pc-windows-msvc/build-support.rs @@ -32,6 +32,9 @@ fn main() { } fn emit_expected_artifact_inputs(target: &str) { + if PACKAGE_LOCAL { + return; + } if let Some(path) = env::var_os("OLIPHAUNT_WASIX_TOOLS_AOT_DIR") { let path = PathBuf::from(path); let candidate = if path.ends_with(target) { @@ -73,6 +76,11 @@ fn find_artifact_dir(target: &str) -> Option { } if PACKAGE_LOCAL { + let reconstructed = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is set by Cargo")) + .join("artifacts"); + if reconstructed.join("manifest.json").is_file() { + return Some(reconstructed); + } panic!("published WASIX carrier requires package-local artifacts"); } @@ -108,6 +116,10 @@ fn repo_root_from_manifest_dir(manifest_dir: &Path) -> Option<&Path> { } fn emit_rerun_directives(artifact_dir: &Path) { + // Reconstructed payloads are build outputs; their part crates are Cargo inputs. + if artifact_dir.starts_with(env::var_os("OUT_DIR").expect("OUT_DIR is set by Cargo")) { + return; + } println!("cargo:rerun-if-changed={}", artifact_dir.display()); if let Ok(entries) = fs::read_dir(artifact_dir) { for entry in entries.flatten() { @@ -144,7 +156,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { let Some(file_name) = file.file_name().and_then(|name| name.to_str()) else { continue; }; - let Some(stem) = file_name.strip_suffix("-llvm-opta.bin.zst") else { + let Some(stem) = file_name.strip_suffix("-v8.bin.zst") else { continue; }; let artifact_name = artifact_name_from_file_stem(stem); @@ -162,7 +174,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { let text = format!( "pub const TARGET_TRIPLE: &str = {:?};\n\ - pub const ENGINE: &str = \"llvm-opta\";\n\ + pub const ENGINE: &str = \"v8\";\n\ pub const HAS_EMBEDDED_AOT: bool = true;\n\ pub const MANIFEST_JSON: &str = include_str!({});\n\ #[rustfmt::skip]\n\ @@ -188,11 +200,11 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"v8\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ - pub const ENGINE: &str = \"llvm-opta\";\n\ + pub const ENGINE: &str = \"v8\";\n\ pub const HAS_EMBEDDED_AOT: bool = false;\n\ pub const MANIFEST_JSON: &str = r#\"{manifest}\"#;\n\ pub fn artifact_bytes(_name: &str) -> Option<&'static [u8]> {{ None }}\n" @@ -233,7 +245,7 @@ fn write_core_aot_manifest(source: &Path, destination: &Path) -> Vec { serde_json::from_str(&text).expect("parse generated WASIX AOT manifest"); assert_eq!( manifest.get("engine").and_then(serde_json::Value::as_str), - Some("llvm-opta"), + Some("v8"), "stale WASIX AOT profile; rebuild artifacts before compiling the carrier" ); let artifacts = manifest diff --git a/src/wasix/postgres-tools/tools/package-cargo-artifacts.mts b/src/wasix/postgres-tools/tools/package-cargo-artifacts.mts index 9e3d85c32..e37a0d979 100644 --- a/src/wasix/postgres-tools/tools/package-cargo-artifacts.mts +++ b/src/wasix/postgres-tools/tools/package-cargo-artifacts.mts @@ -9,7 +9,7 @@ import { WASIX_CARGO_ARTIFACT_SCHEMA, } from '../../runtime/tools/wasix-cargo-artifact-contract.mts'; import { extractPortableArchiveTree } from '../../../../tools/packaging/portable-archive.mts'; -import { packageSpec } from '../../../../tools/packaging/wasix-cargo-payload.mts'; +import { packageAotSpec, packageSpec } from '../../../../tools/packaging/wasix-cargo-payload.mts'; import { currentProductVersionSync } from '../../../../tools/release/release-artifact-targets.mts'; import { fail, @@ -38,7 +38,7 @@ export function packageWasixToolsCargoArtifacts(argv) { mkdirSync(directory, { recursive: true }); } const targets = values.target ?? ['portable', ...Object.keys(AOT_TARGET_TRIPLES)]; - const packages = targets.map((target) => { + const packages = targets.flatMap((target) => { const triple = AOT_TARGET_TRIPLES[target]; if (target !== 'portable' && !triple) fail(`unsupported WASIX tools target ${target}`); const payloadRoot = path.join(extracted, target); @@ -50,26 +50,25 @@ export function packageWasixToolsCargoArtifacts(argv) { extractPortableArchiveTree(archive, payloadRoot); if (triple) validateToolsAotPayload(payloadRoot, triple); else validatePortableToolsPayload(payloadRoot, values.version); - return packageSpec( - { - name: triple ? TOOLS_AOT_PACKAGES[target] : TOOLS_PACKAGE, - target: triple ?? 'portable', - kind: triple ? 'wasix-tools-aot' : 'wasix-tools', - templateDir: path.join( - ROOT, - 'src/wasix/postgres-tools/crates', - triple ? `aot/${triple}` : 'tools', - ), - payloadRoot, - payloadDirName: triple ? 'artifacts' : 'payload', - }, - { - version: values.version, - sourceRoot, - outputDir, - cargoTargetDir: path.join(work, 'cargo-package-target'), - }, - ); + const spec = { + name: triple ? TOOLS_AOT_PACKAGES[target] : TOOLS_PACKAGE, + target: triple ?? 'portable', + kind: triple ? 'wasix-tools-aot' : 'wasix-tools', + templateDir: path.join( + ROOT, + 'src/wasix/postgres-tools/crates', + triple ? `aot/${triple}` : 'tools', + ), + payloadRoot, + payloadDirName: triple ? 'artifacts' : 'payload', + }; + const options = { + version: values.version, + sourceRoot, + outputDir, + cargoTargetDir: path.join(work, 'cargo-package-target'), + }; + return triple ? packageAotSpec(spec, options) : [packageSpec(spec, options)]; }); const relative = (value) => path.relative(ROOT, value).split(path.sep).join('/'); writeFileSync( diff --git a/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs b/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs index 7d317c193..bc0921dea 100644 --- a/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs +++ b/src/wasix/runtime/crates/aot/x86_64-pc-windows-msvc/build-support.rs @@ -32,6 +32,9 @@ fn main() { } fn emit_expected_artifact_inputs(target: &str) { + if PACKAGE_LOCAL { + return; + } if let Some(path) = env::var_os("OLIPHAUNT_WASM_GENERATED_AOT_DIR") { let path = PathBuf::from(path); let candidate = if path.ends_with(target) { @@ -69,6 +72,11 @@ fn find_artifact_dir(target: &str) -> Option { } if PACKAGE_LOCAL { + let reconstructed = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is set by Cargo")) + .join("artifacts"); + if reconstructed.join("manifest.json").is_file() { + return Some(reconstructed); + } panic!("published WASIX carrier requires package-local artifacts"); } @@ -102,6 +110,10 @@ fn repo_root_from_manifest_dir(manifest_dir: &Path) -> Option<&Path> { } fn emit_rerun_directives(artifact_dir: &Path) { + // Reconstructed payloads are build outputs; their part crates are Cargo inputs. + if artifact_dir.starts_with(env::var_os("OUT_DIR").expect("OUT_DIR is set by Cargo")) { + return; + } println!("cargo:rerun-if-changed={}", artifact_dir.display()); if let Ok(entries) = fs::read_dir(artifact_dir) { for entry in entries.flatten() { @@ -138,7 +150,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { let Some(file_name) = file.file_name().and_then(|name| name.to_str()) else { continue; }; - let Some(stem) = file_name.strip_suffix("-llvm-opta.bin.zst") else { + let Some(stem) = file_name.strip_suffix("-v8.bin.zst") else { continue; }; let artifact_name = artifact_name_from_file_stem(stem); @@ -156,7 +168,7 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { let text = format!( "pub const TARGET_TRIPLE: &str = {:?};\n\ - pub const ENGINE: &str = \"llvm-opta\";\n\ + pub const ENGINE: &str = \"v8\";\n\ pub const HAS_EMBEDDED_AOT: bool = true;\n\ pub const MANIFEST_JSON: &str = include_str!({});\n\ #[rustfmt::skip]\n\ @@ -182,11 +194,11 @@ fn write_generated_aot(out: &Path, target: &str, artifact_dir: &Path) { fn write_source_only_aot(out: &Path, target: &str) { let manifest = format!( - "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"llvm-opta\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" + "{{\"format-version\":1,\"target-triple\":{target:?},\"engine\":\"v8\",\"wasmer-version\":\"7.5.0\",\"wasmer-wasix-version\":\"0.705.0\",\"artifacts\":[]}}" ); let text = format!( "pub const TARGET_TRIPLE: &str = {target:?};\n\ - pub const ENGINE: &str = \"llvm-opta\";\n\ + pub const ENGINE: &str = \"v8\";\n\ pub const HAS_EMBEDDED_AOT: bool = false;\n\ pub const MANIFEST_JSON: &str = r#\"{manifest}\"#;\n\ pub fn artifact_bytes(_name: &str) -> Option<&'static [u8]> {{ None }}\n" @@ -227,7 +239,7 @@ fn write_core_aot_manifest(source: &Path, destination: &Path) -> Vec { serde_json::from_str(&text).expect("parse generated WASIX AOT manifest"); assert_eq!( manifest.get("engine").and_then(serde_json::Value::as_str), - Some("llvm-opta"), + Some("v8"), "stale WASIX AOT profile; rebuild artifacts before compiling the carrier" ); let artifacts = manifest diff --git a/src/wasix/runtime/engine/README.md b/src/wasix/runtime/engine/README.md new file mode 100644 index 000000000..f791936fd --- /dev/null +++ b/src/wasix/runtime/engine/README.md @@ -0,0 +1,40 @@ +# Native WASIX engine + +This directory owns the private Wasmer/WASIX source family used by the Rust SDK +and Node addon. Its package versions follow `liboliphaunt-wasix`; upstream versions +and immutable input digests live in `source.toml`. The browser engine remains a +separate toolchain. + +Linux and macOS use the existing LLVM producer and headless loader. Windows uses +V8 because Wasmer 7.5 no longer supports its LLVM backend there. All consumers +receive precompiled artifacts and the Windows engine DLL through their normal +SDK packages. Runtime loading never compiles a missing guest or extension. + +The desktop x64 minimum is SSE4.1. Windows artifacts contain genuine native +caches for CPUs with and without CET, compiled with the same pinned DLL and +flags. V8 validates the original cache headers and selects a compatible profile; +we do not rewrite CPU masks or fall back to compilation. + +The ordered Wasmer patches cover engine delivery, C API ownership, native cache +profiles, and shared memory/terminal lifetimes. The WASIX patches cover optional +runner features, cached module loading, and terminal shutdown. The complete +ordered series is the build and qualification unit. +These are internal compatibility patches, not a promise to support every V8 +backend API exposed upstream. + +`windows/bindings.rs` retains the generated C ABI for the pinned Wasmer header; +the producer verifies that header's digest before generating DLL dispatch. +Consumers use the frozen bindings and need no binding generator or native +engine build. + +Use `moon run liboliphaunt-wasix:engine-sources` to replay the pinned source +patches. The Windows host workflow produces the DLL and baseline caches before +normal AOT, runtime, extension, and SDK checks. Packaging freezes those same-run +inputs using the existing deterministic Cargo carrier helpers, verifies source +and byte identities, and includes the upstream notices. Release assembly +consumes these outputs without building an engine. + +Qualification must cover all desktop targets, the complete extension catalog, +and installed Rust/npm consumers. Research artifacts do not qualify a production +commit. Changes to source pins, patches, flags, or DLL bindings require fresh +native caches and qualification. diff --git a/src/wasix/runtime/engine/contract.mts b/src/wasix/runtime/engine/contract.mts new file mode 100644 index 000000000..514349bc5 --- /dev/null +++ b/src/wasix/runtime/engine/contract.mts @@ -0,0 +1,16 @@ +export const ENGINE_SOURCE_CRATES = Object.freeze([ + 'wasmer', + 'wasmer-wasix-types', + 'wasmer-journal', + 'wasmer-wasix', +]); + +export const ENGINE_PAYLOAD_PACKAGE = 'oliphaunt-wasmer-v8-windows-x64-msvc'; +export function isEnginePayloadPart(name: string): boolean { + return new RegExp(`^${ENGINE_PAYLOAD_PACKAGE}-part-(?!000)[0-9]{3}$`, 'u').test(name); +} + +export const ENGINE_CARGO_PACKAGES = Object.freeze([ + ...ENGINE_SOURCE_CRATES.map((name) => `oliphaunt-${name}`), + ENGINE_PAYLOAD_PACKAGE, +]); diff --git a/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/Cargo.toml b/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/Cargo.toml new file mode 100644 index 000000000..818fd0c8b --- /dev/null +++ b/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "oliphaunt-wasmer-v8-windows-x64-msvc" +version = "0.3.1" +edition = "2024" +rust-version = "1.96" +description = "Bundled Windows V8 engine for the Oliphaunt WASIX runtime" +repository = "https://github.com/f0rr0/oliphaunt" +license = "MIT AND BSD-3-Clause AND BSD-2-Clause AND Apache-2.0 AND Unicode-3.0 AND SunPro AND CC0-1.0 AND Zlib AND Python-2.0" +publish = false + +[lib] +path = "src/lib.rs" diff --git a/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/build.rs b/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/build.rs new file mode 100644 index 000000000..65f30c997 --- /dev/null +++ b/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/build.rs @@ -0,0 +1,12 @@ +use std::{env, fs, path::PathBuf}; + +fn main() { + let root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); + let source = root.join( + "../../../../../../target/oliphaunt-wasix/engine/windows-x64-msvc/oliphaunt_wee8.dll", + ); + println!("cargo:rerun-if-changed={}", source.display()); + let output = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + fs::copy(&source, output.join("oliphaunt_wee8.dll")) + .expect("build the Windows engine through liboliphaunt-wasix:engine-build first"); +} diff --git a/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/src/lib.rs b/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/src/lib.rs new file mode 100644 index 000000000..9adc832ae --- /dev/null +++ b/src/wasix/runtime/engine/crates/v8-windows-x64-msvc/src/lib.rs @@ -0,0 +1,7 @@ +//! Internal build carrier. Release packaging supplies the same verified engine bytes. +pub fn write_dll(path: &std::path::Path) -> std::io::Result<()> { + std::fs::write( + path, + include_bytes!(concat!(env!("OUT_DIR"), "/oliphaunt_wee8.dll")), + ) +} diff --git a/src/wasix/runtime/engine/crates/wasmer-journal/Cargo.toml b/src/wasix/runtime/engine/crates/wasmer-journal/Cargo.toml new file mode 100644 index 000000000..ec7edfbc1 --- /dev/null +++ b/src/wasix/runtime/engine/crates/wasmer-journal/Cargo.toml @@ -0,0 +1,120 @@ +[package] +edition = "2024" +rust-version = "1.96" +name = "oliphaunt-wasmer-journal" +version = "0.3.1" +authors = ["Wasmer Engineering Team "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Journaling functionality used by Wasmer to save and restore WASM state" +homepage = "https://wasmer.io/" +readme = false +license = "MIT" +repository = "https://github.com/wasmerio/wasmer" + +[package.metadata.docs.rs] +features = [ + "wasmer/sys", + "wasmer/compiler", +] +rustc-args = [ + "--cfg", + "docsrs", +] + +[features] +default = [ + "log-file", + "wasmer/sys-default", +] +log-file = ["shared-buffer"] + +[lib] +name = "wasmer_journal" +path = "upstream/src/lib.rs" + +[dependencies.anyhow] +version = "1.0" + +[dependencies.async-trait] +version = "0.1.68" + +[dependencies.base64] +version = "0.23.0" + +[dependencies.bincode] +version = "=2.0.1" + +[dependencies.bytecheck] +version = "0.8.2" + +[dependencies.bytes] +version = "1.11.1" + +[dependencies.derive_more] +version = "2.0.1" +features = [ + "debug", + "display", +] + +[dependencies.lz4_flex] +version = "0.14.0" + +[dependencies.num_enum] +version = "0.7.3" + +[dependencies.rkyv] +version = "0.8.13" +features = [ + "indexmap-2", + "bytes-1", +] + +[dependencies.serde] +version = "1" +features = ["derive"] +default-features = false + +[dependencies.serde_json] +version = "1" + +[dependencies.shared-buffer] +version = "0.1.4" +optional = true + +[dependencies.thiserror] +version = "2.0.17" + +[dependencies.tracing] +version = "0.1" +default-features = false + +[dependencies.virtual-fs] +version = "0.705.0" +default-features = false + +[dependencies.virtual-net] +version = "0.705.0" +features = ["rkyv"] +default-features = false + +[dependencies.wasmer] +package = "oliphaunt-wasmer" +path = "../wasmer" +version = "*" +default-features = false + +[dependencies.wasmer-config] +version = "=0.705.0" +default-features = false + +[dependencies.wasmer-wasix-types] +package = "oliphaunt-wasmer-wasix-types" +path = "../wasmer-wasix-types" +version = "*" +features = ["enable-serde"] diff --git a/src/wasix/runtime/engine/crates/wasmer-wasix-types/Cargo.toml b/src/wasix/runtime/engine/crates/wasmer-wasix-types/Cargo.toml new file mode 100644 index 000000000..11eb9168a --- /dev/null +++ b/src/wasix/runtime/engine/crates/wasmer-wasix-types/Cargo.toml @@ -0,0 +1,107 @@ +[package] +edition = "2024" +rust-version = "1.96" +name = "oliphaunt-wasmer-wasix-types" +version = "0.3.1" +authors = ["Wasmer Engineering Team "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "WASI and WASIX types for Wasmer WebAssembly runtime" +homepage = "https://wasmer.io/" +readme = "upstream/README.md" +keywords = [ + "wasm", + "webassembly", + "wasi", + "sandbox", + "ABI", +] +categories = [ + "wasm", + "os", +] +license = "MIT" +repository = "https://github.com/wasmerio/wasmer" + +[package.metadata.docs.rs] +features = [ + "wasmer/sys", + "wasmer/cranelift", +] +rustc-args = [ + "--cfg", + "docsrs", +] + +[features] +enable-serde = [ + "serde", + "wasmer-types/serde", +] + +[lib] +name = "wasmer_wasix_types" +path = "upstream/src/lib.rs" + +[dependencies.anyhow] +version = "1.0" + +[dependencies.bitflags] +version = "2.13.2" + +[dependencies.byteorder] +version = "1.3" + +[dependencies.num_enum] +version = "0.7.3" + +[dependencies.serde] +version = "1" +features = ["derive"] +optional = true +default-features = false + +[dependencies.time] +version = "0.3.47" +features = ["formatting"] +default-features = false + +[dependencies.tracing] +version = "0.1" +default-features = false + +[dependencies.wai-bindgen-gen-core] +version = "0.2.1" +default-features = false + +[dependencies.wai-bindgen-gen-rust] +version = "0.2.1" +default-features = false + +[dependencies.wai-bindgen-gen-rust-wasm] +version = "0.2.1" +default-features = false + +[dependencies.wai-bindgen-rust] +version = "0.2.1" +features = ["macros"] +default-features = false + +[dependencies.wai-parser] +version = "0.2.1" + +[dependencies.wasmer] +package = "oliphaunt-wasmer" +path = "../wasmer" +version = "*" +default-features = false + +[dependencies.wasmer-derive] +version = "=7.5.0" + +[dependencies.wasmer-types] +version = "=7.5.0" diff --git a/src/wasix/runtime/engine/crates/wasmer-wasix/Cargo.toml b/src/wasix/runtime/engine/crates/wasmer-wasix/Cargo.toml new file mode 100644 index 000000000..2720db3c8 --- /dev/null +++ b/src/wasix/runtime/engine/crates/wasmer-wasix/Cargo.toml @@ -0,0 +1,491 @@ +[package] +edition = "2024" +rust-version = "1.96" +name = "oliphaunt-wasmer-wasix" +version = "0.3.1" +authors = ["Wasmer Engineering Team "] +build = false +exclude = ["tests/wasm_tests/*/**"] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "WASI and WASIX implementation library for Wasmer WebAssembly runtime" +homepage = "https://wasmer.io/" +readme = "upstream/README.md" +keywords = [ + "wasm", + "webassembly", + "wasi", + "sandbox", + "ABI", +] +categories = [ + "wasm", + "os", +] +license = "MIT" +repository = "https://github.com/wasmerio/wasmer" + +[package.metadata.docs.rs] +features = [ + "wasmer/sys", + "wasmer/cranelift", + "sys-default", +] +rustc-args = [ + "--cfg", + "docsrs", +] + +[features] +compiler = [] +ctrlc = ["tokio/signal"] +default = ["sys-default"] +disable-all-logging = [ + "tracing/release_max_level_off", + "tracing/max_level_off", +] +enable-serde = [ + "wasmer-wasix-types/enable-serde", + "wasmer/enable-serde", +] +extra-logging = [] +host-fs = ["virtual-fs/host-fs"] +host-reqwest = ["reqwest"] +host-threads = [] +host-vnet = ["virtual-net/host-net"] +journal = [ + "tokio/fs", + "wasmer-journal/log-file", +] +js = [ + "virtual-fs/js", + "getrandom/wasm_js", + "chrono", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wasmer/js-default", + "wasmer/wasm-types-polyfill", + "wasmer/wat", + "wasmer/js-serializable-module", +] +js-default = ["js"] +llvm = ["wasmer/llvm"] +logging = ["tracing/log"] +remote-vnet = ["virtual-net/remote"] +singlepass = ["wasmer/singlepass"] +sys = [ + "webc/mmap", + "time", + "virtual-mio/sys", + "logging", + "host-fs", + "journal", + "sys-poll", + "sys-thread", + "host-vnet", + "host-threads", + "host-reqwest", + "ctrlc", + "wasmer/wat", + "wasmer/js-serializable-module", + "wasmer/sys", + "wasmer/headless", +] +sys-default = ["sys"] +sys-minimal = [ + "wasmer/sys", + "sys-thread", + "tokio/fs", +] +sys-poll = [] +sys-thread = [ + "tokio/rt", + "tokio/time", + "tokio/rt-multi-thread", + "rusty_pool", +] +test-js = [ + "js", + "wasmer/wat", +] +time = ["tokio/time"] +v8 = ["wasmer/v8"] + +[lib] +name = "wasmer_wasix" +path = "upstream/src/lib.rs" + +[dependencies.anyhow] +version = "1.0" + +[dependencies.async-trait] +version = "0.1.68" + +[dependencies.base64] +version = "0.23.0" + +[dependencies.bincode] +version = "=2.0.1" + +[dependencies.blake3] +version = "1.0" + +[dependencies.bus] +version = "2.4.1" + +[dependencies.bytecheck] +version = "0.8.2" + +[dependencies.bytes] +version = "1.11.1" + +[dependencies.chrono] +version = "0.4.38" +features = [ + "wasmbind", + "std", + "clock", +] +optional = true +default-features = false + +[dependencies.cooked-waker] +version = "5" + +[dependencies.crossbeam-channel] +version = "0.5.15" + +[dependencies.dashmap] +version = "6.1.0" + +[dependencies.derive_more] +version = "2.0.1" +features = [ + "debug", + "display", +] + +[dependencies.flate2] +version = "1.0.34" + +[dependencies.fnv] +version = "1.0.3" + +[dependencies.fs_extra] +version = "1.3.0" + +[dependencies.futures] +version = "0.3.30" + +[dependencies.getrandom] +version = "0.4.1" + +[dependencies.heapless] +version = "0.9.2" + +[dependencies.hex] +version = "0.4" + +[dependencies.http] +version = "1.4.0" + +[dependencies.http-body-util] +version = "0.1.1" +optional = true + +[dependencies.hyper] +version = "1.8.1" +features = ["server"] +optional = true + +[dependencies.hyper-util] +version = "0.1.5" +features = [ + "server", + "server-graceful", + "tokio", + "service", + "client", +] +optional = true + +[dependencies.itertools] +version = "0.15" + +[dependencies.js-sys] +version = "0.3.77" +optional = true + +[dependencies.libtest-mimic] +version = "0.8.2" + +[dependencies.linked_hash_set] +version = "0.1.6" + +[dependencies.lz4_flex] +version = "0.14.0" + +[dependencies.num_enum] +version = "0.7.3" + +[dependencies.once_cell] +version = "1.17.1" + +[dependencies.petgraph] +version = "0.8.3" + +[dependencies.pin-project] +version = "1.0.12" + +[dependencies.pin-utils] +version = "0.1.0" + +[dependencies.rand] +version = "0.10.1" + +[dependencies.rayon] +version = "1.7.0" +optional = true + +[dependencies.rkyv] +version = "0.8.13" +features = [ + "indexmap-2", + "bytes-1", +] + +[dependencies.rusty_pool] +version = "1.0.0" +optional = true + +[dependencies.semver] +version = "1.0" + +[dependencies.serde] +version = "1" +features = ["derive"] +default-features = false + +[dependencies.serde_derive] +version = "1" + +[dependencies.serde_json] +version = "1" + +[dependencies.serde_yaml] +version = "0.9.34" + +[dependencies.sha2] +version = "0.11.0" + +[dependencies.shared-buffer] +version = "0.1.4" + +[dependencies.tempfile] +version = "3.6.0" + +[dependencies.thiserror] +version = "2.0.17" + +[dependencies.tokio] +version = "1.39.0" +features = [ + "sync", + "macros", + "time", + "rt", +] +default-features = false + +[dependencies.tokio-stream] +version = "0.1" +features = ["sync"] + +[dependencies.toml] +version = "1.1.0" +features = ["preserve_order"] + +[dependencies.tower] +version = "0.5.2" +features = [ + "make", + "util", +] +optional = true + +[dependencies.tower-http] +version = "0.7.0" +features = [ + "trace", + "util", + "catch-panic", + "cors", +] +optional = true + +[dependencies.tracing] +version = "0.1" +default-features = false + +[dependencies.url] +version = "2.5" + +[dependencies.urlencoding] +version = "2" + +[dependencies.virtual-fs] +version = "0.705.0" +features = ["webc-fs"] +default-features = false + +[dependencies.virtual-mio] +version = "0.705.0" +default-features = false + +[dependencies.virtual-net] +version = "0.705.0" +features = ["rkyv"] +default-features = false + +[dependencies.waker-fn] +version = "1.1" + +[dependencies.walkdir] +version = "2.3.2" + +[dependencies.wasm-bindgen] +version = "0.2.120" +optional = true + +[dependencies.wasm-bindgen-futures] +version = "0.4.70" +optional = true + +[dependencies.wasm-encoder] +version = "0.259.0" +features = ["std"] +default-features = false + +[dependencies.wasmer] +package = "oliphaunt-wasmer" +path = "../wasmer" +version = "*" +features = ["experimental-async"] +default-features = false + +[dependencies.wasmer-config] +version = "0.705.0" + +[dependencies.wasmer-journal] +package = "oliphaunt-wasmer-journal" +path = "../wasmer-journal" +version = "*" +default-features = false + +[dependencies.wasmer-package] +version = "0.705.0" + +[dependencies.wasmer-types] +version = "=7.5.0" +default-features = false + +[dependencies.wasmer-wasix-types] +package = "oliphaunt-wasmer-wasix-types" +path = "../wasmer-wasix-types" +version = "*" +features = ["enable-serde"] + +[dependencies.wasmparser] +version = "0.259.0" +features = [ + "validate", + "features", + "simd", +] +default-features = false + +[dependencies.wcgi] +version = "0.3.0" +optional = true + +[dependencies.wcgi-host] +version = "0.3.0" +optional = true + +[dependencies.web-sys] +version = "0.3.64" +features = [ + "Request", + "RequestInit", + "Window", + "WorkerGlobalScope", + "RequestMode", + "Response", + "Headers", +] +optional = true + +[dependencies.webc] +version = "=12.0.1" +features = [ + "v2", + "v3", +] +default-features = false + +[dependencies.xxhash-rust] +version = "0.8.10" +features = ["xxh64"] +default-features = false + +[target.'cfg(all(unix, not(target_os="ios")))'.dependencies.termios] +version = "0.3" + +[target.'cfg(any(target_arch = "riscv64", target_arch = "loongarch64"))'.dependencies.reqwest] +version = "0.13.0" +features = [ + "native-tls", + "json", + "stream", + "socks", + "blocking", +] +optional = true +default-features = false + +[target.'cfg(not(any(target_arch = "riscv64", target_arch = "loongarch64")))'.dependencies.reqwest] +version = "0.13.0" +features = [ + "rustls", + "json", + "stream", + "socks", + "blocking", +] +optional = true +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.terminal_size] +version = "0.4.3" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.zstd] +version = "0.14.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom03] +version = "0.3.4" +features = ["wasm_js"] +package = "getrandom" + +[target."cfg(unix)".dependencies.libc] +version = "0.2.178" +default-features = false + +[target."cfg(windows)".dependencies.windows-sys] +version = "0.61.2" +features = ["Win32_System_SystemInformation"] +default-features = false diff --git a/src/wasix/runtime/engine/crates/wasmer/Cargo.toml b/src/wasix/runtime/engine/crates/wasmer/Cargo.toml new file mode 100644 index 000000000..dcc87185a --- /dev/null +++ b/src/wasix/runtime/engine/crates/wasmer/Cargo.toml @@ -0,0 +1,318 @@ +[package] +edition = "2024" +rust-version = "1.96" +name = "oliphaunt-wasmer" +version = "0.3.1" +authors = ["Wasmer Engineering Team "] +build = "build.rs" +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "High-performance WebAssembly runtime" +homepage = "https://wasmer.io/" +readme = "upstream/README.md" +keywords = [ + "wasm", + "webassembly", + "runtime", + "vm", +] +categories = ["wasm"] +license = "MIT" +repository = "https://github.com/wasmerio/wasmer" + +[package.metadata.wasm-pack.profile.release] +wasm-opt = false + +[package.metadata.docs.rs] +features = [ + "compiler", + "core", + "cranelift", + "singlepass", + "static-artifact-create", + "static-artifact-load", + "sys", + "sys-default", + "wasmer-artifact-create", + "wasmer-artifact-load", +] +rustc-args = [ + "--cfg", + "docsrs", +] + +[badges.maintenance] +status = "actively-developed" + +[features] +artifact-size = [ + "dep:loupe", + "wasmer-vm/artifact-size", + "wasmer-compiler/artifact-size", +] +baremetal = ["wasmer-vm/baremetal"] +compiler = [ + "sys", + "dep:wasmer-compiler", + "wasmer-compiler/translator", + "wasmer-compiler/compiler", +] +core = ["hashbrown"] +cranelift = [ + "compiler", + "wasmer-compiler-cranelift", +] +default = ["sys-default"] +demangle = ["dep:symbolic-demangle"] +enable-serde = [ + "wasmer-vm/enable-serde", + "wasmer-compiler/enable-serde", + "wasmer-types/enable-serde", +] +experimental-async = [ + "dep:futures", + "dep:corosensei", +] +experimental-host-interrupt = ["wasmer-vm/experimental-host-interrupt"] +headless = [] +js = [ + "wasm-bindgen", + "wasm-bindgen-futures", + "js-sys", +] +js-default = [ + "js", + "std", + "wasm-types-polyfill", +] +js-serializable-module = [] +llvm = [ + "compiler", + "wasmer-compiler-llvm", +] +sanitizer = ["wasmer-vm/sanitizer"] +singlepass = [ + "compiler", + "wasmer-compiler-singlepass", +] +static-artifact-create = ["wasmer-compiler/static-artifact-create"] +static-artifact-load = ["wasmer-compiler/static-artifact-load"] +std = [] +sys = [ + "std", + "dep:wasmer-vm", + "dep:wasmer-compiler", +] +sys-default = [ + "sys", + "wat", + "cranelift", + "demangle", +] +unsafe-cothread = ["sys"] +v8 = [ + "dep:oliphaunt-wasmer-v8-windows-x64-msvc", + "wasm-c-api", + "std", +] +v8-default = [ + "v8", + "wat", +] +wasm-c-api = ["wasm-types-polyfill"] +wasm-types-polyfill = ["wasmparser"] +wasmer-artifact-create = ["wasmer-compiler/wasmer-artifact-create"] +wasmer-artifact-load = ["wasmer-compiler/wasmer-artifact-load"] +wat = [ + "dep:wat", + "wasmparser", +] + +[lib] +name = "wasmer" +path = "upstream/src/lib.rs" + +[dependencies.bytes] +version = "1.11.1" + +[dependencies.derive_more] +version = "2.0.1" +features = [ + "debug", + "display", + "from", + "debug", + "unwrap", +] + +[dependencies.indexmap] +version = "2" + +[dependencies.loupe] +version = "0.2.0" +features = [ + "indexmap", + "enable-indexmap", +] +optional = true + +[dependencies.more-asserts] +version = "0.3.1" + +[dependencies.paste] +version = "1.0.15" + +[dependencies.shared-buffer] +version = "0.1.4" + +[dependencies.symbolic-demangle] +version = "13" +features = [ + "rust", + "cpp", + "msvc", +] +optional = true +default-features = false + +[dependencies.thiserror] +version = "2.0.17" + +[dependencies.tracing] +version = "0.1" +default-features = true + +[dependencies.wat] +version = "1.258.0" +optional = true + +[target.'cfg(all(not(target_arch = "wasm32"), target_os = "windows"))'.dependencies.windows-sys] +version = "0.61.2" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.corosensei] +version = "0.3.0" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.dashmap] +version = "6.1.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.futures] +version = "0.3.30" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.itertools] +version = "0.15" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.js-sys] +version = "0.3.77" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rkyv] +version = "0.8.13" +features = [ + "indexmap-2", + "bytes-1", +] + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.target-lexicon] +version = "0.13.3" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasm-bindgen] +version = "0.2.120" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmer-compiler] +version = "=7.5.0" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmer-compiler-cranelift] +version = "=7.5.0" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmer-compiler-llvm] +version = "=7.5.0" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmer-compiler-singlepass] +version = "=7.5.0" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmer-derive] +version = "=7.5.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmer-types] +version = "=7.5.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmer-vm] +version = "=7.5.0" +optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wasmparser] +version = "0.259.0" +features = [ + "validate", + "features", + "simd", +] +optional = true +default-features = false + +[target.'cfg(target_arch = "wasm32")'.dependencies.hashbrown] +version = "0.17.0" +optional = true + +[target.'cfg(target_arch = "wasm32")'.dependencies.js-sys] +version = "0.3.77" + +[target.'cfg(target_arch = "wasm32")'.dependencies.serde] +version = "1" +default-features = false + +[target.'cfg(target_arch = "wasm32")'.dependencies.serde-wasm-bindgen] +version = "0.6.5" + +[target.'cfg(target_arch = "wasm32")'.dependencies.target-lexicon] +version = "0.13.3" +default-features = false + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] +version = "0.2.120" + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen-futures] +version = "0.4.70" +optional = true + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasmer-compiler] +version = "=7.5.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasmer-derive] +version = "=7.5.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasmer-types] +version = "=7.5.0" +features = ["std"] +default-features = false + +[target.'cfg(target_arch = "wasm32")'.dependencies.wasmparser] +version = "0.259.0" +features = [ + "validate", + "features", + "simd", +] +optional = true +default-features = false + + +[target.'cfg(windows)'.dependencies.tempfile] +version = "3" + +[build-dependencies.oliphaunt-wasmer-v8-windows-x64-msvc] +version = "*" +path = "../v8-windows-x64-msvc" +optional = true diff --git a/src/wasix/runtime/engine/crates/wasmer/build.rs b/src/wasix/runtime/engine/crates/wasmer/build.rs new file mode 100644 index 000000000..aa7e1e973 --- /dev/null +++ b/src/wasix/runtime/engine/crates/wasmer/build.rs @@ -0,0 +1,14 @@ +#[cfg(feature = "v8")] +fn main() { + use std::{env, fs, path::PathBuf}; + assert_eq!(env::var("TARGET").unwrap(), "x86_64-pc-windows-msvc"); + let root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); + let output = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let bindings = root.join("prebuilt/embedded_bindings.rs"); + println!("cargo:rerun-if-changed={}", bindings.display()); + fs::copy(bindings, output.join("v8_bindings.rs")).unwrap(); + oliphaunt_wasmer_v8_windows_x64_msvc::write_dll(&output.join("oliphaunt_wee8.dll")).unwrap(); +} + +#[cfg(not(feature = "v8"))] +fn main() {} diff --git a/src/wasix/runtime/engine/licenses/V8-LICENSES.txt b/src/wasix/runtime/engine/licenses/V8-LICENSES.txt new file mode 100644 index 000000000..13095d2eb --- /dev/null +++ b/src/wasix/runtime/engine/licenses/V8-LICENSES.txt @@ -0,0 +1,2153 @@ +V8 13.6.233.17 / wee8 11.9.7 Windows runtime licenses + +--- LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/LICENSE +SHA-256: 6ab33af8774a0f396ee3aeeb761e3229057682d6f9fa7f572e390c2cb3a6e509 + +This license applies to all parts of V8 that are not externally +maintained libraries. The externally maintained libraries used by V8 +are: + + - PCRE test suite, located in + test/mjsunit/third_party/regexp-pcre/regexp-pcre.js. This is based on the + test suite from PCRE-7.3, which is copyrighted by the University + of Cambridge and Google, Inc. The copyright notice and license + are embedded in regexp-pcre.js. + + - Layout tests, located in test/mjsunit/third_party/object-keys. These are + based on layout tests from webkit.org which are copyrighted by + Apple Computer, Inc. and released under a 3-clause BSD license. + + - Strongtalk assembler, the basis of the files assembler-arm-inl.h, + assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h, + assembler-ia32.cc, assembler-ia32.h, assembler-x64-inl.h, + assembler-x64.cc, assembler-x64.h, assembler.cc and assembler.h. + This code is copyrighted by Sun Microsystems Inc. and released + under a 3-clause BSD license. + + - Valgrind client API header, located at third_party/valgrind/valgrind.h + This is released under the BSD license. + + - The Wasm C/C++ API headers, located at third_party/wasm-api/wasm.{h,hh} + This is released under the Apache license. The API's upstream prototype + implementation also formed the basis of V8's implementation in + src/wasm/c-api.cc. + +These libraries have their own licenses; we recommend you read them, +as their terms may differ from the terms below. + +Further license information can be found in LICENSE files located in +sub-directories. + +Copyright 2014, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- LICENSE.v8 --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/LICENSE.v8 +SHA-256: 4af93c12062c58058378de2397dc1c92bbff9ddfb1d583a01c84127557ce97ca + +Copyright 2006-2011, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- LICENSE.fdlibm --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/LICENSE.fdlibm +SHA-256: e7115e18444dae09d17f361ddc365fb1d342640fe500796209c63f7c80dfae10 + +Copyright (C) 1993-2004 by Sun Microsystems, Inc. All rights reserved. + +Developed at SunSoft, a Sun Microsystems, Inc. business. +Permission to use, copy, modify, and distribute this +software is freely granted, provided that this notice +is preserved. + +--- LICENSE.strongtalk --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/LICENSE.strongtalk +SHA-256: 6a585a9f466654abc8fc0829d56b1bc987e3a073d31faa03bba37d33640a23cd + +Copyright (c) 1994-2006 Sun Microsystems Inc. +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistribution in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of Sun Microsystems or the names of contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- third_party/wasm-api/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/wasm-api/LICENSE +SHA-256: c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--- third_party/inspector_protocol/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/inspector_protocol/LICENSE +SHA-256: 450ebb9e9e50c2809d9ca581931fc8946d8d5aa0c8c35aae9ac471342fe4a88e + +// Copyright 2016 The Chromium Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- third_party/rapidhash-v8/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/rapidhash-v8/LICENSE +SHA-256: db672df7faf793fbe28b8074fdd10822363e1525816b45c9627754885de842b0 + +/* + * rapidhash - Very fast, high quality, platform independant hashing algorithm. + * Copyright (C) 2024 Nicolas De Carli + * + * Based on 'wyhash', by Wang Yi + * + * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You can contact the author at: + * - rapidhash source repository: https://github.com/Nicoshev/rapidhash + */ + +--- third_party/siphash/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/siphash/LICENSE +SHA-256: 36ffd9dc085d529a7e60e1276d73ae5a030b020313e6c5408593a6ae2af39673 + +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + + +--- third_party/utf8-decoder/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/utf8-decoder/LICENSE +SHA-256: 3f712e5fbdfdbd5ee7d9b8c8152580220df55de47f4eba2f26c95c4de19ad096 + +Copyright (c) 2008-2009 Bjoern Hoehrmann + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- third_party/v8/builtins/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/v8/builtins/LICENSE +SHA-256: b9a6d9320b8f2693e8d41e496ce56caadacaddcca9be2a64a61749278f425cf2 + +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All +Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- third_party/v8/codegen/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/v8/codegen/LICENSE +SHA-256: 17e4f539024be2749ee729d1e2f01d24cef12ece8c9bf18e91a4349be29c80bf + +The MIT License (MIT) + +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2017 Georgia Institute of Technology +Copyright 2019 Google LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- third_party/fp16/LICENSE --- +Source: https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/fp16/LICENSE +SHA-256: c91377adafa2f498211a4fe017788e20c38892124673860f068f2919c289193d + +The MIT License (MIT) + +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2017 Georgia Institute of Technology +Copyright 2019 Google LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- abseil --- +Source: https://chromium.googlesource.com/chromium/src/third_party/abseil-cpp/+/3fbb10e80d80e3430224b75add53c47c7a711612/LICENSE?format=TEXT +SHA-256: c79a7fea0e3cac04cd43f20e7b648e5a0ff8fa5344e644b0ee09ca1162b62747 + + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--- icu --- +Source: https://chromium.googlesource.com/chromium/deps/icu/+/c9fb4b3a6fb54aa8c20a03bbcaa0a4a985ffd34b/LICENSE?format=TEXT +SHA-256: 17510cf7a58b4879b887ec05a45d72cf1b73544dd9ec7e72f20110ed104229ee + +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2016-2023 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +---------------------------------------------------------------------- + +Third-Party Software Licenses + +This section contains third-party software notices and/or additional +terms for licensed third-party software components included within ICU +libraries. + +---------------------------------------------------------------------- + +ICU License - ICU 1.8.1 to ICU 57.1 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2016 International Business Machines Corporation and others +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder. + +All trademarks and registered trademarks mentioned herein are the +property of their respective owners. + +---------------------------------------------------------------------- + +Chinese/Japanese Word Break Dictionary Data (cjdict.txt) + + # The Google Chrome software developed by Google is licensed under + # the BSD license. Other software included in this distribution is + # provided under other licenses, as set forth below. + # + # The BSD License + # http://opensource.org/licenses/bsd-license.php + # Copyright (C) 2006-2008, Google Inc. + # + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # Redistributions in binary form must reproduce the above + # copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided with + # the distribution. + # Neither the name of Google Inc. nor the names of its + # contributors may be used to endorse or promote products derived from + # this software without specific prior written permission. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + # + # + # The word list in cjdict.txt are generated by combining three word lists + # listed below with further processing for compound word breaking. The + # frequency is generated with an iterative training against Google web + # corpora. + # + # * Libtabe (Chinese) + # - https://sourceforge.net/project/?group_id=1519 + # - Its license terms and conditions are shown below. + # + # * IPADIC (Japanese) + # - http://chasen.aist-nara.ac.jp/chasen/distribution.html + # - Its license terms and conditions are shown below. + # + # ---------COPYING.libtabe ---- BEGIN-------------------- + # + # /* + # * Copyright (c) 1999 TaBE Project. + # * Copyright (c) 1999 Pai-Hsiang Hsiao. + # * All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the TaBE Project nor the names of its + # * contributors may be used to endorse or promote products derived + # * from this software without specific prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # /* + # * Copyright (c) 1999 Computer Systems and Communication Lab, + # * Institute of Information Science, Academia + # * Sinica. All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the Computer Systems and Communication Lab + # * nor the names of its contributors may be used to endorse or + # * promote products derived from this software without specific + # * prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, + # University of Illinois + # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 + # + # ---------------COPYING.libtabe-----END-------------------------------- + # + # + # ---------------COPYING.ipadic-----BEGIN------------------------------- + # + # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science + # and Technology. All Rights Reserved. + # + # Use, reproduction, and distribution of this software is permitted. + # Any copy of this software, whether in its original form or modified, + # must include both the above copyright notice and the following + # paragraphs. + # + # Nara Institute of Science and Technology (NAIST), + # the copyright holders, disclaims all warranties with regard to this + # software, including all implied warranties of merchantability and + # fitness, in no event shall NAIST be liable for + # any special, indirect or consequential damages or any damages + # whatsoever resulting from loss of use, data or profits, whether in an + # action of contract, negligence or other tortuous action, arising out + # of or in connection with the use or performance of this software. + # + # A large portion of the dictionary entries + # originate from ICOT Free Software. The following conditions for ICOT + # Free Software applies to the current dictionary as well. + # + # Each User may also freely distribute the Program, whether in its + # original form or modified, to any third party or parties, PROVIDED + # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear + # on, or be attached to, the Program, which is distributed substantially + # in the same form as set out herein and that such intended + # distribution, if actually made, will neither violate or otherwise + # contravene any of the laws and regulations of the countries having + # jurisdiction over the User or the intended distribution itself. + # + # NO WARRANTY + # + # The program was produced on an experimental basis in the course of the + # research and development conducted during the project and is provided + # to users as so produced on an experimental basis. Accordingly, the + # program is provided without any warranty whatsoever, whether express, + # implied, statutory or otherwise. The term "warranty" used herein + # includes, but is not limited to, any warranty of the quality, + # performance, merchantability and fitness for a particular purpose of + # the program and the nonexistence of any infringement or violation of + # any right of any third party. + # + # Each user of the program will agree and understand, and be deemed to + # have agreed and understood, that there is no warranty whatsoever for + # the program and, accordingly, the entire risk arising from or + # otherwise connected with the program is assumed by the user. + # + # Therefore, neither ICOT, the copyright holder, or any other + # organization that participated in or was otherwise related to the + # development of the program and their respective officials, directors, + # officers and other employees shall be held liable for any and all + # damages, including, without limitation, general, special, incidental + # and consequential damages, arising out of or otherwise in connection + # with the use or inability to use the program or any product, material + # or result produced or otherwise obtained by using the program, + # regardless of whether they have been advised of, or otherwise had + # knowledge of, the possibility of such damages at any time during the + # project or thereafter. Each user will be deemed to have agreed to the + # foregoing by his or her commencement of use of the program. The term + # "use" as used herein includes, but is not limited to, the use, + # modification, copying and distribution of the program and the + # production of secondary products from the program. + # + # In the case where the program, whether in its original form or + # modified, was distributed or delivered to or received by a user from + # any person, organization or entity other than ICOT, unless it makes or + # grants independently of ICOT any specific warranty to the user in + # writing, such person, organization or entity, will also be exempted + # from and not be held liable to the user for any such damages as noted + # above as far as the program is concerned. + # + # ---------------COPYING.ipadic-----END---------------------------------- + +---------------------------------------------------------------------- + +Lao Word Break Dictionary Data (laodict.txt) + + # Copyright (C) 2016 and later: Unicode, Inc. and others. + # License & terms of use: http://www.unicode.org/copyright.html + # Copyright (c) 2015 International Business Machines Corporation + # and others. All Rights Reserved. + # + # Project: https://github.com/rober42539/lao-dictionary + # Dictionary: https://github.com/rober42539/lao-dictionary/laodict.txt + # License: https://github.com/rober42539/lao-dictionary/LICENSE.txt + # (copied below) + # + # This file is derived from the above dictionary version of Nov 22, 2020 + # ---------------------------------------------------------------------- + # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # Redistributions of source code must retain the above copyright notice, this + # list of conditions and the following disclaimer. Redistributions in binary + # form must reproduce the above copyright notice, this list of conditions and + # the following disclaimer in the documentation and/or other materials + # provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # OF THE POSSIBILITY OF SUCH DAMAGE. + # -------------------------------------------------------------------------- + +---------------------------------------------------------------------- + +Burmese Word Break Dictionary Data (burmesedict.txt) + + # Copyright (c) 2014 International Business Machines Corporation + # and others. All Rights Reserved. + # + # This list is part of a project hosted at: + # github.com/kanyawtech/myanmar-karen-word-lists + # + # -------------------------------------------------------------------------- + # Copyright (c) 2013, LeRoy Benjamin Sharon + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: Redistributions of source code must retain the above + # copyright notice, this list of conditions and the following + # disclaimer. Redistributions in binary form must reproduce the + # above copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided + # with the distribution. + # + # Neither the name Myanmar Karen Word Lists, nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + # -------------------------------------------------------------------------- + +---------------------------------------------------------------------- + +Time Zone Database + + ICU uses the public domain data and code derived from Time Zone +Database for its time zone support. The ownership of the TZ database +is explained in BCP 175: Procedure for Maintaining the Time Zone +Database section 7. + + # 7. Database Ownership + # + # The TZ database itself is not an IETF Contribution or an IETF + # document. Rather it is a pre-existing and regularly updated work + # that is in the public domain, and is intended to remain in the + # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do + # not apply to the TZ Database or contributions that individuals make + # to it. Should any claims be made and substantiated against the TZ + # Database, the organization that is providing the IANA + # Considerations defined in this RFC, under the memorandum of + # understanding with the IETF, currently ICANN, may act in accordance + # with all competent court orders. No ownership claims will be made + # by ICANN or the IETF Trust on the database or the code. Any person + # making a contribution to the database or code waives all rights to + # future claims in that contribution or in the TZ Database. + +---------------------------------------------------------------------- + +Google double-conversion + +Copyright 2006-2011, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- + +File: aclocal.m4 (only for ICU4C) +Section: pkg.m4 - Macros to locate and utilise pkg-config. + + +Copyright © 2004 Scott James Remnant . +Copyright © 2012-2015 Dan Nicholson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +02111-1307, USA. + +As a special exception to the GNU General Public License, if you +distribute this file as part of a program that contains a +configuration script generated by Autoconf, you may include it under +the same distribution terms that you use for the rest of that +program. + + +(The condition for the exception is fulfilled because +ICU4C includes a configuration script generated by Autoconf, +namely the `configure` script.) + +---------------------------------------------------------------------- + +File: config.guess (only for ICU4C) + + +This file is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see . + +As a special exception to the GNU General Public License, if you +distribute this file as part of a program that contains a +configuration script generated by Autoconf, you may include it under +the same distribution terms that you use for the rest of that +program. This Exception is an additional permission under section 7 +of the GNU General Public License, version 3 ("GPLv3"). + + +(The condition for the exception is fulfilled because +ICU4C includes a configuration script generated by Autoconf, +namely the `configure` script.) + +---------------------------------------------------------------------- + +File: install-sh (only for ICU4C) + + +Copyright 1991 by the Massachusetts Institute of Technology + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of M.I.T. not be used in advertising or +publicity pertaining to distribution of the software without specific, +written prior permission. M.I.T. makes no representations about the +suitability of this software for any purpose. It is provided "as is" +without express or implied warranty. + +--- zlib --- +Source: https://chromium.googlesource.com/chromium/src/third_party/zlib/+/788cb3c270e8700b425c7bdca1f9ce6b0c1400a9/LICENSE?format=TEXT +SHA-256: e1cfcc55c325b3f78cf55df9664abaa066e2271dffe8213347d9fccdfbac8f2c + +version 1.2.12, March 27th, 2022 + +Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +--- simdutf --- +Source: https://chromium.googlesource.com/chromium/src/third_party/simdutf/+/40d1fa26cd5ca221605c974e22c001ca2fb12fde/LICENSE?format=TEXT +SHA-256: fc8dbc04e03ad4efc08a647ffe7f995b811a95bc04c0e85a56d5277c6593fa5f + +Copyright 2021 The simdutf authors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- highway --- +Source: https://chromium.googlesource.com/external/github.com/google/highway/+/00fe003dac355b979f36157f9407c7c46448958e/LICENSE?format=TEXT +SHA-256: 43070e2d4e532684de521b885f385d0841030efa2b1a20bafb76133a5e1379c1 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--- fast-float --- +Source: https://chromium.googlesource.com/external/github.com/fastfloat/fast_float/+/cb1d42aaa1e14b09e1452cfdef373d051b8c02a4/LICENSE-MIT?format=TEXT +SHA-256: e562f3f974ced7e69dd1db77b820b36bcf8f30377f1aa105723fba449c53c4e6 + +MIT License + +Copyright (c) 2021 The fast_float authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +--- cpu-features --- +Source: https://chromium.googlesource.com/external/github.com/google/cpu_features/+/936b9ab5515dead115606559502e3864958f7f6e/LICENSE?format=TEXT +SHA-256: af272c1b4dd08dd61d43e001d3134119d03ca7a2b057a64f7355fdf27fbea3dd + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- +For files in the `ndk_compat` folder: +-------------------------------------------------------------------------------- + +Copyright (C) 2010 The Android Open Source Project +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +--- fp16 --- +Source: https://chromium.googlesource.com/external/github.com/Maratyszcza/FP16/+/0a92994d729ff76a58f692d3028ca1b64b145d91/LICENSE?format=TEXT +SHA-256: 17e4f539024be2749ee729d1e2f01d24cef12ece8c9bf18e91a4349be29c80bf + +The MIT License (MIT) + +Copyright (c) 2017 Facebook Inc. +Copyright (c) 2017 Georgia Institute of Technology +Copyright 2019 Google LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- zlib/google/compression_utils_portable.cc --- +Source: https://chromium.googlesource.com/chromium/src/third_party/zlib/+/788cb3c270e8700b425c7bdca1f9ce6b0c1400a9/google/compression_utils_portable.cc?format=TEXT +SHA-256: 5213dd083af3d7099251413e0fe8988ffe998b0dbee68722899910c480aea933 + +Copyright 2019 The Chromium Authors + +License source: https://raw.githubusercontent.com/chromium/chromium/8d621d9143e4526d70d688abf40651c149908708/LICENSE +License SHA-256: 368cca1106be99d39ecd32a38d8305585d802a475effb66380b91ffc9bcf709b + +// Copyright 2015 The Chromium Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/wasix/runtime/engine/licenses/Wasmer-LICENSE b/src/wasix/runtime/engine/licenses/Wasmer-LICENSE new file mode 100644 index 000000000..62bb543eb --- /dev/null +++ b/src/wasix/runtime/engine/licenses/Wasmer-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-present Wasmer, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/wasix/runtime/engine/package.mts b/src/wasix/runtime/engine/package.mts new file mode 100644 index 000000000..863cf7281 --- /dev/null +++ b/src/wasix/runtime/engine/package.mts @@ -0,0 +1,192 @@ +import assert from 'node:assert/strict'; +import { cpSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { + fitCargoPayloadParts, + packageGeneratedCargoSource, + packagedCargoManifestText, +} from '../../../../tools/packaging/cargo-source-package.mts'; +import { canonicalGzipSync } from '../../../../tools/packaging/portable-archive.mts'; +import { releaseProfilePackageLicense } from '../../../../tools/packaging/release-notices.mts'; +import { RUST_BUILD_SCRIPT_SHA256 } from '../../../../tools/packaging/rust-build-script-sha256.mts'; +import { ROOT } from '../../../../tools/release/release-artifact-targets.mts'; +import { archiveTreeDigest, sha256File } from '../../../third-party/tools/source-fetch-core.mts'; +import { ENGINE_PAYLOAD_PACKAGE, ENGINE_SOURCE_CRATES } from './contract.mts'; + +const OWNER = path.join(ROOT, 'src/wasix/runtime/engine'); +const DLL = 'oliphaunt_wee8.dll'; + +/** Freeze qualified sources and engine bytes; this step never builds the engine. */ +export function packageEngine({ + sourceRoot, + outputDir, + version, + engineDir = path.join(ROOT, 'target/oliphaunt-wasix/engine/windows-x64-msvc'), + cratesDir = path.join(OWNER, 'crates'), + expectedSourceSha = process.env.RELEASE_ARTIFACT_SHA ?? + Bun.spawnSync(['git', 'rev-parse', 'HEAD'], { cwd: ROOT }).stdout.toString().trim(), +}: { + sourceRoot: string; + outputDir: string; + version: string; + engineDir?: string; + cratesDir?: string; + expectedSourceSha?: string; +}) { + const receipt = JSON.parse(readFileSync(path.join(engineDir, 'source.json'), 'utf8')); + assert.equal( + receipt.sourceSha, + expectedSourceSha, + 'Windows engine must come from the qualified source commit', + ); + assert.deepEqual( + receipt.source, + Bun.TOML.parse(readFileSync(path.join(OWNER, 'source.toml'), 'utf8')), + 'engine source pins', + ); + assert.equal( + sha256File(path.join(engineDir, DLL)), + receipt.dllSha256, + 'frozen engine DLL digest', + ); + assert.equal( + sha256File(path.join(cratesDir, 'wasmer/prebuilt/embedded_bindings.rs')), + receipt.bindingsSha256, + 'bindings must describe the frozen DLL', + ); + for (const name of ENGINE_SOURCE_CRATES) { + assert.equal( + archiveTreeDigest(path.join(cratesDir, name, 'upstream')), + receipt.engineSources[name], + `${name}: qualified patched source digest`, + ); + } + + const packages = []; + function freeze(name: string, profile: string, existingCrate?: string) { + const manifestPath = path.join(sourceRoot, name, 'Cargo.toml'); + const cratePath = + existingCrate ?? + packageGeneratedCargoSource(manifestPath, outputDir, { + noticeProfile: profile, + }); + const row = { + name, + manifestPath, + cratePath, + target: name.startsWith(ENGINE_PAYLOAD_PACKAGE) ? 'windows-x64-msvc' : 'portable', + kind: 'wasix-engine', + size: statSync(cratePath).size, + sha256: sha256File(cratePath), + }; + packages.push(row); + return cratePath; + } + function manifest(name: string, profile: string, extra: string) { + const directory = path.join(sourceRoot, name); + mkdirSync(path.join(directory, 'src'), { recursive: true }); + writeFileSync( + path.join(directory, 'Cargo.toml'), + `[package]\nname = ${JSON.stringify(name)}\nversion = ${JSON.stringify(version)}\nedition = "2024"\nrust-version = "1.96"\ndescription = "Internal Oliphaunt Windows engine carrier"\nrepository = "https://github.com/f0rr0/oliphaunt"\nlicense = ${JSON.stringify(releaseProfilePackageLicense(profile).spdx)}\n\n${extra}`, + ); + return directory; + } + + const compressed = canonicalGzipSync(readFileSync(path.join(engineDir, DLL))); + const parts = fitCargoPayloadParts( + (budget) => { + const result = []; + for (let offset = 0; offset < compressed.length; offset += budget) { + const name = `${ENGINE_PAYLOAD_PACKAGE}-part-${String(result.length + 1).padStart(3, '0')}`; + assert(result.length < 999, 'engine payload exceeds supported part count'); + const directory = manifest(name, 'wasix-engine-windows', ''); + writeFileSync( + path.join(directory, 'payload.gz.part'), + compressed.subarray(offset, offset + budget), + ); + writeFileSync( + path.join(directory, 'src/lib.rs'), + 'pub const PAYLOAD: &[u8] = include_bytes!("../payload.gz.part");\n', + ); + result.push(name); + } + return result; + }, + (name) => + packageGeneratedCargoSource(path.join(sourceRoot, name, 'Cargo.toml'), outputDir, { + noticeProfile: 'wasix-engine-windows', + }), + 8 * 1024 * 1024, + ); + for (const name of parts) + freeze(name, 'wasix-engine-windows', path.join(outputDir, `${name}-${version}.crate`)); + const directory = manifest( + ENGINE_PAYLOAD_PACKAGE, + 'wasix-engine-windows', + `[build-dependencies]\nflate2 = { version = "1", default-features = false, features = ["rust_backend"] }\nsha2 = "0.10"\n${parts.map((name) => `${name} = "=${version}"`).join('\n')}\n`, + ); + cpSync( + path.join(cratesDir, 'v8-windows-x64-msvc/src/lib.rs'), + path.join(directory, 'src/lib.rs'), + ); + writeFileSync( + path.join(directory, 'build.rs'), + `use std::{env, fs, io::{self, Read}, path::{Path, PathBuf}}; +${RUST_BUILD_SCRIPT_SHA256} +fn main() { + let parts: &[&[u8]] = &[${parts.map((name) => `${name.replaceAll('-', '_')}::PAYLOAD`).join(', ')}]; + let compressed: Vec = parts.iter().flat_map(|part| part.iter().copied()).collect(); + let mut decoder = flate2::read::GzDecoder::new(&compressed[..]).take(${statSync(path.join(engineDir, DLL)).size + 1}); + let output = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR")).join(${JSON.stringify(DLL)}); + let mut file = fs::File::create(&output).expect("create bundled engine"); + let size = io::copy(&mut decoder, &mut file).expect("decompress bundled engine"); + assert_eq!(size, ${statSync(path.join(engineDir, DLL)).size}, "bundled engine byte size"); + assert_eq!(sha256_file(&output).expect("hash bundled engine"), ${JSON.stringify(receipt.dllSha256)}, "bundled engine digest"); + println!("cargo::rerun-if-changed=build.rs"); +} +`, + ); + freeze(ENGINE_PAYLOAD_PACKAGE, 'wasix-engine-windows'); + + for (const upstream of ENGINE_SOURCE_CRATES) { + const name = `oliphaunt-${upstream}`; + const destination = path.join(sourceRoot, name); + rmSync(destination, { recursive: true, force: true }); + mkdirSync(path.join(destination, 'upstream'), { recursive: true }); + cpSync(path.join(cratesDir, upstream, 'upstream/src'), path.join(destination, 'upstream/src'), { + recursive: true, + }); + const original = Bun.TOML.parse( + readFileSync(path.join(cratesDir, upstream, 'Cargo.toml'), 'utf8'), + ); + if (typeof original.package.readme === 'string') + cpSync( + path.join(cratesDir, upstream, original.package.readme), + path.join(destination, original.package.readme), + ); + cpSync(path.join(cratesDir, upstream, 'Cargo.toml'), path.join(destination, 'Cargo.toml')); + if (upstream === 'wasmer') { + cpSync(path.join(cratesDir, upstream, 'build.rs'), path.join(destination, 'build.rs')); + cpSync(path.join(cratesDir, upstream, 'prebuilt'), path.join(destination, 'prebuilt'), { + recursive: true, + }); + } + let text = packagedCargoManifestText( + readFileSync(path.join(destination, 'Cargo.toml'), 'utf8'), + ); + text = text + .replace(/^publish = false\n/gmu, '') + .replace(/^version = "[^"]+"$/mu, `version = "${version}"`); + text = text.replace( + /(package = "oliphaunt-wasmer[^"\n]*"\nversion = ")[^"]+/gu, + `$1=${version}`, + ); + text = text.replace( + /(\[build-dependencies\.oliphaunt-wasmer-v8-windows-x64-msvc\]\nversion = ")[^"]+/u, + `$1=${version}`, + ); + writeFileSync(path.join(destination, 'Cargo.toml'), text); + freeze(name, 'wasix-engine-source'); + } + return packages; +} diff --git a/src/wasix/runtime/engine/package.test.mts b/src/wasix/runtime/engine/package.test.mts new file mode 100644 index 000000000..e47608671 --- /dev/null +++ b/src/wasix/runtime/engine/package.test.mts @@ -0,0 +1,152 @@ +import assert from 'node:assert/strict'; +import { randomBytes } from 'node:crypto'; +import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { gunzipSync } from 'node:zlib'; +import { packagedCargoManifestText } from '../../../../tools/packaging/cargo-source-package.mts'; +import { preparePackagedCargoTestClosure } from '../../../../tools/packaging/cargo-package-test-closure.mts'; +import { readPortableArchiveEntries } from '../../../../tools/packaging/portable-archive.mts'; +import { assertReleaseNoticesInArchive } from '../../../../tools/packaging/release-notices.mts'; +import { archiveTreeDigest, sha256File } from '../../../third-party/tools/source-fetch-core.mts'; +import { + ENGINE_CARGO_PACKAGES, + ENGINE_PAYLOAD_PACKAGE, + ENGINE_SOURCE_CRATES, + isEnginePayloadPart, +} from './contract.mts'; +import { packageEngine } from './package.mts'; + +test('freezes a closed engine family, rejects mixed inputs, and reconstructs every DLL byte', (t) => { + const root = mkdtempSync(path.join(os.tmpdir(), 'oliphaunt-engine-package-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + const cratesDir = path.join(root, 'crates'); + cpSync(path.join(import.meta.dir, 'crates'), cratesDir, { recursive: true }); + const engineDir = path.join(root, 'engine'); + mkdirSync(engineDir); + mkdirSync(path.join(cratesDir, 'wasmer/prebuilt'), { recursive: true }); + // Incompressible bytes exercise the registry size boundary and ordered parts. + const dll = randomBytes(10 * 1024 * 1024); + writeFileSync(path.join(engineDir, 'oliphaunt_wee8.dll'), dll); + writeFileSync( + path.join(cratesDir, 'wasmer/prebuilt/embedded_bindings.rs'), + '// fixture bindings\n', + ); + const sourceSha = 'b'.repeat(40); + const receipt = { + sourceSha, + source: Bun.TOML.parse(readFileSync(path.join(import.meta.dir, 'source.toml'), 'utf8')), + dllSha256: sha256File(path.join(engineDir, 'oliphaunt_wee8.dll')), + bindingsSha256: sha256File(path.join(cratesDir, 'wasmer/prebuilt/embedded_bindings.rs')), + engineSources: Object.fromEntries( + ENGINE_SOURCE_CRATES.map((name) => [ + name, + archiveTreeDigest(path.join(cratesDir, name, 'upstream')), + ]), + ), + }; + const receiptPath = path.join(engineDir, 'source.json'); + const save = () => writeFileSync(receiptPath, JSON.stringify(receipt)); + save(); + const options = { + engineDir, + cratesDir, + sourceRoot: path.join(root, 'source'), + outputDir: path.join(root, 'output'), + version: '9.8.7', + expectedSourceSha: sourceSha, + }; + assert.throws( + () => packageEngine({ ...options, expectedSourceSha: 'a'.repeat(40) }), + /qualified source commit/u, + ); + const digest = receipt.dllSha256; + receipt.dllSha256 = '0'.repeat(64); + save(); + assert.throws(() => packageEngine(options), /frozen engine DLL digest/u); + receipt.dllSha256 = digest; + save(); + const packages = packageEngine(options); + const partNames = [`${ENGINE_PAYLOAD_PACKAGE}-part-001`, `${ENGINE_PAYLOAD_PACKAGE}-part-002`]; + assert(!isEnginePayloadPart(`${ENGINE_PAYLOAD_PACKAGE}-part-000`)); + assert.deepEqual( + packages.map((row) => row.name).sort(), + [...ENGINE_CARGO_PACKAGES, ...partNames].sort(), + ); + const chunks = []; + for (const row of packages) { + assert.ok(row.size <= 9 * 1024 * 1024, row.name); + const prefix = `${row.name}-9.8.7`; + assertReleaseNoticesInArchive(row.cratePath, { + prefix, + profile: row.name.startsWith(ENGINE_PAYLOAD_PACKAGE) + ? 'wasix-engine-windows' + : 'wasix-engine-source', + }); + const entries = readPortableArchiveEntries(row.cratePath); + const text = entries.get(`${prefix}/Cargo.toml`).data().toString(); + const cargo = Bun.TOML.parse(text); + assert.equal(cargo.package.version, '9.8.7'); + assert.equal(text, packagedCargoManifestText(text)); + for (const table of [cargo.dependencies, cargo['build-dependencies']]) { + for (const [key, spec] of Object.entries(table ?? {})) { + assert.equal(spec.path, undefined, `${row.name}:${key} must use registry dependencies`); + if (ENGINE_CARGO_PACKAGES.includes(spec.package ?? key) || partNames.includes(key)) + assert.equal(spec.version ?? spec, '=9.8.7', `${row.name}:${key}`); + } + } + if (partNames.includes(row.name)) chunks.push(entries.get(`${prefix}/payload.gz.part`).data()); + assert.ok( + ![...entries.keys()].some((name) => /\.prepared|upstream\/tests\/|v8\.lib/u.test(name)), + ); + } + assert.deepEqual(gunzipSync(Buffer.concat(chunks)), dll); + const before = packages.map((row) => row.sha256); + assert.deepEqual( + packageEngine(options).map((row) => row.sha256), + before, + 'frozen carriers must be reproducible', + ); + + const consumer = path.join(root, 'consumer'); + const carrier = packages.find((row) => row.name === ENGINE_PAYLOAD_PACKAGE); + const manifest = preparePackagedCargoTestClosure({ + cratePath: carrier.cratePath, + scratch: consumer, + dependencyCrates: packages + .filter((row) => partNames.includes(row.name)) + .map((row) => row.cratePath), + }); + const env = { ...process.env, CARGO_TARGET_DIR: path.join(consumer, 'target') }; + const fetched = Bun.spawnSync( + ['cargo', '--config', 'net.offline=false', 'fetch', '--manifest-path', manifest], + { cwd: consumer, env, timeout: 300_000 }, + ); + assert.equal(fetched.exitCode, 0, fetched.stderr.toString()); + const result = Bun.spawnSync( + [ + 'cargo', + 'build', + '--manifest-path', + manifest, + '--locked', + '--offline', + '--message-format=json', + ], + { cwd: consumer, env, timeout: 300_000 }, + ); + assert.equal(result.exitCode, 0, result.stderr.toString()); + const build = result.stdout + .toString() + .trim() + .split('\n') + .map((line) => JSON.parse(line)) + .find( + (message) => + message.reason === 'build-script-executed' && + message.package_id.includes(`${ENGINE_PAYLOAD_PACKAGE}@`), + ); + assert.ok(build, 'the extracted carrier must reconstruct its own DLL'); + assert.deepEqual(readFileSync(path.join(build.out_dir, 'oliphaunt_wee8.dll')), dll); +}); diff --git a/src/wasix/runtime/engine/patches/wasmer-wasix/0001-optional-runner-features.patch b/src/wasix/runtime/engine/patches/wasmer-wasix/0001-optional-runner-features.patch new file mode 100644 index 000000000..f25ae28f7 --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer-wasix/0001-optional-runner-features.patch @@ -0,0 +1,86 @@ +From: Sid Jain +Date: Thu, 8 Oct 2026 00:00:00 +0000 +Subject: [PATCH 1/3] wasix: respect optional runner feature boundaries + +Keep journal and host filesystem runner imports and fields behind their existing +feature gates so the minimal desktop feature set compiles. + +The complete ordered series is the build and qualification unit. + +diff --git a/src/runners/wasi.rs b/src/runners/wasi.rs +--- a/src/runners/wasi.rs ++++ b/src/runners/wasi.rs +@@ -9,11 +9,12 @@ + use wasmer_types::ModuleHash; + use webc::metadata::{Command, annotations::Wasi}; + ++#[cfg(feature = "journal")] ++use crate::journal::{DynJournal, DynReadableJournal, SnapshotTrigger}; + use crate::{ + Runtime, WasiEnvBuilder, WasiError, WasiRuntimeError, + bin_factory::BinaryPackage, + capabilities::Capabilities, +- journal::{DynJournal, DynReadableJournal, SnapshotTrigger}, + runners::{MappedDirectory, MountedDirectory, wasi_common::CommonWasiOptions}, + runtime::task_manager::VirtualTaskManagerExt, + }; +@@ -364,13 +365,16 @@ + + let wasi = webc::metadata::annotations::Wasi::new(program_name); + +- let mut builder = runner.prepare_webc_env( ++ let builder = runner.prepare_webc_env( + program_name, + &wasi, + PackageOrHash::Hash(module_hash), + runtime_or_engine, + None, + )?; ++ ++ #[cfg(any(feature = "ctrlc", feature = "journal"))] ++ let mut builder = builder; + + #[cfg(feature = "ctrlc")] + { +diff --git a/src/runners/wasi_common.rs b/src/runners/wasi_common.rs +--- a/src/runners/wasi_common.rs ++++ b/src/runners/wasi_common.rs +@@ -6,6 +6,7 @@ + }; + + use anyhow::{Context, Error}; ++#[cfg(feature = "host-fs")] + use tokio::runtime::Handle; + use virtual_fs::{ + ArcFileSystem, ExactMountConflictMode, FileSystem, MountFileSystem, OverlayFileSystem, +@@ -13,12 +14,13 @@ + }; + use webc::metadata::annotations::Wasi as WasiAnnotation; + ++#[cfg(feature = "journal")] ++use crate::journal::{DynJournal, DynReadableJournal, SnapshotTrigger}; + use crate::{ + WasiEnvBuilder, + bin_factory::{BinaryPackage, BinaryPackageMounts}, + capabilities::Capabilities, + fs::WasiFsRoot, +- journal::{DynJournal, DynReadableJournal, SnapshotTrigger}, + }; + + pub const MAPPED_CURRENT_DIR_DEFAULT_PATH: &str = "/home"; +@@ -49,10 +51,15 @@ + pub(crate) is_home_mapped: bool, + pub(crate) injected_packages: Vec, + pub(crate) capabilities: Capabilities, ++ #[cfg(feature = "journal")] + pub(crate) read_only_journals: Vec>, ++ #[cfg(feature = "journal")] + pub(crate) writable_journals: Vec>, ++ #[cfg(feature = "journal")] + pub(crate) snapshot_on: Vec, ++ #[cfg(feature = "journal")] + pub(crate) snapshot_interval: Option, ++ #[cfg(feature = "journal")] + pub(crate) stop_running_after_snapshot: bool, + pub(crate) skip_stdio_during_bootstrap: bool, + pub(crate) current_dir: Option, diff --git a/src/wasix/runtime/engine/patches/wasmer-wasix/0002-cached-module-loading.patch b/src/wasix/runtime/engine/patches/wasmer-wasix/0002-cached-module-loading.patch new file mode 100644 index 000000000..dc801adc3 --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer-wasix/0002-cached-module-loading.patch @@ -0,0 +1,86 @@ +From: Sid Jain +Date: Thu, 8 Oct 2026 00:00:00 +0000 +Subject: [PATCH 2/3] wasix: require cached modules for SDK runtime loading + +Add an opt-in cache-only policy to the existing Runtime abstraction and preserve +it through runtime overrides. The SDK enables it to keep cache misses from +compiling guests or extensions at runtime. + +The complete ordered series is the build and qualification unit. + +diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs +--- a/src/runtime/mod.rs ++++ b/src/runtime/mod.rs +@@ -353,6 +353,11 @@ + Arc::new(UnsupportedPackageLoader) + } + ++ /// Require cached modules instead of compiling on cache misses. ++ fn module_cache_only(&self) -> bool { ++ false ++ } ++ + /// A cache for compiled modules. + fn module_cache(&self) -> Arc { + // Return a cache that uses a thread-local variable. This isn't ideal +@@ -471,7 +476,21 @@ + + let module_cache = self.module_cache(); + +- let task = async move { load_module(&engine, &module_cache, input, on_progress).await }; ++ let cache_only = self.module_cache_only(); ++ let task = async move { ++ if cache_only { ++ let result = if let Some(progress) = on_progress { ++ module_cache ++ .load_with_progress(input.hash(), &engine, progress) ++ .await ++ } else { ++ module_cache.load(input.hash(), &engine).await ++ }; ++ result.map_err(SpawnError::CacheError) ++ } else { ++ load_module(&engine, &module_cache, input, on_progress).await ++ } ++ }; + Box::pin(task) + } + +@@ -674,6 +693,7 @@ + pub source: Arc, + pub engine: Engine, + pub module_cache: Arc, ++ pub module_cache_only: bool, + pub tty: Option>, + #[cfg(feature = "journal")] + pub read_only_journals: Vec>, +@@ -715,6 +735,7 @@ + source: Arc::new(source), + package_loader: Arc::new(loader), + module_cache: Arc::new(module_cache::in_memory()), ++ module_cache_only: false, + #[cfg(feature = "journal")] + read_only_journals: Vec::new(), + #[cfg(feature = "journal")] +@@ -878,6 +899,10 @@ + } + + impl Runtime for PluggableRuntime { ++ fn module_cache_only(&self) -> bool { ++ self.module_cache_only ++ } ++ + fn networking(&self) -> &DynVirtualNetworking { + &self.networking + } +@@ -1111,6 +1136,10 @@ + } + + impl Runtime for OverriddenRuntime { ++ fn module_cache_only(&self) -> bool { ++ self.inner.module_cache_only() ++ } ++ + fn networking(&self) -> &DynVirtualNetworking { + if let Some(net) = self.networking.as_ref() { + net diff --git a/src/wasix/runtime/engine/patches/wasmer-wasix/0003-terminal-memory-attachment.patch b/src/wasix/runtime/engine/patches/wasmer-wasix/0003-terminal-memory-attachment.patch new file mode 100644 index 000000000..27ef23ddf --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer-wasix/0003-terminal-memory-attachment.patch @@ -0,0 +1,67 @@ +From: Sid Jain +Date: Thu, 8 Oct 2026 00:00:00 +0000 +Subject: [PATCH 3/3] wasix: propagate terminal shared memory cancellation + +Use the engine terminal-cancellation operation for process shutdown. Propagate +failed shared-memory attachment through the existing task and linker errors +instead of panicking when shutdown races with task creation. + +The complete ordered series is the build and qualification unit. + +diff --git a/src/os/task/process.rs b/src/os/task/process.rs +--- a/src/os/task/process.rs ++++ b/src/os/task/process.rs +@@ -925,9 +925,9 @@ + }; + + if signal == Signal::Sigkill { +- // On kill, disable atomics to prevent threads from resuming. +- // NOTE: disable_atomics also wakes all current waiters. +- if let Err(err) = memory.disable_atomics() { ++ // Terminal cancellation: V8 interrupts whole execution contexts; ++ // Sys keeps its existing memory-specific atomics shutdown. ++ if let Err(err) = memory.terminate_execution_contexts() { + tracing::trace!( + pid=%process.pid, + error = &err as &dyn std::error::Error, +diff --git a/src/runtime/task_manager/mod.rs b/src/runtime/task_manager/mod.rs +--- a/src/runtime/task_manager/mod.rs ++++ b/src/runtime/task_manager/mod.rs +@@ -206,7 +206,10 @@ + })?; + Ok(Some(mem)) + } +- SpawnType::AttachMemory(mem) => Ok(Some(mem.attach(store))), ++ SpawnType::AttachMemory(mem) => mem ++ .try_attach(store) ++ .map(Some) ++ .map_err(WasiThreadError::MemoryCreateFailed), + SpawnType::CreateMemory | SpawnType::NewLinkerInstanceGroup(..) => Ok(None), + } + } +diff --git a/src/runtime/task_manager/tokio.rs b/src/runtime/task_manager/tokio.rs +--- a/src/runtime/task_manager/tokio.rs ++++ b/src/runtime/task_manager/tokio.rs +@@ -150,7 +150,9 @@ + SpawnType::CreateMemoryOfType(t) => (SpawnMemoryTypeOrStore::Type(t), None), + SpawnType::AttachMemory(mem) => { + let mut store = task.env.runtime().new_store(); +- let memory = mem.attach(&mut store); ++ let memory = mem ++ .try_attach(&mut store) ++ .map_err(WasiThreadError::MemoryCreateFailed)?; + (SpawnMemoryTypeOrStore::StoreAndMemory(store, memory), None) + } + }; +diff --git a/src/state/linker/mod.rs b/src/state/linker/mod.rs +--- a/src/state/linker/mod.rs ++++ b/src/state/linker/mod.rs +@@ -692,7 +692,7 @@ + + let mut imports = import_object_for_all_wasi_versions(&main_module, store, &func_env.env); + +- let memory = memory.attach(store); ++ let memory = memory.try_attach(store)?; + + let indirect_function_table = create_indirect_function_table( + store, diff --git a/src/wasix/runtime/engine/patches/wasmer-wasix/series b/src/wasix/runtime/engine/patches/wasmer-wasix/series new file mode 100644 index 000000000..0ecfae9ad --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer-wasix/series @@ -0,0 +1,3 @@ +0001-optional-runner-features.patch +0002-cached-module-loading.patch +0003-terminal-memory-attachment.patch diff --git a/src/wasix/runtime/engine/patches/wasmer/0001-v8-engine-delivery.patch b/src/wasix/runtime/engine/patches/wasmer/0001-v8-engine-delivery.patch new file mode 100644 index 000000000..4a669ea36 --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer/0001-v8-engine-delivery.patch @@ -0,0 +1,51 @@ +From: Sid Jain +Date: Thu, 8 Oct 2026 00:00:00 +0000 +Subject: [PATCH 1/4] v8: prepare the bundled Windows engine before dispatch + +Return retryable Windows delivery errors before creating the process-wide V8 +engine. Configure the pinned flags once, before engine initialization. + +The complete ordered series is the build and qualification unit. + +diff --git a/src/backend/v8/entities/engine.rs b/src/backend/v8/entities/engine.rs +--- a/src/backend/v8/entities/engine.rs ++++ b/src/backend/v8/entities/engine.rs +@@ -1,4 +1,4 @@ +-//! Data types, functions and traits for `sys` runtime's `Engine` implementation. ++//! Data types, functions and traits for `v8` runtime's `Engine` implementation. + use crate::{ + BackendEngine, + backend::v8::bindings::{wasm_engine_delete, wasm_engine_new, wasm_engine_t}, +@@ -27,9 +27,15 @@ + + impl Default for CApiEngine { + fn default() -> Self { +- let engine = ENGINE +- .get_or_init(|| unsafe { std::sync::Mutex::new(EngineCapsule(wasm_engine_new())) }); +- let engine = unsafe { engine.lock().unwrap().0 }; ++ #[cfg(windows)] ++ crate::backend::v8::bindings::oliphaunt_v8_prepare() ++ .expect("prepare Windows V8 engine; use V8::try_new for delivery errors"); ++ let engine = ENGINE.get_or_init(|| unsafe { ++ #[cfg(windows)] ++ crate::backend::v8::bindings::oliphaunt_v8_configure(); ++ std::sync::Mutex::new(EngineCapsule(wasm_engine_new())) ++ }); ++ let engine = engine.lock().unwrap().0; + Self { engine } + } + } +@@ -48,6 +54,13 @@ + /// Create a new instance of the `V8` engine. + pub fn new() -> Self { + Self::default() ++ } ++ ++ /// Prepare the embedded native engine, returning retryable delivery errors. ++ pub fn try_new() -> std::io::Result { ++ #[cfg(windows)] ++ crate::backend::v8::bindings::oliphaunt_v8_prepare()?; ++ Ok(Self::default()) + } + + pub(crate) fn deterministic_id(&self) -> String { diff --git a/src/wasix/runtime/engine/patches/wasmer/0002-v8-handle-and-call-ownership.patch b/src/wasix/runtime/engine/patches/wasmer/0002-v8-handle-and-call-ownership.patch new file mode 100644 index 000000000..5c6da377a --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer/0002-v8-handle-and-call-ownership.patch @@ -0,0 +1,691 @@ +From: Sid Jain +Date: Thu, 8 Oct 2026 00:00:00 +0000 +Subject: [PATCH 2/4] v8: retain C handles and preserve borrowed call values + +Give Stores ownership of C wrappers borrowed by Rust aliases. Release temporary +types and traps while their isolate is live, and copy borrowed call values and +imports before passing them to owning C API vectors. + +The complete ordered series is the build and qualification unit. + +diff --git a/src/backend/v8/entities/function/mod.rs b/src/backend/v8/entities/function/mod.rs +--- a/src/backend/v8/entities/function/mod.rs ++++ b/src/backend/v8/entities/function/mod.rs +@@ -59,6 +59,12 @@ + .field("env_is_some", &self.env_handle.is_some()) + .finish() + } ++} ++ ++// Each constructor transfers one unique callback environment to V8. ++// V8's existing host-info finalizer releases it when its isolate is disposed. ++unsafe extern "C" fn drop_callback_env(ptr: *mut c_void) { ++ unsafe { drop(Box::from_raw(ptr.cast::>())) }; + } + + impl Function { +@@ -145,8 +151,21 @@ + wasm_functype, + Some(callback), + callback_env as *mut _ as _, +- None, ++ Some(drop_callback_env::), + ) ++ }; ++ ++ // The callback has a borrowed StoreMut; retain only its unique C root. ++ unsafe { ++ let root = wasm_func_as_ref(wasm_function); ++ (*callback_env) ++ .store ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(root)); ++ wasm_functype_delete(wasm_functype); + }; + + if wasm_function.is_null() { +@@ -217,8 +236,21 @@ + wasm_functype, + Some(callback), + callback_env as _, +- None, ++ Some(drop_callback_env::), + ) ++ }; ++ ++ // The callback has a borrowed StoreMut; retain only its unique C root. ++ unsafe { ++ let root = wasm_func_as_ref(wasm_function); ++ (*callback_env) ++ .store ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(root)); ++ wasm_functype_delete(wasm_functype); + }; + + if wasm_function.is_null() { +@@ -297,8 +329,21 @@ + wasm_functype, + Some(callback), + callback_env as _, +- None, ++ Some(drop_callback_env::), + ) ++ }; ++ ++ // The callback has a borrowed StoreMut; retain only its unique C root. ++ unsafe { ++ let root = wasm_func_as_ref(wasm_function); ++ (*callback_env) ++ .store ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(root)); ++ wasm_functype_delete(wasm_functype); + }; + + if wasm_function.is_null() { +@@ -332,6 +377,7 @@ + res + }; + ++ unsafe { wasm_functype_delete(type_) }; + FunctionType::new(params, returns) + } + +@@ -375,31 +421,34 @@ + + // let wasm_func_param_arity(self.handle) + +- let mut args = unsafe { ++ let mut args = typed::OwnedValueVec(unsafe { + let mut wasm_params = params + .iter() +- .map(|v| IntoCApiValue::into_cv(v.clone())) ++ .map(|v| typed::own_call_parameter(IntoCApiValue::into_cv(v.clone()))) + .collect::>() + .into_boxed_slice(); + let mut vec = Default::default(); + wasm_val_vec_new(&mut vec, wasm_params.len(), wasm_params.as_ptr()); + vec +- }; ++ }); + + let size = unsafe { wasm_func_result_arity(self.handle) }; + +- let mut results = { ++ let mut results = typed::OwnedValueVec({ + unsafe { + let mut vec = Default::default(); + wasm_val_vec_new_uninitialized(&mut vec, size); + vec + } +- }; ++ }); + + let mut trap; + + loop { +- trap = unsafe { wasm_func_call(self.handle, &mut args as _, &mut results as *mut _) }; ++ store.as_store_ref().inner.store.as_v8().ensure_running()?; ++ let raw_trap = ++ unsafe { wasm_func_call(self.handle, &mut *args as _, &mut *results as *mut _) }; ++ trap = (!raw_trap.is_null()).then(|| Trap::from(raw_trap)); + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { + match callback(store_mut) { +@@ -418,15 +467,17 @@ + break; + } + +- if !trap.is_null() { +- return Err(Into::::into(trap).into()); ++ store.as_store_ref().inner.store.as_v8().ensure_running()?; ++ if let Some(trap) = trap { ++ return Err(trap.into()); + } + + let values = unsafe { + let results = std::ptr::slice_from_raw_parts(results.data, results.size); + (*results) + .iter() +- .map(|v| IntoWasmerValue::into_wv(*v)) ++ .zip(signature.results()) ++ .map(|(v, expected)| typed::into_call_value(*v, *expected, store)) + .collect::>() + .into_boxed_slice() + }; +@@ -526,8 +577,9 @@ + unsafe { trap.into_wasm_trap(&mut store) } + } + +- Err(e) => { +- unimplemented!("host function panicked"); ++ Err(_) => { ++ let trap = Trap::user(Box::new(RuntimeError::new("host function panicked"))); ++ unsafe { trap.into_wasm_trap(&mut store) } + } + } + } +@@ -634,8 +686,9 @@ + // unimplemented!("host function panicked"); + }, + +- Err(e) => { +- unimplemented!("host function panicked"); ++ Err(_) => { ++ let trap = Trap::user(Box::new(RuntimeError::new("host function panicked"))); ++ unsafe { trap.into_wasm_trap(store) } + } + } + }} +@@ -707,7 +760,10 @@ + let trap = crate::backend::v8::error::Trap::user(Box::new(e)); unsafe { trap.into_wasm_trap(store) } + }, + +- Err(e) => { unimplemented!("host function panicked"); } ++ Err(_) => { ++ let trap = Trap::user(Box::new(RuntimeError::new("host function panicked"))); ++ unsafe { trap.into_wasm_trap(store) } ++ } + } + }} + +diff --git a/src/backend/v8/entities/function/typed.rs b/src/backend/v8/entities/function/typed.rs +--- a/src/backend/v8/entities/function/typed.rs ++++ b/src/backend/v8/entities/function/typed.rs +@@ -11,12 +11,66 @@ + use std::iter::FromIterator; + + use crate::{ +- AsStoreMut, FromToNativeWasmType, NativeWasmType, NativeWasmTypeInto, RuntimeError, ++ AsStoreMut, AsStoreRef, FromToNativeWasmType, NativeWasmType, NativeWasmTypeInto, RuntimeError, + TypedFunction, Value, WasmTypeList, + backend::v8::{bindings::*, error::Trap, function::Function, utils::convert::*}, + }; + use wasmer_types::RawValue; + ++// Delete owned argument and result vectors on every return path. ++pub(super) struct OwnedValueVec(pub(super) wasm_val_vec_t); ++impl std::ops::Deref for OwnedValueVec { ++ type Target = wasm_val_vec_t; ++ fn deref(&self) -> &Self::Target { ++ &self.0 ++ } ++} ++impl std::ops::DerefMut for OwnedValueVec { ++ fn deref_mut(&mut self) -> &mut Self::Target { ++ &mut self.0 ++ } ++} ++impl Drop for OwnedValueVec { ++ fn drop(&mut self) { ++ unsafe { wasm_val_vec_delete(&mut self.0) } ++ } ++} ++// C vectors adopt reference wrappers; borrow a Function only through a copy. ++pub(super) fn own_call_parameter(value: wasm_val_t) -> wasm_val_t { ++ if value.kind == wasm_valkind_enum_WASM_FUNCREF as _ { ++ let mut owned = value; ++ unsafe { wasm_val_copy(&mut owned, &value) }; ++ owned ++ } else { ++ value ++ } ++} ++ ++// Returned Function aliases must outlive the temporary native result vector. ++pub(super) fn into_call_value( ++ mut value: wasm_val_t, ++ expected: wasmer_types::Type, ++ store: &mut impl AsStoreMut, ++) -> Value { ++ // The pinned C API reports returned function references as generic refs. ++ if expected == wasmer_types::Type::FuncRef { ++ value.kind = wasm_valkind_enum_WASM_FUNCREF as _; ++ let pointer = unsafe { value.of.ref_ }; ++ if pointer.is_null() { ++ return Value::FuncRef(None); ++ } ++ let owned = unsafe { wasm_ref_copy(pointer) }; ++ store ++ .as_store_mut() ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::super::store::OwnedRef(owned)); ++ value.of.ref_ = owned; ++ } ++ value.into_wv() ++} + macro_rules! impl_native_traits { + ( $( $x:ident ),* ) => { + #[allow(unused_parens, non_snake_case)] +@@ -37,28 +91,31 @@ + vec![ $( { + let raw = $x.to_native().into_raw(store); + let value = Value::from_raw(&mut store, <$x::Native as NativeWasmType>::WASM_TYPE, raw); +- value.into_cv() ++ own_call_parameter(value.into_cv()) + } ),* ] + }; + +- let mut results = { ++ let mut results = OwnedValueVec({ + unsafe { + let mut ret = std::mem::zeroed(); + wasm_val_vec_new_uninitialized(&mut ret, Rets::wasm_types().len()); + ret + } +- }; ++ }); + + + let func = unsafe { wasm_extern_as_func(self.func.to_vm_extern().unwrap_v_8()) }; + + let mut params = unsafe {std::mem::zeroed()}; + unsafe {wasm_val_vec_new(&mut params, params_list.len(), params_list.as_ptr())}; ++ let params = OwnedValueVec(params); + + let mut trap; + + loop { +- trap = unsafe { wasm_func_call(func, ¶ms, &mut results) }; ++ store.as_store_ref().inner.store.as_v8().ensure_running()?; ++ let raw_trap = unsafe { wasm_func_call(func, &*params, &mut *results) }; ++ trap = (!raw_trap.is_null()).then(|| Trap::from(raw_trap)); + + let store_mut = store.as_store_mut(); + if let Some(callback) = store_mut.inner.on_called.take() { +@@ -74,12 +131,10 @@ + + + +- if !trap.is_null() { +- unsafe { +- let trap: Trap = trap.into(); +- return Err(RuntimeError::from(trap)); +- } +- } ++ store.as_store_ref().inner.store.as_v8().ensure_running()?; ++ if let Some(trap) = trap { ++ return Err(RuntimeError::from(trap)); ++ } + + unsafe { + let mut rets_list_array = Rets::empty_array(); +@@ -89,13 +144,13 @@ + 0 => {}, + 1 => { + let val = (*results.data.wrapping_add(0)).clone(); +- let val = val.into_wv(); ++ let val = into_call_value(val, Rets::wasm_types()[0], &mut store); + *mut_rets = val.as_raw(&mut store); + } + _n => { + for (i, ret_type) in Rets::wasm_types().iter().enumerate() { + let val = (*results.data.wrapping_add(i)).clone(); +- let val = val.into_wv(); ++ let val = into_call_value(val, *ret_type, &mut store); + let slot = mut_rets.add(i); + *slot = val.as_raw(&mut store); + } +diff --git a/src/backend/v8/entities/global.rs b/src/backend/v8/entities/global.rs +--- a/src/backend/v8/entities/global.rs ++++ b/src/backend/v8/entities/global.rs +@@ -60,9 +60,17 @@ + let v8_global_type = + unsafe { wasm_globaltype_new(wasm_valtype_new(v8_type), v8_mutability) }; + +- Ok(Self { +- handle: unsafe { wasm_global_new(v8_store.inner, v8_global_type, &v8_value) }, +- }) ++ let handle = unsafe { wasm_global_new(v8_store.inner, v8_global_type, &v8_value) }; ++ unsafe { bindings::wasm_globaltype_delete(v8_global_type) }; ++ store ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(unsafe { ++ bindings::wasm_global_as_ref(handle) ++ })); ++ Ok(Self { handle }) + } + + pub fn ty(&self, store: &impl AsStoreRef) -> GlobalType { +@@ -73,14 +81,16 @@ + let valtype = unsafe { wasm_globaltype_content(r#type) }; + let wasmer_type = valtype.into_wt(); + +- GlobalType::new( ++ let result = GlobalType::new( + wasmer_type, + if mutability == bindings::wasm_mutability_enum_WASM_VAR as u8 { + Mutability::Var + } else { + Mutability::Const + }, +- ) ++ ); ++ unsafe { bindings::wasm_globaltype_delete(r#type) }; ++ result + } + + pub fn get(&self, store: &mut impl AsStoreMut) -> Value { +diff --git a/src/backend/v8/entities/instance.rs b/src/backend/v8/entities/instance.rs +--- a/src/backend/v8/entities/instance.rs ++++ b/src/backend/v8/entities/instance.rs +@@ -26,20 +26,15 @@ + let externs: Vec<_> = externs.into_iter().map(|v| v.unwrap_v_8()).collect(); + + let instance = unsafe { +- let mut imports = unsafe { +- let mut vec = Default::default(); +- wasm_extern_vec_new(&mut vec, externs.len(), externs.as_ptr()); +- vec +- }; +- +- std::mem::forget(externs); +- +- wasm_instance_new( +- store, +- wasm_module_obtain(store, module), +- &mut imports, +- &mut trap, +- ) ++ // The vector consumes C wrappers: copy borrowed reusable imports. ++ let copied: Vec<_> = externs.iter().map(|p| wasm_extern_copy(*p)).collect(); ++ let mut imports = wasm_extern_vec_t::default(); ++ wasm_extern_vec_new(&mut imports, copied.len(), copied.as_ptr()); ++ let local_module = wasm_module_obtain(store, module); ++ let instance = wasm_instance_new(store, local_module, &imports, &mut trap); ++ wasm_module_delete(local_module); ++ wasm_extern_vec_delete(&mut imports); ++ instance + }; + + if instance.is_null() { +@@ -65,7 +60,7 @@ + }; + + let exports_ty = module.exports().collect::>(); +- exports_ty ++ let result = exports_ty + .iter() + .zip(wasm_exports.iter()) + .map(|(export_type, wasm_export)| { +@@ -77,14 +72,18 @@ + let extern_ = Extern::from_vm_extern(&mut store, VMExtern::V8(*wasm_export)); + (name.to_string(), extern_) + }) +- .collect::() ++ .collect::(); ++ store ++ .as_store_mut() ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_exports ++ .push(super::store::OwnedExports(exports)); ++ result + } + } +-impl Drop for InstanceHandle { +- fn drop(&mut self) { +- unsafe { wasm_instance_delete(self.0) } +- } +-} ++// Store owns the C wrapper; a Rust alias may outlive its Store. + + #[derive(Clone, PartialEq, Eq)] + /// A WebAssembly `instance` in the `v8` runtime. +@@ -117,6 +116,12 @@ + ) -> Result<(Self, Exports), InstantiationError> { + check_isolate(store); + let store_ref = store.as_store_ref(); ++ store_ref ++ .inner ++ .store ++ .as_v8() ++ .ensure_running() ++ .map_err(InstantiationError::Start)?; + let externs: Vec = externs + .iter() + .map(|extern_| extern_.to_vm_extern()) +@@ -126,6 +131,22 @@ + module.as_v8().handle.v8_shared_module_handle, + externs, + )?; ++ // C instance wrappers belong to the Store, like their export aliases. ++ let owned = unsafe { wasm_instance_as_ref(instance.0) }; ++ store ++ .as_store_mut() ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(owned)); ++ store ++ .as_store_ref() ++ .inner ++ .store ++ .as_v8() ++ .ensure_running() ++ .map_err(InstantiationError::Start)?; + let exports = instance.get_exports(store, module); + + Ok(( +diff --git a/src/backend/v8/entities/table.rs b/src/backend/v8/entities/table.rs +--- a/src/backend/v8/entities/table.rs ++++ b/src/backend/v8/entities/table.rs +@@ -26,13 +26,12 @@ + pub(crate) fn type_to_v8(ty: TableType) -> *mut wasm_tabletype_t { + let valtype = unsafe { wasm_valtype_new(ty.ty.into_ct()) }; + +- let limits = Box::into_raw(Box::new(wasm_limits_t { ++ let limits = wasm_limits_t { + min: ty.minimum, + max: ty.maximum.unwrap_or(wasm_limits_max_default), + shared: false, +- })); +- +- unsafe { wasm_tabletype_new(valtype, limits) } ++ }; ++ unsafe { wasm_tabletype_new(valtype, &limits) } + } + + pub fn new( +@@ -49,9 +48,15 @@ + let wasm_tablety = Self::type_to_v8(ty); + let init: wasm_val_t = init.into_cv(); + +- Ok(Self { +- handle: unsafe { wasm_table_new(v8_store.inner, wasm_tablety, init.of.ref_) }, +- }) ++ let handle = unsafe { wasm_table_new(v8_store.inner, wasm_tablety, init.of.ref_) }; ++ unsafe { wasm_tabletype_delete(wasm_tablety) }; ++ store_mut ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(unsafe { wasm_table_as_ref(handle) })); ++ Ok(Self { handle }) + } + + pub fn to_vm_extern(&self) -> VMExtern { +@@ -65,10 +70,10 @@ + + let table_type: *mut wasm_tabletype_t = unsafe { wasm_table_type(self.handle) }; + let table_limits = unsafe { wasm_tabletype_limits(table_type) }; +- let table_type = unsafe { wasm_tabletype_element(table_type) }; +- +- TableType { +- ty: table_type.into_wt(), ++ let element_type = unsafe { wasm_tabletype_element(table_type) }; ++ ++ let result = TableType { ++ ty: element_type.into_wt(), + minimum: unsafe { (*table_limits).min }, + maximum: unsafe { + if (*table_limits).max == wasm_limits_max_default { +@@ -78,7 +83,9 @@ + } + }, + readonly: false, +- } ++ }; ++ unsafe { wasm_tabletype_delete(table_type) }; ++ result + } + + pub fn get(&self, store: &mut impl AsStoreMut, index: u32) -> Option { +diff --git a/src/backend/v8/entities/tag.rs b/src/backend/v8/entities/tag.rs +--- a/src/backend/v8/entities/tag.rs ++++ b/src/backend/v8/entities/tag.rs +@@ -21,6 +21,9 @@ + + unsafe impl Send for Tag {} + unsafe impl Sync for Tag {} ++ ++// C bridge uses ExternType's virtual implementation destructor. ++use crate::backend::v8::bindings::oliphaunt_externtype_delete; + + impl Tag { + pub fn new>>(store: &mut impl AsStoreMut, params: P) -> Self { +@@ -49,6 +52,13 @@ + } + + let handle = unsafe { wasm_tag_new(v8_store.inner, tag_type) }; ++ unsafe { oliphaunt_externtype_delete(tag_type) }; ++ store_mut ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(unsafe { wasm_tag_as_ref(handle) })); + if handle.is_null() { + panic!("failed to create new tag: returned tag is null"); + } +@@ -69,6 +79,7 @@ + res + }; + ++ unsafe { oliphaunt_externtype_delete(type_) }; + TagType::new(wasmer_types::TagKind::Exception, params) + } + +diff --git a/src/backend/v8/error.rs b/src/backend/v8/error.rs +--- a/src/backend/v8/error.rs ++++ b/src/backend/v8/error.rs +@@ -1,7 +1,4 @@ +-use std::{ +- error::Error, +- ffi::{CStr, c_char}, +-}; ++use std::error::Error; + + use crate::{ + AsStoreMut, +@@ -11,7 +8,7 @@ + #[derive(Debug)] + enum InnerTrap { + User(Box), +- CApi(*mut wasm_trap_t), ++ CApi(String), + } + + /// A struct representing a Trap +@@ -69,7 +66,14 @@ + #[allow(clippy::unnecessary_mut_passed)] + pub unsafe fn into_wasm_trap(self, store: &mut impl AsStoreMut) -> *mut wasm_trap_t { + match self.inner { +- InnerTrap::CApi(t) => t, ++ InnerTrap::CApi(message) => { ++ let mut data = wasm_byte_vec_t::default(); ++ unsafe { wasm_byte_vec_new(&mut data, message.len(), message.as_ptr() as _) }; ++ let store = store.as_store_mut(); ++ let trap = unsafe { wasm_trap_new(store.inner.store.as_v8().inner, &data) }; ++ unsafe { wasm_byte_vec_delete(&mut data) }; ++ trap ++ } + InnerTrap::User(err) => { + let err_ptr = Box::leak(Box::new(err)); + // UNSAFE: The boxed error is propagated by passing the string +@@ -90,13 +94,20 @@ + + impl From<*mut wasm_trap_t> for Trap { + fn from(value: *mut wasm_trap_t) -> Self { ++ // Snapshot the message while the Store is live. RuntimeError can outlive ++ // its Store, so it must not retain an isolate-owned C API trap handle. + let message = unsafe { +- let mut message = unsafe { std::mem::zeroed() }; +- unsafe { wasm_trap_message(value, &mut message) }; +- +- CStr::from_ptr(message.data as *const c_char) +- .to_str() +- .unwrap() ++ let mut bytes = wasm_byte_vec_t::default(); ++ wasm_trap_message(value, &mut bytes); ++ let text = String::from_utf8_lossy(std::slice::from_raw_parts( ++ bytes.data.cast::(), ++ bytes.size, ++ )) ++ .trim_end_matches('\0') ++ .to_owned(); ++ wasm_byte_vec_delete(&mut bytes); ++ wasm_trap_delete(value); ++ text + }; + + // User traps are encoded by `into_wasm_trap` as a leaked `Box` pointer +@@ -104,8 +115,8 @@ + if let Some((_, ptr_str)) = message.split_once("🐛") { + let ptr: Box = unsafe { + let r = ptr_str.trim_start_matches("0x"); +- std::ptr::read(usize::from_str_radix(r, 16).unwrap() +- as *const Box) ++ *Box::from_raw(usize::from_str_radix(r, 16).unwrap() ++ as *mut Box) + }; + + Self { +@@ -113,7 +124,7 @@ + } + } else { + Self { +- inner: InnerTrap::CApi(value), ++ inner: InnerTrap::CApi(message), + } + } + } +@@ -132,18 +143,7 @@ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner { + InnerTrap::User(e) => write!(f, "{e}"), +- InnerTrap::CApi(value) => { +- // let message: wasm_message_t; +- // wasm_trap_message(value, &mut message); +- let mut out = unsafe { +- let mut vec: wasm_byte_vec_t = Default::default(); +- wasm_byte_vec_new_empty(&mut vec); +- &mut vec as *mut _ +- }; +- unsafe { wasm_trap_message(*value, out) }; +- let cstr = unsafe { CStr::from_ptr((*out).data) }; +- write!(f, "wasm-c-api trap: {}", cstr.to_str().unwrap()) +- } ++ InnerTrap::CApi(message) => write!(f, "wasm-c-api trap: {message}"), + } + } + } diff --git a/src/wasix/runtime/engine/patches/wasmer/0003-v8-native-cache-profiles.patch b/src/wasix/runtime/engine/patches/wasmer/0003-v8-native-cache-profiles.patch new file mode 100644 index 000000000..a4f8483a7 --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer/0003-v8-native-cache-profiles.patch @@ -0,0 +1,309 @@ +From: Sid Jain +Date: Thu, 8 Oct 2026 00:00:00 +0000 +Subject: [PATCH 3/4] v8: load compatible producer-built native cache profiles + +Keep module sharing independent of the original Store. Serialize genuine native +CPU profiles for the same guest and reject incomplete or incompatible caches +without falling back to guest compilation. Include the Windows cache contracts. + +The complete ordered series is the build and qualification unit. + +diff --git a/src/backend/v8/entities/module.rs b/src/backend/v8/entities/module.rs +--- a/src/backend/v8/entities/module.rs ++++ b/src/backend/v8/entities/module.rs +@@ -15,22 +15,97 @@ + }; + use wasmparser::{Parser, Payload}; + ++#[cfg(all(test, windows))] ++mod oliphaunt_contract { ++ use super::*; ++ ++ const GUEST: &[u8] = b"\0asm\x01\0\0\0\x01\x05\x01\x60\0\x01\x7f\x03\x02\x01\0\x07\x0a\x01\x06answer\0\0\x0a\x06\x01\x04\0\x41\x2a\x0b"; ++ ++ fn native_offset(cache: &[u8]) -> usize { ++ assert!(cache[0] < 128, "the fixture has a one-byte wire length"); ++ 1 + usize::from(cache[0]) ++ } ++ ++ fn encoded(info: &V8ModuleInfo) -> Vec { ++ rkyv::to_bytes::(info) ++ .unwrap() ++ .to_vec() ++ } ++ ++ #[test] ++ fn rejects_wire_only_and_incompatible_caches_without_compilation() { ++ let engine: crate::Engine = crate::v8::V8::try_new().unwrap().into(); ++ let module = Module::from_binary(&engine, GUEST).unwrap(); ++ let mut info = module.info.clone(); ++ let offset = native_offset(&info.v8_module_bytes[0]); ++ info.v8_module_bytes[0].truncate(offset); ++ let error = unsafe { crate::Module::deserialize(&engine, encoded(&info)) }.unwrap_err(); ++ assert!(error.to_string().contains("guest compilation is disabled")); ++ ++ let mut info = module.info.clone(); ++ // Deliberately incompatible test bytes, never a producer workaround. ++ info.v8_module_bytes[0][offset + 8..offset + 12].copy_from_slice(&u32::MAX.to_le_bytes()); ++ assert!(unsafe { crate::Module::deserialize(&engine, encoded(&info)) }.is_err()); ++ } ++ ++ #[test] ++ fn skips_an_incompatible_cpu_profile_and_executes_the_next_native_cache() { ++ let engine: crate::Engine = crate::v8::V8::try_new().unwrap().into(); ++ let module = Module::from_binary(&engine, GUEST).unwrap(); ++ let mut info = module.info.clone(); ++ let original = info.v8_module_bytes[0].clone(); ++ let offset = native_offset(&original); ++ assert_eq!( ++ u32::from_le_bytes(original[offset + 8..offset + 12].try_into().unwrap()), ++ 0x1000e, ++ "the native runner must qualify the CET profile" ++ ); ++ info.v8_module_bytes[0][offset + 8..offset + 12].copy_from_slice(&u32::MAX.to_le_bytes()); ++ info.v8_module_bytes.push(original); ++ let loaded = unsafe { crate::Module::deserialize(&engine, encoded(&info)) }.unwrap(); ++ let mut store = crate::Store::new(engine); ++ let instance = crate::Instance::new(&mut store, &loaded, &crate::imports! {}).unwrap(); ++ let answer = instance ++ .exports ++ .get_typed_function::<(), i32>(&store, "answer") ++ .unwrap(); ++ assert_eq!(answer.call(&mut store).unwrap(), 42); ++ } ++ ++ #[test] ++ fn rejects_variants_for_another_guest() { ++ let engine: crate::Engine = crate::v8::V8::try_new().unwrap().into(); ++ let module = Module::from_binary(&engine, GUEST).unwrap(); ++ let mut different = GUEST.to_vec(); ++ let position = different.len() - 2; ++ different[position] = 43; ++ let other = Module::from_binary(&engine, &different).unwrap(); ++ assert!( ++ module ++ .serialize_with_native_variants(&other.info.v8_module_bytes) ++ .is_err() ++ ); ++ } ++} ++ + #[derive(Debug)] + pub(crate) struct ModuleHandle { + pub(crate) v8_shared_module_handle: *mut wasm_shared_module_t, +- pub(crate) orig_store: Store, ++ pub(crate) engine: crate::Engine, + } + + impl PartialEq for ModuleHandle { + fn eq(&self, other: &Self) -> bool { ++ // Compare in one isolate; each obtained wrapper is unique. + unsafe { +- wasm_module_same( +- wasm_module_obtain(self.orig_store.as_v8().inner, self.v8_shared_module_handle), +- wasm_module_obtain( +- other.orig_store.as_v8().inner, +- other.v8_shared_module_handle, +- ), +- ) ++ let transient = Store::new(self.engine.clone()); ++ let store = transient.as_v8().inner; ++ let left = wasm_module_obtain(store, self.v8_shared_module_handle); ++ let right = wasm_module_obtain(store, other.v8_shared_module_handle); ++ let same = wasm_module_same(left, right); ++ wasm_module_delete(right); ++ wasm_module_delete(left); ++ same + } + } + } +@@ -46,7 +121,6 @@ + + let engine = engine.as_engine_ref().engine().clone(); + let store = Store::new(engine.clone()); +- let engine = engine.as_v8().inner.engine; + + let inner = unsafe { wasm_module_new(store.as_v8().inner, &bytes as *const _) }; + +@@ -56,7 +130,11 @@ + )); + } + +- let inner = unsafe { wasm_module_share(inner) }; ++ let inner = unsafe { ++ let shared = wasm_module_share(inner); ++ wasm_module_delete(inner); ++ shared ++ }; + + if inner.is_null() { + return Err(CompileError::Validate( +@@ -66,11 +144,39 @@ + + Ok(Self { + v8_shared_module_handle: inner, +- orig_store: store, ++ engine, + }) + } + + fn deserialize(engine: &impl AsEngineRef, binary: &[u8]) -> Result { ++ // The pinned C API compiles wire-only serialized modules. Deserialization ++ // of an AOT artifact must reject that case before entering V8. ++ let mut wire_len = 0u64; ++ let mut prefix_len = None; ++ for (index, byte) in binary.iter().copied().take(10).enumerate() { ++ if index == 9 && byte > 1 { ++ break; ++ } ++ wire_len |= u64::from(byte & 0x7f) << (index * 7); ++ if byte & 0x80 == 0 { ++ prefix_len = Some(index + 1); ++ break; ++ } ++ } ++ let native_start = prefix_len.and_then(|n| { ++ usize::try_from(wire_len) ++ .ok() ++ .and_then(|wire| n.checked_add(wire)) ++ }); ++ if native_start ++ .and_then(|n| binary.len().checked_sub(n)) ++ .is_none_or(|n| n < 20) ++ { ++ return Err(CompileError::Validate( ++ "V8 AOT artifact has no complete native payload; guest compilation is disabled" ++ .into(), ++ )); ++ } + let bytes = wasm_byte_vec_t { + size: binary.len(), + data: binary.as_ptr() as _, +@@ -86,7 +192,11 @@ + )); + } + +- let inner = unsafe { wasm_module_share(inner) }; ++ let inner = unsafe { ++ let shared = wasm_module_share(inner); ++ wasm_module_delete(inner); ++ shared ++ }; + + if inner.is_null() { + return Err(CompileError::Validate( +@@ -96,15 +206,16 @@ + + Ok(Self { + v8_shared_module_handle: inner, +- orig_store: store, ++ engine, + }) + } + + #[tracing::instrument(skip(self))] + pub fn serialize(&self) -> Result, SerializeError> { ++ let transient = Store::new(self.engine.clone()); + let handle = unsafe { + wasm_module_obtain( +- self.orig_store.as_v8().inner, ++ transient.as_v8().inner, + self.v8_shared_module_handle as *const _, + ) + }; +@@ -147,7 +258,7 @@ + // (store) than the one V8 associated with the module, which can crash. + imports: Vec, + exports: Vec, +- v8_module_bytes: Vec, ++ v8_module_bytes: Vec>, + // Copy of the custom sections, since the current API cannot retrieve them + // later from the handle. Use a BTreeMap to keep entries in deterministic order, + // producing stable serialized bytes. +@@ -205,7 +316,7 @@ + name: info.name, + imports, + exports, +- v8_module_bytes, ++ v8_module_bytes: vec![v8_module_bytes], + custom_sections, + }, + }) +@@ -240,6 +351,50 @@ + .map_err(|err| SerializeError::Generic(format!("{err:?}")))? + .to_vec(); + Ok(info.into()) ++ } ++ ++ /// Serialize this module with additional producer-built native CPU profiles. ++ /// Every profile must contain the same WebAssembly wire bytes. ++ pub fn serialize_with_native_variants( ++ &self, ++ variants: &[Vec], ++ ) -> Result { ++ fn wire(bytes: &[u8]) -> Option<&[u8]> { ++ let mut length = 0u64; ++ for (index, byte) in bytes.iter().copied().take(10).enumerate() { ++ if index == 9 && byte > 1 { ++ return None; ++ } ++ length |= u64::from(byte & 0x7f) << (index * 7); ++ if byte & 0x80 == 0 { ++ let end = (index + 1).checked_add(usize::try_from(length).ok()?)?; ++ if bytes.len().checked_sub(end)? < 20 { ++ return None; ++ } ++ return bytes.get(index + 1..end); ++ } ++ } ++ None ++ } ++ let original = self ++ .info ++ .v8_module_bytes ++ .first() ++ .ok_or_else(|| SerializeError::Generic("module has no native cache".into()))?; ++ let expected = wire(original) ++ .ok_or_else(|| SerializeError::Generic("module has no complete native cache".into()))?; ++ let mut info = self.info.clone(); ++ for variant in variants { ++ if wire(variant) != Some(expected) { ++ return Err(SerializeError::Generic( ++ "native cache wire identity mismatch".into(), ++ )); ++ } ++ info.v8_module_bytes.push(variant.clone()); ++ } ++ rkyv::to_bytes::(&info) ++ .map(|bytes| bytes.to_vec().into()) ++ .map_err(|error| SerializeError::Generic(format!("{error:?}"))) + } + + #[allow(clippy::arc_with_non_send_sync)] +@@ -252,7 +407,25 @@ + let info = rkyv::access::(&binary) + .and_then(rkyv::deserialize::) + .map_err(|err| DeserializeError::CorruptedBinary(format!("{err:?}")))?; +- let module = ModuleHandle::deserialize(engine, &info.v8_module_bytes)?; ++ // Native caches retain their original CPU/flag headers. Try the producer's ++ // profiles without compiling a guest when no cache is compatible. ++ let mut failures = Vec::new(); ++ let module = info ++ .v8_module_bytes ++ .iter() ++ .find_map(|cache| match ModuleHandle::deserialize(engine, cache) { ++ Ok(module) => Some(module), ++ Err(error) => { ++ failures.push(error.to_string()); ++ None ++ } ++ }) ++ .ok_or_else(|| { ++ DeserializeError::CorruptedBinary(format!( ++ "no compatible V8 native cache: {}", ++ failures.join("; ") ++ )) ++ })?; + + Ok(Self { + handle: Arc::new(module), diff --git a/src/wasix/runtime/engine/patches/wasmer/0004-v8-memory-and-terminal-lifetimes.patch b/src/wasix/runtime/engine/patches/wasmer/0004-v8-memory-and-terminal-lifetimes.patch new file mode 100644 index 000000000..e78cbb601 --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer/0004-v8-memory-and-terminal-lifetimes.patch @@ -0,0 +1,546 @@ +From: Sid Jain +Date: Thu, 8 Oct 2026 00:00:00 +0000 +Subject: [PATCH 4/4] v8: coordinate shared memory and terminal Store lifetimes + +Preserve memory copies across growth and attach shared memory with its owning +Store interruption state. Retire isolate pointers before Store destruction and +reject attachment and guest reentry after terminal cancellation. The ownership +wrappers introduced by the preceding patch are supplied here. + +The complete ordered series is the build and qualification unit. + +diff --git a/src/backend/v8/entities/memory/mod.rs b/src/backend/v8/entities/memory/mod.rs +--- a/src/backend/v8/entities/memory/mod.rs ++++ b/src/backend/v8/entities/memory/mod.rs +@@ -53,14 +53,23 @@ + }); + } + +- let limits = Box::into_raw(Box::new(wasm_limits_t { ++ let limits = wasm_limits_t { + min, + max, + shared: ty.shared, +- })); +- +- let memorytype = unsafe { wasm_memorytype_new(limits) }; ++ }; ++ ++ let memorytype = unsafe { wasm_memorytype_new(&limits) }; + let c_memory = unsafe { wasm_memory_new(v8_store.inner, memorytype) }; ++ unsafe { wasm_memorytype_delete(memorytype) }; ++ store_mut ++ .inner ++ .store ++ .as_v8_mut() ++ .owned_refs ++ .push(super::store::OwnedRef(unsafe { ++ wasm_memory_as_ref(c_memory) ++ })); + + Ok(Self { + handle: VMMemory(c_memory), +@@ -84,7 +93,7 @@ + let memory_type: *mut wasm_memorytype_t = unsafe { wasm_memory_type(self.handle.0) }; + let limits: *const wasm_limits_t = unsafe { wasm_memorytype_limits(memory_type) }; + +- MemoryType { ++ let result = MemoryType { + shared: unsafe { (*limits).shared }, + minimum: unsafe { wasmer_types::Pages((*limits).min) }, + maximum: unsafe { +@@ -94,7 +103,9 @@ + Some(wasmer_types::Pages((*limits).max)) + } + }, +- } ++ }; ++ unsafe { wasm_memorytype_delete(memory_type) }; ++ result + } + + pub fn size(&self, store: &impl AsStoreRef) -> Pages { +@@ -198,6 +209,8 @@ + )); + } + ++ // Own the temporary wrapper on success and every early-return path. ++ let _copied_owner = super::store::OwnedRef(unsafe { wasm_memory_as_ref(copied_memory) }); + let copied = Self { + handle: VMMemory(copied_memory), + }; +@@ -226,9 +239,14 @@ + .copy_to_memory(src_view.data_size(), &dst_view) + .map_err(|err| MemoryError::Generic(format!("failed to copy V8 memory: {err}")))?; + +- Ok(SharedMemory::new(crate::vm::VMSharedMemory::V8( +- copied.handle.as_shared()?, +- ))) ++ let shared = copied ++ .handle ++ .as_shared(super::store::interrupt::MemoryInterrupt::detached())?; ++ let ops = shared.1.clone(); ++ Ok(SharedMemory::new_with_ops( ++ crate::vm::VMSharedMemory::V8(shared), ++ ops, ++ )) + } + + pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { +@@ -238,9 +256,16 @@ + + pub fn as_shared(&self, store: &impl AsStoreRef) -> Result { + check_isolate(store); +- Ok(SharedMemory::new(crate::vm::VMSharedMemory::V8( +- self.handle.as_shared()?, +- ))) ++ let store = store.as_store_ref(); ++ let v8_store = store.inner.store.as_v8(); ++ let shared = self ++ .handle ++ .as_shared(v8_store.shared_memory_ops(self.handle.0))?; ++ let ops = shared.1.clone(); ++ Ok(SharedMemory::new_with_ops( ++ crate::vm::VMSharedMemory::V8(shared), ++ ops, ++ )) + } + } + +diff --git a/src/backend/v8/entities/store/interrupt.rs b/src/backend/v8/entities/store/interrupt.rs +new file mode 100644 +--- /dev/null ++++ b/src/backend/v8/entities/store/interrupt.rs +@@ -0,0 +1,164 @@ ++use std::{ ++ cell::Cell, ++ ffi::c_void, ++ sync::{ ++ Arc, Mutex, Weak, ++ atomic::{AtomicBool, Ordering}, ++ }, ++}; ++ ++use crate::backend::v8::bindings::*; ++use crate::{error::AtomicsError, location::SharedMemoryOps}; ++ ++#[cfg(not(windows))] ++unsafe extern "C" { ++ #[link_name = "_ZN2v87Isolate10GetCurrentEv"] ++ fn current_isolate() -> *mut c_void; ++ #[link_name = "_ZN2v87Isolate18TerminateExecutionEv"] ++ fn terminate_execution(isolate: *mut c_void); ++} ++#[cfg(windows)] ++use crate::backend::v8::bindings::{ ++ oliphaunt_v8_current_isolate as current_isolate, ++ oliphaunt_v8_terminate_execution as terminate_execution, ++}; ++ ++thread_local! { static CAPTURED: Cell = const { Cell::new(0) }; } ++ ++unsafe extern "C" fn capture( ++ _args: *const wasm_val_vec_t, ++ _results: *mut wasm_val_vec_t, ++) -> *mut wasm_trap_t { ++ // The pinned C API enters the Store's isolate before a direct host call. ++ CAPTURED.with(|slot| slot.set(unsafe { current_isolate() } as usize)); ++ std::ptr::null_mut() ++} ++ ++pub(super) struct StoreInterrupt { ++ isolate: Mutex>, ++ terminated: AtomicBool, ++} ++ ++impl StoreInterrupt { ++ pub(super) fn new() -> Arc { ++ Arc::new(Self { ++ isolate: Mutex::new(None), ++ terminated: AtomicBool::new(false), ++ }) ++ } ++ ++ pub(super) fn capture(&self, store: *mut wasm_store_t) { ++ unsafe { ++ let mut params = std::mem::zeroed(); ++ let mut results = std::mem::zeroed(); ++ let ty = wasm_functype_new(&mut params, &mut results); ++ assert!(!ty.is_null(), "cannot create V8 interrupt capture type"); ++ let function = wasm_func_new(store, ty, Some(capture)); ++ wasm_functype_delete(ty); ++ assert!( ++ !function.is_null(), ++ "cannot create V8 interrupt capture function" ++ ); ++ let owner = super::OwnedRef(wasm_func_as_ref(function)); ++ let mut args = std::mem::zeroed(); ++ let mut results = std::mem::zeroed(); ++ CAPTURED.with(|slot| slot.set(0)); ++ let trap = wasm_func_call(function, &mut args, &mut results); ++ let pointer = CAPTURED.with(Cell::get); ++ if !trap.is_null() { ++ wasm_trap_delete(trap); ++ } ++ drop(owner); ++ assert!( ++ trap.is_null() && pointer != 0, ++ "cannot capture V8 Store interrupt" ++ ); ++ *self.isolate.lock().unwrap_or_else(|e| e.into_inner()) = Some(pointer); ++ } ++ } ++ ++ pub(super) fn is_terminated(&self) -> bool { ++ self.terminated.load(Ordering::Acquire) ++ } ++ ++ fn terminate(&self) { ++ self.terminated.store(true, Ordering::Release); ++ // Keep the lock through termination. Store teardown takes this same ++ // lock before retiring the pointer and deleting the native Store. ++ let pointer = self.isolate.lock().unwrap_or_else(|e| e.into_inner()); ++ if let Some(pointer) = *pointer { ++ unsafe { terminate_execution(pointer as *mut c_void) }; ++ } ++ } ++ ++ pub(super) fn retire(&self) { ++ *self.isolate.lock().unwrap_or_else(|e| e.into_inner()) = None; ++ } ++} ++ ++#[derive(Default)] ++struct MemoryState { ++ terminal: bool, ++ stores: Vec>, ++} ++ ++pub(crate) struct MemoryInterrupt(Mutex); ++ ++impl MemoryInterrupt { ++ pub(crate) fn detached() -> Arc { ++ Arc::new(Self(Mutex::new(MemoryState::default()))) ++ } ++ ++ pub(super) fn new(store: &Arc) -> Arc { ++ Arc::new(Self(Mutex::new(MemoryState { ++ terminal: false, ++ stores: vec![Arc::downgrade(store)], ++ }))) ++ } ++ ++ pub(crate) fn attach( ++ self: &Arc, ++ store: &mut super::Store, ++ memory: *mut wasm_shared_memory_t, ++ ) -> Result<*mut wasm_memory_t, crate::MemoryError> { ++ let mut state = self.0.lock().unwrap_or_else(|e| e.into_inner()); ++ if state.terminal { ++ return Err(crate::MemoryError::Generic( ++ "cannot attach V8 shared memory after execution contexts are terminal".to_owned(), ++ )); ++ } ++ let handle = unsafe { wasm_memory_obtain(store.inner, memory) }; ++ if handle.is_null() { ++ return Err(crate::MemoryError::Generic( ++ "cannot obtain V8 shared memory".to_owned(), ++ )); ++ } ++ state.stores.retain(|entry| entry.strong_count() != 0); ++ let interrupt = Arc::downgrade(&store.interrupt); ++ if !state ++ .stores ++ .iter() ++ .any(|entry| Weak::ptr_eq(entry, &interrupt)) ++ { ++ state.stores.push(interrupt); ++ } ++ store.remember_shared_memory(handle, self.clone()); ++ Ok(handle) ++ } ++} ++ ++impl SharedMemoryOps for MemoryInterrupt { ++ fn terminate_execution_contexts(&self) -> Result<(), AtomicsError> { ++ let mut state = self.0.lock().unwrap_or_else(|e| e.into_inner()); ++ state.terminal = true; ++ state.stores.retain(|entry| { ++ if let Some(store) = entry.upgrade() { ++ store.terminate(); ++ true ++ } else { ++ false ++ } ++ }); ++ Ok(()) ++ } ++} +diff --git a/src/backend/v8/entities/store/mod.rs b/src/backend/v8/entities/store/mod.rs +--- a/src/backend/v8/entities/store/mod.rs ++++ b/src/backend/v8/entities/store/mod.rs +@@ -1,5 +1,5 @@ + //! Data types, functions and traits for `v8` runtime's `Store` implementation. +-use std::thread::ThreadId; ++use std::{cell::RefCell, sync::Arc, thread::ThreadId}; + + use crate::{ + AsStoreRef, BackendStore, StoreRef, +@@ -7,14 +7,36 @@ + engine::{AsEngineRef, Engine, EngineRef}, + }; + ++pub(crate) mod interrupt; + mod obj; + pub use obj::*; ++ ++#[derive(Debug)] ++pub(crate) struct OwnedExports(pub(crate) crate::backend::v8::bindings::wasm_extern_vec_t); ++ ++impl Drop for OwnedExports { ++ fn drop(&mut self) { ++ unsafe { crate::backend::v8::bindings::wasm_extern_vec_delete(&mut self.0) }; ++ } ++} ++ ++#[derive(Debug)] ++pub(crate) struct OwnedRef(pub(crate) *mut crate::backend::v8::bindings::wasm_ref_t); ++impl Drop for OwnedRef { ++ fn drop(&mut self) { ++ unsafe { crate::backend::v8::bindings::wasm_ref_delete(self.0) }; ++ } ++} + + /// A WebAssembly `store` in the `v8` runtime. + pub struct Store { + pub(crate) engine: Engine, + pub(crate) inner: *mut wasm_store_t, + pub(crate) thread_id: ThreadId, ++ pub(crate) owned_exports: Vec, ++ pub(crate) owned_refs: Vec, ++ interrupt: Arc, ++ shared_memories: RefCell)>>, + } + + impl std::fmt::Debug for Store { +@@ -29,10 +51,62 @@ + pub(crate) fn new(engine: crate::engine::Engine) -> Self { + let inner: *mut wasm_store_t = unsafe { wasm_store_new(engine.as_v8().inner.engine) }; + let thread_id = std::thread::current().id(); +- Self { ++ let store = Self { + inner, + engine, + thread_id, ++ owned_exports: Vec::new(), ++ owned_refs: Vec::new(), ++ interrupt: interrupt::StoreInterrupt::new(), ++ shared_memories: RefCell::new(Vec::new()), ++ }; ++ store.interrupt.capture(inner); ++ store ++ } ++ ++ pub(crate) fn shared_memory_ops( ++ &self, ++ memory: *mut crate::backend::v8::bindings::wasm_memory_t, ++ ) -> Arc { ++ use crate::backend::v8::bindings::*; ++ let mut memories = self.shared_memories.borrow_mut(); ++ if let Some((_, ops)) = memories ++ .iter() ++ .find(|(entry, _)| unsafe { wasm_memory_same(wasm_ref_as_memory(entry.0), memory) }) ++ { ++ return ops.clone(); ++ } ++ let ops = interrupt::MemoryInterrupt::new(&self.interrupt); ++ memories.push(( ++ OwnedRef(unsafe { wasm_memory_as_ref(wasm_memory_copy(memory)) }), ++ ops.clone(), ++ )); ++ ops ++ } ++ ++ pub(crate) fn remember_shared_memory( ++ &self, ++ memory: *mut crate::backend::v8::bindings::wasm_memory_t, ++ ops: Arc, ++ ) { ++ use crate::backend::v8::bindings::*; ++ let mut memories = self.shared_memories.borrow_mut(); ++ if !memories ++ .iter() ++ .any(|(entry, _)| unsafe { wasm_memory_same(wasm_ref_as_memory(entry.0), memory) }) ++ { ++ memories.push(( ++ OwnedRef(unsafe { wasm_memory_as_ref(wasm_memory_copy(memory)) }), ++ ops, ++ )); ++ } ++ } ++ ++ pub(crate) fn ensure_running(&self) -> Result<(), crate::RuntimeError> { ++ if self.interrupt.is_terminated() { ++ Err(crate::RuntimeError::new("V8 execution context terminated")) ++ } else { ++ Ok(()) + } + } + +@@ -47,6 +121,12 @@ + + impl Drop for Store { + fn drop(&mut self) { ++ // These vectors uniquely own the export wrappers. Rust export clones ++ // borrow them through their Store, so retain them until Store teardown. ++ self.interrupt.retire(); ++ self.shared_memories.get_mut().clear(); ++ self.owned_exports.clear(); ++ self.owned_refs.clear(); + unsafe { wasm_store_delete(self.inner) } + } + } +diff --git a/src/backend/v8/vm/mod.rs b/src/backend/v8/vm/mod.rs +--- a/src/backend/v8/vm/mod.rs ++++ b/src/backend/v8/vm/mod.rs +@@ -154,7 +154,10 @@ + #[derive(Debug, Clone)] + pub struct VMMemory(pub(super) *mut wasm_memory_t); + struct SharedMemoryPointer(*mut wasm_shared_memory_t); +-pub struct VMSharedMemory(Arc); ++pub struct VMSharedMemory( ++ Arc, ++ pub(crate) Arc, ++); + pub(crate) type VMExternMemory = *mut wasm_memory_t; + + impl SharedMemoryPointer { +@@ -182,7 +185,10 @@ + + impl VMMemory { + /// Created a shared detached memory that can be attached in another Store. +- pub(crate) fn as_shared(&self) -> Result { ++ pub(crate) fn as_shared( ++ &self, ++ ops: Arc, ++ ) -> Result { + let memory_type = unsafe { wasm_memory_type(self.0) }; + let limits = unsafe { wasm_memorytype_limits(memory_type) }; + if !unsafe { (*limits).shared } { +@@ -202,23 +208,36 @@ + )); + } + +- Ok(VMSharedMemory(Arc::new(SharedMemoryPointer::new(shared)))) ++ Ok(VMSharedMemory( ++ Arc::new(SharedMemoryPointer::new(shared)), ++ ops, ++ )) + } + } + + impl VMSharedMemory { + /// We can clone a shared memory handle as we want. + pub(crate) fn clone(&self) -> Self { +- Self(self.0.clone()) ++ Self(self.0.clone(), self.1.clone()) + } + + /// Attach the shared memory handle to a new Store and convert it to VMMemory + pub(crate) fn into_vm_memory(self, store: &mut Store) -> VMMemory { +- let memory = unsafe { wasm_memory_obtain(store.inner, self.0.0) }; ++ self.try_into_vm_memory(store) ++ .expect("cannot attach V8 shared memory") ++ } ++ ++ pub(crate) fn try_into_vm_memory(self, store: &mut Store) -> Result { ++ let memory = self.1.attach(store, self.0.0)?; ++ store ++ .owned_refs ++ .push(crate::backend::v8::store::OwnedRef(unsafe { ++ wasm_memory_as_ref(memory) ++ })); + assert!( + !memory.is_null(), + "Failed to obtain V8 shared memory: wasm_memory_obtain returned null" + ); +- VMMemory(memory) +- } +-} ++ Ok(VMMemory(memory)) ++ } ++} +diff --git a/src/entities/memory/location.rs b/src/entities/memory/location.rs +--- a/src/entities/memory/location.rs ++++ b/src/entities/memory/location.rs +@@ -24,6 +24,10 @@ + + /// See [`crate::SharedMemory`]. + pub(crate) trait SharedMemoryOps { ++ fn terminate_execution_contexts(&self) -> Result<(), AtomicsError> { ++ self.disable_atomics() ++ } ++ + /// See [`crate::SharedMemory::disable_atomics`]. + fn disable_atomics(&self) -> Result<(), AtomicsError> { + Err(AtomicsError::Unimplemented) +diff --git a/src/entities/memory/shared.rs b/src/entities/memory/shared.rs +--- a/src/entities/memory/shared.rs ++++ b/src/entities/memory/shared.rs +@@ -68,6 +68,23 @@ + Memory::new_from_existing(store, memory) + } + ++ /// Attach shared memory, returning an error after V8 terminal cancellation. ++ /// ++ /// This permits task creation to race with terminal cancellation without ++ /// unwinding through the task or linker. Other backends retain their ++ /// existing attachment behavior. ++ pub fn try_attach(self, store: &mut impl AsStoreMut) -> Result { ++ let memory = match self.memory { ++ #[cfg(feature = "v8")] ++ VMSharedMemory::V8(memory) => { ++ let mut store = store.as_store_mut(); ++ VMMemory::V8(memory.try_into_vm_memory(store.inner.store.as_v8_mut())?) ++ } ++ memory => memory.into_vm_memory(store), ++ }; ++ Ok(Memory::new_from_existing(store, memory)) ++ } ++ + /// Create an operations handle that does not keep the underlying memory alive. + pub fn ops(&self) -> MemoryOps { + MemoryOps { +@@ -122,6 +139,16 @@ + } + + impl MemoryOps { ++ /// Terminally cancel execution contexts using this memory. ++ /// ++ /// V8 cancels whole Stores and rejects guest reentry; this is not a ++ /// memory-specific atomic operation. Hosts must retire these contexts. ++ /// Other backends retain their existing shutdown through atomics disabling. ++ #[doc(hidden)] ++ pub fn terminate_execution_contexts(&self) -> Result<(), AtomicsError> { ++ self.shared_ops()?.terminate_execution_contexts() ++ } ++ + #[inline] + fn shared_ops(&self) -> Result<&(dyn SharedMemoryOps + Send + Sync), AtomicsError> { + self.ops +diff --git a/src/vm/impls.rs b/src/vm/impls.rs +--- a/src/vm/impls.rs ++++ b/src/vm/impls.rs +@@ -59,7 +59,11 @@ + #[cfg(feature = "sys")] + Self::Sys(s) => s.0.as_shared().map(VMSharedMemory::Sys), + #[cfg(feature = "v8")] +- Self::V8(s) => s.as_shared().map(VMSharedMemory::V8), ++ // V8 sharing needs its owning Store to preserve interruption state. ++ // The V8 Memory API supplies it; this store-free helper is Sys-only. ++ Self::V8(_) => Err(wasmer_types::MemoryError::Generic( ++ "V8 memory sharing requires its owning Store".to_owned(), ++ )), + #[cfg(feature = "js")] + Self::Js(s) => s.try_clone().map(VMSharedMemory::Js), + } diff --git a/src/wasix/runtime/engine/patches/wasmer/series b/src/wasix/runtime/engine/patches/wasmer/series new file mode 100644 index 000000000..9f8b482d6 --- /dev/null +++ b/src/wasix/runtime/engine/patches/wasmer/series @@ -0,0 +1,4 @@ +0001-v8-engine-delivery.patch +0002-v8-handle-and-call-ownership.patch +0003-v8-native-cache-profiles.patch +0004-v8-memory-and-terminal-lifetimes.patch diff --git a/src/wasix/runtime/engine/prepare-sources.mts b/src/wasix/runtime/engine/prepare-sources.mts new file mode 100644 index 000000000..ec77d897f --- /dev/null +++ b/src/wasix/runtime/engine/prepare-sources.mts @@ -0,0 +1,100 @@ +import assert from 'node:assert/strict'; +import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { + archiveTreeDigest, + promotePathTransactional, + sha256File, +} from '../../../third-party/tools/source-fetch-core.mts'; +import { ENGINE_SOURCE_CRATES } from './contract.mts'; + +const root = path.resolve(import.meta.dir, '../../../..'); +const owner = path.join(root, 'src/wasix/runtime/engine'); +const pins = Bun.TOML.parse(readFileSync(path.join(owner, 'source.toml'), 'utf8')); + +function inputs(name) { + const directory = path.join(owner, 'patches', name); + return existsSync(directory) + ? readFileSync(path.join(directory, 'series'), 'utf8') + .trim() + .split('\n') + .map((file) => path.join(directory, file)) + : []; +} + +export function applyEnginePatches(directory: string, patches: string[]) { + // Match PostgreSQL's patch runner: ignore the enclosing Oliphaunt worktree. + // Otherwise Git-format patches can succeed while silently skipping hunks. + const options = { + cwd: directory, + env: { ...process.env, GIT_CEILING_DIRECTORIES: path.dirname(directory) }, + }; + for (const patch of patches) { + const checked = Bun.spawnSync( + ['git', 'apply', '--check', '--whitespace=error-all', patch], + options, + ); + assert.equal(checked.exitCode, 0, checked.stderr.toString()); + const applied = Bun.spawnSync(['git', 'apply', '--whitespace=error-all', patch], options); + assert.equal(applied.exitCode, 0, applied.stderr.toString()); + } +} + +function prepare(name) { + const source = path.join(root, 'target/oliphaunt-sources/checkouts', `sdk-${name}`); + const directory = path.join(owner, 'crates', name); + const output = path.join(directory, 'upstream'); + const patches = inputs(name); + const series = path.join(owner, 'patches', name, 'series'); + const signature = JSON.stringify([ + pins[name].sha256, + existsSync(series) ? sha256File(series) : null, + ...patches.map(sha256File), + ]); + const stamp = path.join(directory, '.prepared.json'); + if (existsSync(stamp) && existsSync(output)) { + const previous = JSON.parse(readFileSync(stamp, 'utf8')); + if (previous.signature === signature && previous.tree === archiveTreeDigest(output)) return; + } + const stage = path.join(directory, `.upstream-${process.pid}`); + rmSync(stage, { recursive: true, force: true }); + try { + cpSync(source, stage, { recursive: true }); + rmSync(path.join(stage, '.oliphaunt-source-pin')); + applyEnginePatches(stage, patches); + promotePathTransactional(stage, output); + writeFileSync(stamp, JSON.stringify({ signature, tree: archiveTreeDigest(output) }) + '\n'); + } finally { + rmSync(stage, { recursive: true, force: true }); + } +} + +if (import.meta.main) { + const [operation, plan] = Bun.argv.slice(2); + if (operation === 'plan' && plan) { + mkdirSync(plan, { recursive: true }); + for (const name of ENGINE_SOURCE_CRATES) { + const pin = pins[name]; + writeFileSync( + path.join(plan, `${name}.json`), + JSON.stringify({ + name: `sdk-${name}`, + kind: 'archive', + url: pin.url, + branch: 'oliphaunt-pinned', + commit: pin.sha256, + sha256: pin.sha256, + stripPrefix: `${name}-${pin.version}`, + }), + ); + } + writeFileSync( + path.join(plan, 'pins'), + ENGINE_SOURCE_CRATES.map((name) => path.resolve(plan, `${name}.json`) + '\0').join(''), + ); + } else if (operation === 'prepare' && !plan) { + for (const name of ENGINE_SOURCE_CRATES) prepare(name); + } else { + throw new Error('usage: prepare-sources.mts plan DIRECTORY | prepare'); + } +} diff --git a/src/wasix/runtime/engine/prepare-sources.sh b/src/wasix/runtime/engine/prepare-sources.sh new file mode 100644 index 000000000..1efb7e477 --- /dev/null +++ b/src/wasix/runtime/engine/prepare-sources.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(git rev-parse --show-toplevel)" +source src/third-party/tools/fetch-sources.sh +engine_dir=src/wasix/runtime/engine +plan=target/oliphaunt-wasix/engine/source-plan +bun "$engine_dir/prepare-sources.mts" plan "$plan" +while IFS= read -r -d '' pin; do + fetch_source "$pin" target/oliphaunt-sources/checkouts target/oliphaunt-sources/archives fetch + # Bash 3.2 does not propagate a failed subshell function through this loop. + status=$? + [[ "$status" == 0 ]] || exit "$status" +done < "$plan/pins" +bun "$engine_dir/prepare-sources.mts" prepare diff --git a/src/wasix/runtime/engine/prepare-sources.test.mts b/src/wasix/runtime/engine/prepare-sources.test.mts new file mode 100644 index 000000000..b895741e7 --- /dev/null +++ b/src/wasix/runtime/engine/prepare-sources.test.mts @@ -0,0 +1,27 @@ +import assert from 'node:assert/strict'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { applyEnginePatches } from './prepare-sources.mts'; + +test('ordered Git patches apply inside an ignored directory of the enclosing worktree', (t) => { + const root = path.resolve(import.meta.dir, '../../../..'); + const scratch = mkdtempSync(path.join(root, 'target/engine-patch-test-')); + t.after(() => rmSync(scratch, { recursive: true, force: true })); + const source = path.join(scratch, 'source'); + mkdirSync(source); + writeFileSync(path.join(source, 'value.txt'), 'original\n'); + const patches = [ + ['original', 'first'], + ['first', 'second'], + ].map(([before, after], index) => { + const file = path.join(scratch, `${index}.patch`); + writeFileSync( + file, + `diff --git a/value.txt b/value.txt\n--- a/value.txt\n+++ b/value.txt\n@@ -1 +1 @@\n-${before}\n+${after}\n`, + ); + return file; + }); + applyEnginePatches(source, patches); + assert.equal(readFileSync(path.join(source, 'value.txt'), 'utf8'), 'second\n'); +}); diff --git a/src/wasix/runtime/engine/source.toml b/src/wasix/runtime/engine/source.toml new file mode 100644 index 000000000..6f8b03ec7 --- /dev/null +++ b/src/wasix/runtime/engine/source.toml @@ -0,0 +1,48 @@ +[wasmer] +version = "7.5.0" +url = "https://static.crates.io/crates/wasmer/wasmer-7.5.0.crate" +sha256 = "ddfde3972691f1082774c71c42e21add1d1838cb6482a6921a0ba8a993e0046a" +bytes = 235496 + +[wasmer-wasix] +version = "0.705.0" +url = "https://static.crates.io/crates/wasmer-wasix/wasmer-wasix-0.705.0.crate" +sha256 = "1d5bbce1be073ab1e40f5ed2468653832c22aecfd73e2ba763c41a5d831a4475" +bytes = 510422 + +[wasmer-wasix-types] +version = "0.705.0" +url = "https://static.crates.io/crates/wasmer-wasix-types/wasmer-wasix-types-0.705.0.crate" +sha256 = "910259427f2e3af0f2238cf4d2d3dbf284ca471cf02965f4635de7f4b2eee66e" +bytes = 47482 + +[wasmer-journal] +version = "0.705.0" +url = "https://static.crates.io/crates/wasmer-journal/wasmer-journal-0.705.0.crate" +sha256 = "d1ba6aab53ebc49f540787f2daf9a8865b0933de8a8d081b22a78b19259d279c" +bytes = 73753 + +[wee8-windows] +version = "11.9.7" +v8-version = "13.6.233.17" +url = "https://github.com/wasmerio/wee8-custom-builds/releases/download/11.9.7/v8-windows-amd64.tar.xz" +sha256 = "2aee8b6c3e8cecae2ce0325ac01b9bcaea4bef49e8f2aac599e1729d60c17285" +bytes = 151695100 +member = "lib/v8.lib" +member-sha256 = "04061d2035659457dcedb2e695ab3ef50de97d3ab71579b4fe824523c47f582a" +member-bytes = 1871746786 + +[v8-header] +url = "https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/third_party/wasm-api/wasm.hh" +sha256 = "18f49f653f79a9485d6bda1d650a9c29f538987b7092457e2bb95e8fa68aae76" +bytes = 18392 + +[v8-license] +url = "https://raw.githubusercontent.com/v8/v8/b0a55a7dad7f536cce1f9aaddba89894c8533946/LICENSE" +sha256 = "6ab33af8774a0f396ee3aeeb761e3229057682d6f9fa7f572e390c2cb3a6e509" +bytes = 3093 + +[wasmer-license] +url = "https://raw.githubusercontent.com/wasmerio/wasmer/82ff099e082da586b38af79986abbb8b677f8baf/LICENSE" +sha256 = "76dc7d305458d07478bc62669fe53dbfd3b94b95c5e00fbb45af1f492cbd7284" +bytes = 1097 diff --git a/src/wasix/runtime/engine/test-packaged.mts b/src/wasix/runtime/engine/test-packaged.mts new file mode 100644 index 000000000..46e6b3823 --- /dev/null +++ b/src/wasix/runtime/engine/test-packaged.mts @@ -0,0 +1,81 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { preparePackagedCargoTestClosure } from '../../../../tools/packaging/cargo-package-test-closure.mts'; +import { packageEngine } from './package.mts'; + +assert.equal(process.platform, 'win32', 'installed engine qualification requires Windows'); +const root = mkdtempSync(path.join(os.tmpdir(), 'oliphaunt-installed-engine-')); +try { + const version = Bun.TOML.parse( + readFileSync(path.join(import.meta.dir, 'crates/wasmer/Cargo.toml'), 'utf8'), + ).package.version; + const packages = packageEngine({ + sourceRoot: path.join(root, 'source'), + outputDir: path.join(root, 'packages'), + version, + }); + const consumer = path.join(root, 'consumer'); + // Root the closure at WASIX so its Wasmer dependency is patched locally. + const wasix = packages.find((row) => row.name === 'oliphaunt-wasmer-wasix'); + const wasixManifest = preparePackagedCargoTestClosure({ + cratePath: wasix.cratePath, + scratch: consumer, + dependencyCrates: packages.filter((row) => row !== wasix).map((row) => row.cratePath), + }); + const wasmerManifest = path.join( + consumer, + 'dependencies', + `oliphaunt-wasmer-${version}`, + 'Cargo.toml', + ); + const env = Object.fromEntries( + Object.entries(process.env).filter(([name]) => !name.startsWith('OLIPHAUNT_')), + ); + env.CARGO_TARGET_DIR = path.join(root, 'target'); + function run(command: string[]) { + const result = Bun.spawnSync(command, { + cwd: consumer, + env, + stdout: 'inherit', + stderr: 'inherit', + timeout: 1_200_000, + }); + assert.equal(result.exitCode, 0, `installed engine: ${command.join(' ')}`); + } + // Only the extracted archives supply private dependencies. No workspace + // producer, source archive, native library, or compiler build is consulted. + run(['cargo', '--config', 'net.offline=false', 'fetch', '--manifest-path', wasmerManifest]); + run([ + 'cargo', + 'nextest', + 'run', + '--manifest-path', + wasmerManifest, + '--locked', + '--offline', + '--no-default-features', + '--features', + 'v8', + '--lib', + '-E', + 'test(oliphaunt_contract)', + '--no-tests=fail', + '--test-threads=1', + ]); + run(['cargo', '--config', 'net.offline=false', 'fetch', '--manifest-path', wasixManifest]); + run([ + 'cargo', + 'check', + '--manifest-path', + wasixManifest, + '--locked', + '--offline', + '--no-default-features', + '--features', + 'sys-minimal,sys-poll,host-vnet,time,v8,wasmer/headless', + ]); +} finally { + rmSync(root, { recursive: true, force: true }); +} diff --git a/src/wasix/runtime/engine/windows/bindings.rs b/src/wasix/runtime/engine/windows/bindings.rs new file mode 100644 index 000000000..a3d07075e --- /dev/null +++ b/src/wasix/runtime/engine/windows/bindings.rs @@ -0,0 +1,2042 @@ +// Generated from Wasmer 7.5.0 third-party/wee8/wasm.h for Windows x64. +// Only the WebAssembly C ABI is retained; regenerate when the pinned header changes. +pub type byte_t = ::std::os::raw::c_char; +pub type float32_t = f32; +pub type float64_t = f64; +pub type wasm_byte_t = byte_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_byte_vec_t { + pub size: usize, + pub data: *mut wasm_byte_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_byte_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_byte_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_byte_vec_t::size"] + [::std::mem::offset_of!(wasm_byte_vec_t, size) - 0usize]; + ["Offset of field: wasm_byte_vec_t::data"] + [::std::mem::offset_of!(wasm_byte_vec_t, data) - 8usize]; +}; +impl Default for wasm_byte_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_byte_vec_new_empty"] + pub fn wasm_byte_vec_new_empty(out: *mut wasm_byte_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_byte_vec_new_uninitialized"] + pub fn wasm_byte_vec_new_uninitialized(out: *mut wasm_byte_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_byte_vec_new"] + pub fn wasm_byte_vec_new(out: *mut wasm_byte_vec_t, arg1: usize, arg2: *const wasm_byte_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_byte_vec_copy"] + pub fn wasm_byte_vec_copy(out: *mut wasm_byte_vec_t, arg1: *const wasm_byte_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_byte_vec_delete"] + pub fn wasm_byte_vec_delete(arg1: *mut wasm_byte_vec_t); +} +pub type wasm_name_t = wasm_byte_vec_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_config_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_config_delete"] + pub fn wasm_config_delete(arg1: *mut wasm_config_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_config_new"] + pub fn wasm_config_new() -> *mut wasm_config_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_engine_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_engine_delete"] + pub fn wasm_engine_delete(arg1: *mut wasm_engine_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_engine_new"] + pub fn wasm_engine_new() -> *mut wasm_engine_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_engine_new_with_config"] + pub fn wasm_engine_new_with_config(arg1: *mut wasm_config_t) -> *mut wasm_engine_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_store_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_store_delete"] + pub fn wasm_store_delete(arg1: *mut wasm_store_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_store_new"] + pub fn wasm_store_new(arg1: *mut wasm_engine_t) -> *mut wasm_store_t; +} +pub type wasm_mutability_t = u8; +pub const wasm_mutability_enum_WASM_CONST: wasm_mutability_enum = 0; +pub const wasm_mutability_enum_WASM_VAR: wasm_mutability_enum = 1; +pub type wasm_mutability_enum = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct wasm_limits_t { + pub min: u32, + pub max: u32, + pub shared: bool, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_limits_t"][::std::mem::size_of::() - 12usize]; + ["Alignment of wasm_limits_t"][::std::mem::align_of::() - 4usize]; + ["Offset of field: wasm_limits_t::min"][::std::mem::offset_of!(wasm_limits_t, min) - 0usize]; + ["Offset of field: wasm_limits_t::max"][::std::mem::offset_of!(wasm_limits_t, max) - 4usize]; + ["Offset of field: wasm_limits_t::shared"] + [::std::mem::offset_of!(wasm_limits_t, shared) - 8usize]; +}; +pub const wasm_limits_max_default: u32 = 4294967295; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_valtype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_delete"] + pub fn wasm_valtype_delete(arg1: *mut wasm_valtype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_valtype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_valtype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_valtype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_valtype_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_valtype_vec_t::size"] + [::std::mem::offset_of!(wasm_valtype_vec_t, size) - 0usize]; + ["Offset of field: wasm_valtype_vec_t::data"] + [::std::mem::offset_of!(wasm_valtype_vec_t, data) - 8usize]; +}; +impl Default for wasm_valtype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_vec_new_empty"] + pub fn wasm_valtype_vec_new_empty(out: *mut wasm_valtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_vec_new_uninitialized"] + pub fn wasm_valtype_vec_new_uninitialized(out: *mut wasm_valtype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_vec_new"] + pub fn wasm_valtype_vec_new( + out: *mut wasm_valtype_vec_t, + arg1: usize, + arg2: *const *mut wasm_valtype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_vec_copy"] + pub fn wasm_valtype_vec_copy(out: *mut wasm_valtype_vec_t, arg1: *const wasm_valtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_vec_delete"] + pub fn wasm_valtype_vec_delete(arg1: *mut wasm_valtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_copy"] + pub fn wasm_valtype_copy(arg1: *const wasm_valtype_t) -> *mut wasm_valtype_t; +} +pub type wasm_valkind_t = u8; +pub const wasm_valkind_enum_WASM_I32: wasm_valkind_enum = 0; +pub const wasm_valkind_enum_WASM_I64: wasm_valkind_enum = 1; +pub const wasm_valkind_enum_WASM_F32: wasm_valkind_enum = 2; +pub const wasm_valkind_enum_WASM_F64: wasm_valkind_enum = 3; +pub const wasm_valkind_enum_WASM_V128: wasm_valkind_enum = 4; +pub const wasm_valkind_enum_WASM_EXTERNREF: wasm_valkind_enum = 128; +pub const wasm_valkind_enum_WASM_FUNCREF: wasm_valkind_enum = 129; +pub type wasm_valkind_enum = ::std::os::raw::c_int; +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_new"] + pub fn wasm_valtype_new(arg1: wasm_valkind_t) -> *mut wasm_valtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_valtype_kind"] + pub fn wasm_valtype_kind(arg1: *const wasm_valtype_t) -> wasm_valkind_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_functype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_delete"] + pub fn wasm_functype_delete(arg1: *mut wasm_functype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_functype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_functype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_functype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_functype_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_functype_vec_t::size"] + [::std::mem::offset_of!(wasm_functype_vec_t, size) - 0usize]; + ["Offset of field: wasm_functype_vec_t::data"] + [::std::mem::offset_of!(wasm_functype_vec_t, data) - 8usize]; +}; +impl Default for wasm_functype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_vec_new_empty"] + pub fn wasm_functype_vec_new_empty(out: *mut wasm_functype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_vec_new_uninitialized"] + pub fn wasm_functype_vec_new_uninitialized(out: *mut wasm_functype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_vec_new"] + pub fn wasm_functype_vec_new( + out: *mut wasm_functype_vec_t, + arg1: usize, + arg2: *const *mut wasm_functype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_vec_copy"] + pub fn wasm_functype_vec_copy(out: *mut wasm_functype_vec_t, arg1: *const wasm_functype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_vec_delete"] + pub fn wasm_functype_vec_delete(arg1: *mut wasm_functype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_copy"] + pub fn wasm_functype_copy(arg1: *const wasm_functype_t) -> *mut wasm_functype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_new"] + pub fn wasm_functype_new( + params: *mut wasm_valtype_vec_t, + results: *mut wasm_valtype_vec_t, + ) -> *mut wasm_functype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_params"] + pub fn wasm_functype_params(arg1: *const wasm_functype_t) -> *const wasm_valtype_vec_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_results"] + pub fn wasm_functype_results(arg1: *const wasm_functype_t) -> *const wasm_valtype_vec_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_globaltype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_delete"] + pub fn wasm_globaltype_delete(arg1: *mut wasm_globaltype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_globaltype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_globaltype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_globaltype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_globaltype_vec_t"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_globaltype_vec_t::size"] + [::std::mem::offset_of!(wasm_globaltype_vec_t, size) - 0usize]; + ["Offset of field: wasm_globaltype_vec_t::data"] + [::std::mem::offset_of!(wasm_globaltype_vec_t, data) - 8usize]; +}; +impl Default for wasm_globaltype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_vec_new_empty"] + pub fn wasm_globaltype_vec_new_empty(out: *mut wasm_globaltype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_vec_new_uninitialized"] + pub fn wasm_globaltype_vec_new_uninitialized(out: *mut wasm_globaltype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_vec_new"] + pub fn wasm_globaltype_vec_new( + out: *mut wasm_globaltype_vec_t, + arg1: usize, + arg2: *const *mut wasm_globaltype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_vec_copy"] + pub fn wasm_globaltype_vec_copy( + out: *mut wasm_globaltype_vec_t, + arg1: *const wasm_globaltype_vec_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_vec_delete"] + pub fn wasm_globaltype_vec_delete(arg1: *mut wasm_globaltype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_copy"] + pub fn wasm_globaltype_copy(arg1: *const wasm_globaltype_t) -> *mut wasm_globaltype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_new"] + pub fn wasm_globaltype_new( + arg1: *mut wasm_valtype_t, + arg2: wasm_mutability_t, + ) -> *mut wasm_globaltype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_content"] + pub fn wasm_globaltype_content(arg1: *const wasm_globaltype_t) -> *const wasm_valtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_mutability"] + pub fn wasm_globaltype_mutability(arg1: *const wasm_globaltype_t) -> wasm_mutability_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_tagtype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_delete"] + pub fn wasm_tagtype_delete(arg1: *mut wasm_tagtype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_tagtype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_tagtype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_tagtype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_tagtype_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_tagtype_vec_t::size"] + [::std::mem::offset_of!(wasm_tagtype_vec_t, size) - 0usize]; + ["Offset of field: wasm_tagtype_vec_t::data"] + [::std::mem::offset_of!(wasm_tagtype_vec_t, data) - 8usize]; +}; +impl Default for wasm_tagtype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_vec_new_empty"] + pub fn wasm_tagtype_vec_new_empty(out: *mut wasm_tagtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_vec_new_uninitialized"] + pub fn wasm_tagtype_vec_new_uninitialized(out: *mut wasm_tagtype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_vec_new"] + pub fn wasm_tagtype_vec_new( + out: *mut wasm_tagtype_vec_t, + arg1: usize, + arg2: *const *mut wasm_tagtype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_vec_copy"] + pub fn wasm_tagtype_vec_copy(out: *mut wasm_tagtype_vec_t, arg1: *const wasm_tagtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_vec_delete"] + pub fn wasm_tagtype_vec_delete(arg1: *mut wasm_tagtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_copy"] + pub fn wasm_tagtype_copy(arg1: *const wasm_tagtype_t) -> *mut wasm_tagtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_new"] + pub fn wasm_tagtype_new(params: *mut wasm_valtype_vec_t) -> *mut wasm_tagtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_params"] + pub fn wasm_tagtype_params(arg1: *const wasm_tagtype_t) -> *const wasm_valtype_vec_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_tabletype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_delete"] + pub fn wasm_tabletype_delete(arg1: *mut wasm_tabletype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_tabletype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_tabletype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_tabletype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_tabletype_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_tabletype_vec_t::size"] + [::std::mem::offset_of!(wasm_tabletype_vec_t, size) - 0usize]; + ["Offset of field: wasm_tabletype_vec_t::data"] + [::std::mem::offset_of!(wasm_tabletype_vec_t, data) - 8usize]; +}; +impl Default for wasm_tabletype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_vec_new_empty"] + pub fn wasm_tabletype_vec_new_empty(out: *mut wasm_tabletype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_vec_new_uninitialized"] + pub fn wasm_tabletype_vec_new_uninitialized(out: *mut wasm_tabletype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_vec_new"] + pub fn wasm_tabletype_vec_new( + out: *mut wasm_tabletype_vec_t, + arg1: usize, + arg2: *const *mut wasm_tabletype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_vec_copy"] + pub fn wasm_tabletype_vec_copy( + out: *mut wasm_tabletype_vec_t, + arg1: *const wasm_tabletype_vec_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_vec_delete"] + pub fn wasm_tabletype_vec_delete(arg1: *mut wasm_tabletype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_copy"] + pub fn wasm_tabletype_copy(arg1: *const wasm_tabletype_t) -> *mut wasm_tabletype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_new"] + pub fn wasm_tabletype_new( + arg1: *mut wasm_valtype_t, + arg2: *const wasm_limits_t, + ) -> *mut wasm_tabletype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_element"] + pub fn wasm_tabletype_element(arg1: *const wasm_tabletype_t) -> *const wasm_valtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_limits"] + pub fn wasm_tabletype_limits(arg1: *const wasm_tabletype_t) -> *const wasm_limits_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_memorytype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_delete"] + pub fn wasm_memorytype_delete(arg1: *mut wasm_memorytype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_memorytype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_memorytype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_memorytype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_memorytype_vec_t"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_memorytype_vec_t::size"] + [::std::mem::offset_of!(wasm_memorytype_vec_t, size) - 0usize]; + ["Offset of field: wasm_memorytype_vec_t::data"] + [::std::mem::offset_of!(wasm_memorytype_vec_t, data) - 8usize]; +}; +impl Default for wasm_memorytype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_vec_new_empty"] + pub fn wasm_memorytype_vec_new_empty(out: *mut wasm_memorytype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_vec_new_uninitialized"] + pub fn wasm_memorytype_vec_new_uninitialized(out: *mut wasm_memorytype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_vec_new"] + pub fn wasm_memorytype_vec_new( + out: *mut wasm_memorytype_vec_t, + arg1: usize, + arg2: *const *mut wasm_memorytype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_vec_copy"] + pub fn wasm_memorytype_vec_copy( + out: *mut wasm_memorytype_vec_t, + arg1: *const wasm_memorytype_vec_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_vec_delete"] + pub fn wasm_memorytype_vec_delete(arg1: *mut wasm_memorytype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_copy"] + pub fn wasm_memorytype_copy(arg1: *const wasm_memorytype_t) -> *mut wasm_memorytype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_new"] + pub fn wasm_memorytype_new(arg1: *const wasm_limits_t) -> *mut wasm_memorytype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_limits"] + pub fn wasm_memorytype_limits(arg1: *const wasm_memorytype_t) -> *const wasm_limits_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_externtype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_delete"] + pub fn wasm_externtype_delete(arg1: *mut wasm_externtype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_externtype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_externtype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_externtype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_externtype_vec_t"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_externtype_vec_t::size"] + [::std::mem::offset_of!(wasm_externtype_vec_t, size) - 0usize]; + ["Offset of field: wasm_externtype_vec_t::data"] + [::std::mem::offset_of!(wasm_externtype_vec_t, data) - 8usize]; +}; +impl Default for wasm_externtype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_vec_new_empty"] + pub fn wasm_externtype_vec_new_empty(out: *mut wasm_externtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_vec_new_uninitialized"] + pub fn wasm_externtype_vec_new_uninitialized(out: *mut wasm_externtype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_vec_new"] + pub fn wasm_externtype_vec_new( + out: *mut wasm_externtype_vec_t, + arg1: usize, + arg2: *const *mut wasm_externtype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_vec_copy"] + pub fn wasm_externtype_vec_copy( + out: *mut wasm_externtype_vec_t, + arg1: *const wasm_externtype_vec_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_vec_delete"] + pub fn wasm_externtype_vec_delete(arg1: *mut wasm_externtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_copy"] + pub fn wasm_externtype_copy(arg1: *const wasm_externtype_t) -> *mut wasm_externtype_t; +} +pub type wasm_externkind_t = u8; +pub const wasm_externkind_enum_WASM_EXTERN_FUNC: wasm_externkind_enum = 0; +pub const wasm_externkind_enum_WASM_EXTERN_GLOBAL: wasm_externkind_enum = 1; +pub const wasm_externkind_enum_WASM_EXTERN_TABLE: wasm_externkind_enum = 2; +pub const wasm_externkind_enum_WASM_EXTERN_MEMORY: wasm_externkind_enum = 3; +pub const wasm_externkind_enum_WASM_EXTERN_TAG: wasm_externkind_enum = 4; +pub type wasm_externkind_enum = ::std::os::raw::c_int; +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_kind"] + pub fn wasm_externtype_kind(arg1: *const wasm_externtype_t) -> wasm_externkind_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_as_externtype"] + pub fn wasm_functype_as_externtype(arg1: *mut wasm_functype_t) -> *mut wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_as_externtype"] + pub fn wasm_globaltype_as_externtype(arg1: *mut wasm_globaltype_t) -> *mut wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_as_externtype"] + pub fn wasm_tagtype_as_externtype(arg1: *mut wasm_tagtype_t) -> *mut wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_as_externtype"] + pub fn wasm_tabletype_as_externtype(arg1: *mut wasm_tabletype_t) -> *mut wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_as_externtype"] + pub fn wasm_memorytype_as_externtype(arg1: *mut wasm_memorytype_t) -> *mut wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_functype"] + pub fn wasm_externtype_as_functype(arg1: *mut wasm_externtype_t) -> *mut wasm_functype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_globaltype"] + pub fn wasm_externtype_as_globaltype(arg1: *mut wasm_externtype_t) -> *mut wasm_globaltype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_tagtype"] + pub fn wasm_externtype_as_tagtype(arg1: *mut wasm_externtype_t) -> *mut wasm_tagtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_tabletype"] + pub fn wasm_externtype_as_tabletype(arg1: *mut wasm_externtype_t) -> *mut wasm_tabletype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_memorytype"] + pub fn wasm_externtype_as_memorytype(arg1: *mut wasm_externtype_t) -> *mut wasm_memorytype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_functype_as_externtype_const"] + pub fn wasm_functype_as_externtype_const( + arg1: *const wasm_functype_t, + ) -> *const wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_globaltype_as_externtype_const"] + pub fn wasm_globaltype_as_externtype_const( + arg1: *const wasm_globaltype_t, + ) -> *const wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tagtype_as_externtype_const"] + pub fn wasm_tagtype_as_externtype_const( + arg1: *const wasm_tagtype_t, + ) -> *const wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tabletype_as_externtype_const"] + pub fn wasm_tabletype_as_externtype_const( + arg1: *const wasm_tabletype_t, + ) -> *const wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memorytype_as_externtype_const"] + pub fn wasm_memorytype_as_externtype_const( + arg1: *const wasm_memorytype_t, + ) -> *const wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_functype_const"] + pub fn wasm_externtype_as_functype_const( + arg1: *const wasm_externtype_t, + ) -> *const wasm_functype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_globaltype_const"] + pub fn wasm_externtype_as_globaltype_const( + arg1: *const wasm_externtype_t, + ) -> *const wasm_globaltype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_tagtype_const"] + pub fn wasm_externtype_as_tagtype_const( + arg1: *const wasm_externtype_t, + ) -> *const wasm_tagtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_tabletype_const"] + pub fn wasm_externtype_as_tabletype_const( + arg1: *const wasm_externtype_t, + ) -> *const wasm_tabletype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_externtype_as_memorytype_const"] + pub fn wasm_externtype_as_memorytype_const( + arg1: *const wasm_externtype_t, + ) -> *const wasm_memorytype_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_importtype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_delete"] + pub fn wasm_importtype_delete(arg1: *mut wasm_importtype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_importtype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_importtype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_importtype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_importtype_vec_t"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_importtype_vec_t::size"] + [::std::mem::offset_of!(wasm_importtype_vec_t, size) - 0usize]; + ["Offset of field: wasm_importtype_vec_t::data"] + [::std::mem::offset_of!(wasm_importtype_vec_t, data) - 8usize]; +}; +impl Default for wasm_importtype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_vec_new_empty"] + pub fn wasm_importtype_vec_new_empty(out: *mut wasm_importtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_vec_new_uninitialized"] + pub fn wasm_importtype_vec_new_uninitialized(out: *mut wasm_importtype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_vec_new"] + pub fn wasm_importtype_vec_new( + out: *mut wasm_importtype_vec_t, + arg1: usize, + arg2: *const *mut wasm_importtype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_vec_copy"] + pub fn wasm_importtype_vec_copy( + out: *mut wasm_importtype_vec_t, + arg1: *const wasm_importtype_vec_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_vec_delete"] + pub fn wasm_importtype_vec_delete(arg1: *mut wasm_importtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_copy"] + pub fn wasm_importtype_copy(arg1: *const wasm_importtype_t) -> *mut wasm_importtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_new"] + pub fn wasm_importtype_new( + module: *mut wasm_name_t, + name: *mut wasm_name_t, + arg1: *mut wasm_externtype_t, + ) -> *mut wasm_importtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_module"] + pub fn wasm_importtype_module(arg1: *const wasm_importtype_t) -> *const wasm_name_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_name"] + pub fn wasm_importtype_name(arg1: *const wasm_importtype_t) -> *const wasm_name_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_importtype_type"] + pub fn wasm_importtype_type(arg1: *const wasm_importtype_t) -> *const wasm_externtype_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_exporttype_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_delete"] + pub fn wasm_exporttype_delete(arg1: *mut wasm_exporttype_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_exporttype_vec_t { + pub size: usize, + pub data: *mut *mut wasm_exporttype_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_exporttype_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_exporttype_vec_t"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_exporttype_vec_t::size"] + [::std::mem::offset_of!(wasm_exporttype_vec_t, size) - 0usize]; + ["Offset of field: wasm_exporttype_vec_t::data"] + [::std::mem::offset_of!(wasm_exporttype_vec_t, data) - 8usize]; +}; +impl Default for wasm_exporttype_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_vec_new_empty"] + pub fn wasm_exporttype_vec_new_empty(out: *mut wasm_exporttype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_vec_new_uninitialized"] + pub fn wasm_exporttype_vec_new_uninitialized(out: *mut wasm_exporttype_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_vec_new"] + pub fn wasm_exporttype_vec_new( + out: *mut wasm_exporttype_vec_t, + arg1: usize, + arg2: *const *mut wasm_exporttype_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_vec_copy"] + pub fn wasm_exporttype_vec_copy( + out: *mut wasm_exporttype_vec_t, + arg1: *const wasm_exporttype_vec_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_vec_delete"] + pub fn wasm_exporttype_vec_delete(arg1: *mut wasm_exporttype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_copy"] + pub fn wasm_exporttype_copy(arg1: *const wasm_exporttype_t) -> *mut wasm_exporttype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_new"] + pub fn wasm_exporttype_new( + arg1: *mut wasm_name_t, + arg2: *mut wasm_externtype_t, + ) -> *mut wasm_exporttype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_name"] + pub fn wasm_exporttype_name(arg1: *const wasm_exporttype_t) -> *const wasm_name_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_exporttype_type"] + pub fn wasm_exporttype_type(arg1: *const wasm_exporttype_t) -> *const wasm_externtype_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_ref_t { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct wasm_v128_t { + pub bytes: [u8; 16usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_v128_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_v128_t"][::std::mem::align_of::() - 1usize]; + ["Offset of field: wasm_v128_t::bytes"][::std::mem::offset_of!(wasm_v128_t, bytes) - 0usize]; +}; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct wasm_val_t { + pub kind: wasm_valkind_t, + pub of: wasm_val_t__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union wasm_val_t__bindgen_ty_1 { + pub i32_: i32, + pub i64_: i64, + pub f32_: float32_t, + pub f64_: float64_t, + pub v128: wasm_v128_t, + pub ref_: *mut wasm_ref_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_val_t__bindgen_ty_1"] + [::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_val_t__bindgen_ty_1"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_val_t__bindgen_ty_1::i32_"] + [::std::mem::offset_of!(wasm_val_t__bindgen_ty_1, i32_) - 0usize]; + ["Offset of field: wasm_val_t__bindgen_ty_1::i64_"] + [::std::mem::offset_of!(wasm_val_t__bindgen_ty_1, i64_) - 0usize]; + ["Offset of field: wasm_val_t__bindgen_ty_1::f32_"] + [::std::mem::offset_of!(wasm_val_t__bindgen_ty_1, f32_) - 0usize]; + ["Offset of field: wasm_val_t__bindgen_ty_1::f64_"] + [::std::mem::offset_of!(wasm_val_t__bindgen_ty_1, f64_) - 0usize]; + ["Offset of field: wasm_val_t__bindgen_ty_1::v128"] + [::std::mem::offset_of!(wasm_val_t__bindgen_ty_1, v128) - 0usize]; + ["Offset of field: wasm_val_t__bindgen_ty_1::ref_"] + [::std::mem::offset_of!(wasm_val_t__bindgen_ty_1, ref_) - 0usize]; +}; +impl Default for wasm_val_t__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_val_t"][::std::mem::size_of::() - 24usize]; + ["Alignment of wasm_val_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_val_t::kind"][::std::mem::offset_of!(wasm_val_t, kind) - 0usize]; + ["Offset of field: wasm_val_t::of"][::std::mem::offset_of!(wasm_val_t, of) - 8usize]; +}; +impl Default for wasm_val_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_val_delete"] + pub fn wasm_val_delete(v: *mut wasm_val_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_val_copy"] + pub fn wasm_val_copy(out: *mut wasm_val_t, arg1: *const wasm_val_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_val_vec_t { + pub size: usize, + pub data: *mut wasm_val_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_val_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_val_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_val_vec_t::size"] + [::std::mem::offset_of!(wasm_val_vec_t, size) - 0usize]; + ["Offset of field: wasm_val_vec_t::data"] + [::std::mem::offset_of!(wasm_val_vec_t, data) - 8usize]; +}; +impl Default for wasm_val_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_val_vec_new_empty"] + pub fn wasm_val_vec_new_empty(out: *mut wasm_val_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_val_vec_new_uninitialized"] + pub fn wasm_val_vec_new_uninitialized(out: *mut wasm_val_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_val_vec_new"] + pub fn wasm_val_vec_new(out: *mut wasm_val_vec_t, arg1: usize, arg2: *const wasm_val_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_val_vec_copy"] + pub fn wasm_val_vec_copy(out: *mut wasm_val_vec_t, arg1: *const wasm_val_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_val_vec_delete"] + pub fn wasm_val_vec_delete(arg1: *mut wasm_val_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_delete"] + pub fn wasm_ref_delete(arg1: *mut wasm_ref_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_copy"] + pub fn wasm_ref_copy(arg1: *const wasm_ref_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_same"] + pub fn wasm_ref_same(arg1: *const wasm_ref_t, arg2: *const wasm_ref_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_get_host_info"] + pub fn wasm_ref_get_host_info(arg1: *const wasm_ref_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_set_host_info"] + pub fn wasm_ref_set_host_info(arg1: *mut wasm_ref_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_set_host_info_with_finalizer"] + pub fn wasm_ref_set_host_info_with_finalizer( + arg1: *mut wasm_ref_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_frame_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_delete"] + pub fn wasm_frame_delete(arg1: *mut wasm_frame_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_frame_vec_t { + pub size: usize, + pub data: *mut *mut wasm_frame_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_frame_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_frame_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_frame_vec_t::size"] + [::std::mem::offset_of!(wasm_frame_vec_t, size) - 0usize]; + ["Offset of field: wasm_frame_vec_t::data"] + [::std::mem::offset_of!(wasm_frame_vec_t, data) - 8usize]; +}; +impl Default for wasm_frame_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_vec_new_empty"] + pub fn wasm_frame_vec_new_empty(out: *mut wasm_frame_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_vec_new_uninitialized"] + pub fn wasm_frame_vec_new_uninitialized(out: *mut wasm_frame_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_vec_new"] + pub fn wasm_frame_vec_new( + out: *mut wasm_frame_vec_t, + arg1: usize, + arg2: *const *mut wasm_frame_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_vec_copy"] + pub fn wasm_frame_vec_copy(out: *mut wasm_frame_vec_t, arg1: *const wasm_frame_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_vec_delete"] + pub fn wasm_frame_vec_delete(arg1: *mut wasm_frame_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_copy"] + pub fn wasm_frame_copy(arg1: *const wasm_frame_t) -> *mut wasm_frame_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_instance_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_instance"] + pub fn wasm_frame_instance(arg1: *const wasm_frame_t) -> *mut wasm_instance_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_func_index"] + pub fn wasm_frame_func_index(arg1: *const wasm_frame_t) -> u32; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_func_offset"] + pub fn wasm_frame_func_offset(arg1: *const wasm_frame_t) -> usize; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_frame_module_offset"] + pub fn wasm_frame_module_offset(arg1: *const wasm_frame_t) -> usize; +} +pub type wasm_message_t = wasm_name_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_trap_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_delete"] + pub fn wasm_trap_delete(arg1: *mut wasm_trap_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_copy"] + pub fn wasm_trap_copy(arg1: *const wasm_trap_t) -> *mut wasm_trap_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_same"] + pub fn wasm_trap_same(arg1: *const wasm_trap_t, arg2: *const wasm_trap_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_get_host_info"] + pub fn wasm_trap_get_host_info(arg1: *const wasm_trap_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_set_host_info"] + pub fn wasm_trap_set_host_info(arg1: *mut wasm_trap_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_set_host_info_with_finalizer"] + pub fn wasm_trap_set_host_info_with_finalizer( + arg1: *mut wasm_trap_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_as_ref"] + pub fn wasm_trap_as_ref(arg1: *mut wasm_trap_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_trap"] + pub fn wasm_ref_as_trap(arg1: *mut wasm_ref_t) -> *mut wasm_trap_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_as_ref_const"] + pub fn wasm_trap_as_ref_const(arg1: *const wasm_trap_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_trap_const"] + pub fn wasm_ref_as_trap_const(arg1: *const wasm_ref_t) -> *const wasm_trap_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_new"] + pub fn wasm_trap_new(store: *mut wasm_store_t, arg1: *const wasm_message_t) + -> *mut wasm_trap_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_message"] + pub fn wasm_trap_message(arg1: *const wasm_trap_t, out: *mut wasm_message_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_origin"] + pub fn wasm_trap_origin(arg1: *const wasm_trap_t) -> *mut wasm_frame_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_trap_trace"] + pub fn wasm_trap_trace(arg1: *const wasm_trap_t, out: *mut wasm_frame_vec_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_foreign_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_delete"] + pub fn wasm_foreign_delete(arg1: *mut wasm_foreign_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_copy"] + pub fn wasm_foreign_copy(arg1: *const wasm_foreign_t) -> *mut wasm_foreign_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_same"] + pub fn wasm_foreign_same(arg1: *const wasm_foreign_t, arg2: *const wasm_foreign_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_get_host_info"] + pub fn wasm_foreign_get_host_info(arg1: *const wasm_foreign_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_set_host_info"] + pub fn wasm_foreign_set_host_info(arg1: *mut wasm_foreign_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_set_host_info_with_finalizer"] + pub fn wasm_foreign_set_host_info_with_finalizer( + arg1: *mut wasm_foreign_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_as_ref"] + pub fn wasm_foreign_as_ref(arg1: *mut wasm_foreign_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_foreign"] + pub fn wasm_ref_as_foreign(arg1: *mut wasm_ref_t) -> *mut wasm_foreign_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_as_ref_const"] + pub fn wasm_foreign_as_ref_const(arg1: *const wasm_foreign_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_foreign_const"] + pub fn wasm_ref_as_foreign_const(arg1: *const wasm_ref_t) -> *const wasm_foreign_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_foreign_new"] + pub fn wasm_foreign_new(arg1: *mut wasm_store_t) -> *mut wasm_foreign_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_module_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_delete"] + pub fn wasm_module_delete(arg1: *mut wasm_module_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_copy"] + pub fn wasm_module_copy(arg1: *const wasm_module_t) -> *mut wasm_module_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_same"] + pub fn wasm_module_same(arg1: *const wasm_module_t, arg2: *const wasm_module_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_get_host_info"] + pub fn wasm_module_get_host_info(arg1: *const wasm_module_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_set_host_info"] + pub fn wasm_module_set_host_info(arg1: *mut wasm_module_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_set_host_info_with_finalizer"] + pub fn wasm_module_set_host_info_with_finalizer( + arg1: *mut wasm_module_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_as_ref"] + pub fn wasm_module_as_ref(arg1: *mut wasm_module_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_module"] + pub fn wasm_ref_as_module(arg1: *mut wasm_ref_t) -> *mut wasm_module_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_as_ref_const"] + pub fn wasm_module_as_ref_const(arg1: *const wasm_module_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_module_const"] + pub fn wasm_ref_as_module_const(arg1: *const wasm_ref_t) -> *const wasm_module_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_shared_module_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_shared_module_delete"] + pub fn wasm_shared_module_delete(arg1: *mut wasm_shared_module_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_share"] + pub fn wasm_module_share(arg1: *const wasm_module_t) -> *mut wasm_shared_module_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_obtain"] + pub fn wasm_module_obtain( + arg1: *mut wasm_store_t, + arg2: *const wasm_shared_module_t, + ) -> *mut wasm_module_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_new"] + pub fn wasm_module_new( + arg1: *mut wasm_store_t, + binary: *const wasm_byte_vec_t, + ) -> *mut wasm_module_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_validate"] + pub fn wasm_module_validate(arg1: *mut wasm_store_t, binary: *const wasm_byte_vec_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_imports"] + pub fn wasm_module_imports(arg1: *const wasm_module_t, out: *mut wasm_importtype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_exports"] + pub fn wasm_module_exports(arg1: *const wasm_module_t, out: *mut wasm_exporttype_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_serialize"] + pub fn wasm_module_serialize(arg1: *const wasm_module_t, out: *mut wasm_byte_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_module_deserialize"] + pub fn wasm_module_deserialize( + arg1: *mut wasm_store_t, + arg2: *const wasm_byte_vec_t, + ) -> *mut wasm_module_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_func_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_delete"] + pub fn wasm_func_delete(arg1: *mut wasm_func_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_copy"] + pub fn wasm_func_copy(arg1: *const wasm_func_t) -> *mut wasm_func_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_same"] + pub fn wasm_func_same(arg1: *const wasm_func_t, arg2: *const wasm_func_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_get_host_info"] + pub fn wasm_func_get_host_info(arg1: *const wasm_func_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_set_host_info"] + pub fn wasm_func_set_host_info(arg1: *mut wasm_func_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_set_host_info_with_finalizer"] + pub fn wasm_func_set_host_info_with_finalizer( + arg1: *mut wasm_func_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_as_ref"] + pub fn wasm_func_as_ref(arg1: *mut wasm_func_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_func"] + pub fn wasm_ref_as_func(arg1: *mut wasm_ref_t) -> *mut wasm_func_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_as_ref_const"] + pub fn wasm_func_as_ref_const(arg1: *const wasm_func_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_func_const"] + pub fn wasm_ref_as_func_const(arg1: *const wasm_ref_t) -> *const wasm_func_t; +} +pub type wasm_func_callback_t = ::std::option::Option< + unsafe extern "C" fn( + args: *const wasm_val_vec_t, + results: *mut wasm_val_vec_t, + ) -> *mut wasm_trap_t, +>; +pub type wasm_func_callback_with_env_t = ::std::option::Option< + unsafe extern "C" fn( + env: *mut ::std::os::raw::c_void, + args: *const wasm_val_vec_t, + results: *mut wasm_val_vec_t, + ) -> *mut wasm_trap_t, +>; +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_new"] + pub fn wasm_func_new( + arg1: *mut wasm_store_t, + arg2: *const wasm_functype_t, + arg3: wasm_func_callback_t, + ) -> *mut wasm_func_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_new_with_env"] + pub fn wasm_func_new_with_env( + arg1: *mut wasm_store_t, + type_: *const wasm_functype_t, + arg2: wasm_func_callback_with_env_t, + env: *mut ::std::os::raw::c_void, + finalizer: ::std::option::Option, + ) -> *mut wasm_func_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_type"] + pub fn wasm_func_type(arg1: *const wasm_func_t) -> *mut wasm_functype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_param_arity"] + pub fn wasm_func_param_arity(arg1: *const wasm_func_t) -> usize; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_result_arity"] + pub fn wasm_func_result_arity(arg1: *const wasm_func_t) -> usize; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_call"] + pub fn wasm_func_call( + arg1: *const wasm_func_t, + args: *const wasm_val_vec_t, + results: *mut wasm_val_vec_t, + ) -> *mut wasm_trap_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_global_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_delete"] + pub fn wasm_global_delete(arg1: *mut wasm_global_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_copy"] + pub fn wasm_global_copy(arg1: *const wasm_global_t) -> *mut wasm_global_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_same"] + pub fn wasm_global_same(arg1: *const wasm_global_t, arg2: *const wasm_global_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_get_host_info"] + pub fn wasm_global_get_host_info(arg1: *const wasm_global_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_set_host_info"] + pub fn wasm_global_set_host_info(arg1: *mut wasm_global_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_set_host_info_with_finalizer"] + pub fn wasm_global_set_host_info_with_finalizer( + arg1: *mut wasm_global_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_as_ref"] + pub fn wasm_global_as_ref(arg1: *mut wasm_global_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_global"] + pub fn wasm_ref_as_global(arg1: *mut wasm_ref_t) -> *mut wasm_global_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_as_ref_const"] + pub fn wasm_global_as_ref_const(arg1: *const wasm_global_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_global_const"] + pub fn wasm_ref_as_global_const(arg1: *const wasm_ref_t) -> *const wasm_global_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_new"] + pub fn wasm_global_new( + arg1: *mut wasm_store_t, + arg2: *const wasm_globaltype_t, + arg3: *const wasm_val_t, + ) -> *mut wasm_global_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_type"] + pub fn wasm_global_type(arg1: *const wasm_global_t) -> *mut wasm_globaltype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_get"] + pub fn wasm_global_get(arg1: *const wasm_global_t, out: *mut wasm_val_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_set"] + pub fn wasm_global_set(arg1: *mut wasm_global_t, arg2: *const wasm_val_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_tag_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_delete"] + pub fn wasm_tag_delete(arg1: *mut wasm_tag_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_copy"] + pub fn wasm_tag_copy(arg1: *const wasm_tag_t) -> *mut wasm_tag_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_same"] + pub fn wasm_tag_same(arg1: *const wasm_tag_t, arg2: *const wasm_tag_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_get_host_info"] + pub fn wasm_tag_get_host_info(arg1: *const wasm_tag_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_set_host_info"] + pub fn wasm_tag_set_host_info(arg1: *mut wasm_tag_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_set_host_info_with_finalizer"] + pub fn wasm_tag_set_host_info_with_finalizer( + arg1: *mut wasm_tag_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_as_ref"] + pub fn wasm_tag_as_ref(arg1: *mut wasm_tag_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_tag"] + pub fn wasm_ref_as_tag(arg1: *mut wasm_ref_t) -> *mut wasm_tag_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_as_ref_const"] + pub fn wasm_tag_as_ref_const(arg1: *const wasm_tag_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_tag_const"] + pub fn wasm_ref_as_tag_const(arg1: *const wasm_ref_t) -> *const wasm_tag_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_new"] + pub fn wasm_tag_new(arg1: *mut wasm_store_t, arg2: *const wasm_tagtype_t) -> *mut wasm_tag_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_type"] + pub fn wasm_tag_type(arg1: *const wasm_tag_t) -> *mut wasm_tagtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_get"] + pub fn wasm_tag_get(arg1: *const wasm_tag_t, out: *mut wasm_val_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_set"] + pub fn wasm_tag_set(arg1: *mut wasm_tag_t, arg2: *const wasm_val_t); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_table_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_delete"] + pub fn wasm_table_delete(arg1: *mut wasm_table_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_copy"] + pub fn wasm_table_copy(arg1: *const wasm_table_t) -> *mut wasm_table_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_same"] + pub fn wasm_table_same(arg1: *const wasm_table_t, arg2: *const wasm_table_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_get_host_info"] + pub fn wasm_table_get_host_info(arg1: *const wasm_table_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_set_host_info"] + pub fn wasm_table_set_host_info(arg1: *mut wasm_table_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_set_host_info_with_finalizer"] + pub fn wasm_table_set_host_info_with_finalizer( + arg1: *mut wasm_table_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_as_ref"] + pub fn wasm_table_as_ref(arg1: *mut wasm_table_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_table"] + pub fn wasm_ref_as_table(arg1: *mut wasm_ref_t) -> *mut wasm_table_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_as_ref_const"] + pub fn wasm_table_as_ref_const(arg1: *const wasm_table_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_table_const"] + pub fn wasm_ref_as_table_const(arg1: *const wasm_ref_t) -> *const wasm_table_t; +} +pub type wasm_table_size_t = u32; +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_new"] + pub fn wasm_table_new( + arg1: *mut wasm_store_t, + arg2: *const wasm_tabletype_t, + init: *mut wasm_ref_t, + ) -> *mut wasm_table_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_type"] + pub fn wasm_table_type(arg1: *const wasm_table_t) -> *mut wasm_tabletype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_get"] + pub fn wasm_table_get(arg1: *const wasm_table_t, index: wasm_table_size_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_set"] + pub fn wasm_table_set( + arg1: *mut wasm_table_t, + index: wasm_table_size_t, + arg2: *mut wasm_ref_t, + ) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_size"] + pub fn wasm_table_size(arg1: *const wasm_table_t) -> wasm_table_size_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_grow"] + pub fn wasm_table_grow( + arg1: *mut wasm_table_t, + delta: wasm_table_size_t, + init: *mut wasm_ref_t, + ) -> bool; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_memory_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_delete"] + pub fn wasm_memory_delete(arg1: *mut wasm_memory_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_copy"] + pub fn wasm_memory_copy(arg1: *const wasm_memory_t) -> *mut wasm_memory_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_same"] + pub fn wasm_memory_same(arg1: *const wasm_memory_t, arg2: *const wasm_memory_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_get_host_info"] + pub fn wasm_memory_get_host_info(arg1: *const wasm_memory_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_set_host_info"] + pub fn wasm_memory_set_host_info(arg1: *mut wasm_memory_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_set_host_info_with_finalizer"] + pub fn wasm_memory_set_host_info_with_finalizer( + arg1: *mut wasm_memory_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_as_ref"] + pub fn wasm_memory_as_ref(arg1: *mut wasm_memory_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_memory"] + pub fn wasm_ref_as_memory(arg1: *mut wasm_ref_t) -> *mut wasm_memory_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_as_ref_const"] + pub fn wasm_memory_as_ref_const(arg1: *const wasm_memory_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_memory_const"] + pub fn wasm_ref_as_memory_const(arg1: *const wasm_ref_t) -> *const wasm_memory_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_shared_memory_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_shared_memory_delete"] + pub fn wasm_shared_memory_delete(arg1: *mut wasm_shared_memory_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_share"] + pub fn wasm_memory_share(arg1: *const wasm_memory_t) -> *mut wasm_shared_memory_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_obtain"] + pub fn wasm_memory_obtain( + arg1: *mut wasm_store_t, + arg2: *const wasm_shared_memory_t, + ) -> *mut wasm_memory_t; +} +pub type wasm_memory_pages_t = u32; +pub const MEMORY_PAGE_SIZE: usize = 65536; +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_new"] + pub fn wasm_memory_new( + arg1: *mut wasm_store_t, + arg2: *const wasm_memorytype_t, + ) -> *mut wasm_memory_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_type"] + pub fn wasm_memory_type(arg1: *const wasm_memory_t) -> *mut wasm_memorytype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_data"] + pub fn wasm_memory_data(arg1: *mut wasm_memory_t) -> *mut byte_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_data_size"] + pub fn wasm_memory_data_size(arg1: *const wasm_memory_t) -> usize; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_size"] + pub fn wasm_memory_size(arg1: *const wasm_memory_t) -> wasm_memory_pages_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_grow"] + pub fn wasm_memory_grow(arg1: *mut wasm_memory_t, delta: wasm_memory_pages_t) -> bool; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_extern_t { + _unused: [u8; 0], +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_delete"] + pub fn wasm_extern_delete(arg1: *mut wasm_extern_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_copy"] + pub fn wasm_extern_copy(arg1: *const wasm_extern_t) -> *mut wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_same"] + pub fn wasm_extern_same(arg1: *const wasm_extern_t, arg2: *const wasm_extern_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_get_host_info"] + pub fn wasm_extern_get_host_info(arg1: *const wasm_extern_t) -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_set_host_info"] + pub fn wasm_extern_set_host_info(arg1: *mut wasm_extern_t, arg2: *mut ::std::os::raw::c_void); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_set_host_info_with_finalizer"] + pub fn wasm_extern_set_host_info_with_finalizer( + arg1: *mut wasm_extern_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_ref"] + pub fn wasm_extern_as_ref(arg1: *mut wasm_extern_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_extern"] + pub fn wasm_ref_as_extern(arg1: *mut wasm_ref_t) -> *mut wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_ref_const"] + pub fn wasm_extern_as_ref_const(arg1: *const wasm_extern_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_extern_const"] + pub fn wasm_ref_as_extern_const(arg1: *const wasm_ref_t) -> *const wasm_extern_t; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct wasm_extern_vec_t { + pub size: usize, + pub data: *mut *mut wasm_extern_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of wasm_extern_vec_t"][::std::mem::size_of::() - 16usize]; + ["Alignment of wasm_extern_vec_t"][::std::mem::align_of::() - 8usize]; + ["Offset of field: wasm_extern_vec_t::size"] + [::std::mem::offset_of!(wasm_extern_vec_t, size) - 0usize]; + ["Offset of field: wasm_extern_vec_t::data"] + [::std::mem::offset_of!(wasm_extern_vec_t, data) - 8usize]; +}; +impl Default for wasm_extern_vec_t { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_vec_new_empty"] + pub fn wasm_extern_vec_new_empty(out: *mut wasm_extern_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_vec_new_uninitialized"] + pub fn wasm_extern_vec_new_uninitialized(out: *mut wasm_extern_vec_t, arg1: usize); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_vec_new"] + pub fn wasm_extern_vec_new( + out: *mut wasm_extern_vec_t, + arg1: usize, + arg2: *const *mut wasm_extern_t, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_vec_copy"] + pub fn wasm_extern_vec_copy(out: *mut wasm_extern_vec_t, arg1: *const wasm_extern_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_vec_delete"] + pub fn wasm_extern_vec_delete(arg1: *mut wasm_extern_vec_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_kind"] + pub fn wasm_extern_kind(arg1: *const wasm_extern_t) -> wasm_externkind_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_type"] + pub fn wasm_extern_type(arg1: *const wasm_extern_t) -> *mut wasm_externtype_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_as_extern"] + pub fn wasm_func_as_extern(arg1: *mut wasm_func_t) -> *mut wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_as_extern"] + pub fn wasm_global_as_extern(arg1: *mut wasm_global_t) -> *mut wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_as_extern"] + pub fn wasm_tag_as_extern(arg1: *mut wasm_tag_t) -> *mut wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_as_extern"] + pub fn wasm_table_as_extern(arg1: *mut wasm_table_t) -> *mut wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_as_extern"] + pub fn wasm_memory_as_extern(arg1: *mut wasm_memory_t) -> *mut wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_func"] + pub fn wasm_extern_as_func(arg1: *mut wasm_extern_t) -> *mut wasm_func_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_global"] + pub fn wasm_extern_as_global(arg1: *mut wasm_extern_t) -> *mut wasm_global_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_tag"] + pub fn wasm_extern_as_tag(arg1: *mut wasm_extern_t) -> *mut wasm_tag_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_table"] + pub fn wasm_extern_as_table(arg1: *mut wasm_extern_t) -> *mut wasm_table_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_memory"] + pub fn wasm_extern_as_memory(arg1: *mut wasm_extern_t) -> *mut wasm_memory_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_func_as_extern_const"] + pub fn wasm_func_as_extern_const(arg1: *const wasm_func_t) -> *const wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_global_as_extern_const"] + pub fn wasm_global_as_extern_const(arg1: *const wasm_global_t) -> *const wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_tag_as_extern_const"] + pub fn wasm_tag_as_extern_const(arg1: *const wasm_tag_t) -> *const wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_table_as_extern_const"] + pub fn wasm_table_as_extern_const(arg1: *const wasm_table_t) -> *const wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_memory_as_extern_const"] + pub fn wasm_memory_as_extern_const(arg1: *const wasm_memory_t) -> *const wasm_extern_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_func_const"] + pub fn wasm_extern_as_func_const(arg1: *const wasm_extern_t) -> *const wasm_func_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_global_const"] + pub fn wasm_extern_as_global_const(arg1: *const wasm_extern_t) -> *const wasm_global_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_tag_const"] + pub fn wasm_extern_as_tag_const(arg1: *const wasm_extern_t) -> *const wasm_tag_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_table_const"] + pub fn wasm_extern_as_table_const(arg1: *const wasm_extern_t) -> *const wasm_table_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_extern_as_memory_const"] + pub fn wasm_extern_as_memory_const(arg1: *const wasm_extern_t) -> *const wasm_memory_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_delete"] + pub fn wasm_instance_delete(arg1: *mut wasm_instance_t); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_copy"] + pub fn wasm_instance_copy(arg1: *const wasm_instance_t) -> *mut wasm_instance_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_same"] + pub fn wasm_instance_same(arg1: *const wasm_instance_t, arg2: *const wasm_instance_t) -> bool; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_get_host_info"] + pub fn wasm_instance_get_host_info(arg1: *const wasm_instance_t) + -> *mut ::std::os::raw::c_void; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_set_host_info"] + pub fn wasm_instance_set_host_info( + arg1: *mut wasm_instance_t, + arg2: *mut ::std::os::raw::c_void, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_set_host_info_with_finalizer"] + pub fn wasm_instance_set_host_info_with_finalizer( + arg1: *mut wasm_instance_t, + arg2: *mut ::std::os::raw::c_void, + arg3: ::std::option::Option, + ); +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_as_ref"] + pub fn wasm_instance_as_ref(arg1: *mut wasm_instance_t) -> *mut wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_instance"] + pub fn wasm_ref_as_instance(arg1: *mut wasm_ref_t) -> *mut wasm_instance_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_as_ref_const"] + pub fn wasm_instance_as_ref_const(arg1: *const wasm_instance_t) -> *const wasm_ref_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_ref_as_instance_const"] + pub fn wasm_ref_as_instance_const(arg1: *const wasm_ref_t) -> *const wasm_instance_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_new"] + pub fn wasm_instance_new( + arg1: *mut wasm_store_t, + arg2: *const wasm_module_t, + imports: *const wasm_extern_vec_t, + arg3: *mut *mut wasm_trap_t, + ) -> *mut wasm_instance_t; +} +unsafe extern "C" { + #[link_name = "\u{1}wee8_wasm_instance_exports"] + pub fn wasm_instance_exports(arg1: *const wasm_instance_t, out: *mut wasm_extern_vec_t); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct __crt_locale_data { + pub _address: u8, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct __crt_multibyte_data { + pub _address: u8, +} diff --git a/src/wasix/runtime/engine/windows/bridge.cpp b/src/wasix/runtime/engine/windows/bridge.cpp new file mode 100644 index 000000000..0d4c11d30 --- /dev/null +++ b/src/wasix/runtime/engine/windows/bridge.cpp @@ -0,0 +1,65 @@ +#define LIBWASM_STATIC +#include "wasm.hh" +namespace wasm { +template <> class Shared { + friend class destroyer; + void destroy(); +protected: + Shared() = default; + ~Shared() = default; +}; +} +extern "C" void oliphaunt_store_delete(wasm::Store* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_shared_memory_delete(wasm::Shared* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_shared_module_delete(wasm::Shared* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_functype_delete(wasm::FuncType* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_module_delete(wasm::Module* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_ref_delete(wasm::Ref* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_extern_delete(wasm::Extern* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_valtype_delete(wasm::ValType* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_memorytype_delete(wasm::MemoryType* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_globaltype_delete(wasm::GlobalType* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_tabletype_delete(wasm::TableType* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_trap_delete(wasm::Trap* p) { + if (p) wasm::destroyer{}(p); +} +extern "C" void oliphaunt_externtype_delete(wasm::ExternType* p) { + if (p) wasm::destroyer{}(p); +} +// Pinned V8's C wasm_func_call adopts and destroys its const argument array. +// Borrow it instead: Wasmer owns and reuses that array across WASIX reentry. +struct Values { size_t size; wasm::Val* data; }; +struct BorrowedValues { + wasm::vec values; + ~BorrowedValues() { values.release(); } +}; +extern "C" wasm::Trap* oliphaunt_func_call(const wasm::Func* func, + const Values* args, Values* results) { + BorrowedValues borrowed{wasm::vec::adopt(args->size, args->data)}; + auto values = wasm::vec::adopt(results->size, results->data); + auto trap = func->call(borrowed.values, values); + *results = {values.size(), values.release()}; + return trap.release(); +} diff --git a/src/wasix/runtime/engine/windows/build-caches.mts b/src/wasix/runtime/engine/windows/build-caches.mts new file mode 100644 index 000000000..88a3d2369 --- /dev/null +++ b/src/wasix/runtime/engine/windows/build-caches.mts @@ -0,0 +1,109 @@ +import assert from 'node:assert/strict'; +import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { sha256File } from '../../../../third-party/tools/source-fetch-core.mts'; + +const root = path.resolve(import.meta.dir, '../../../../..'); +const engine = path.join(root, 'target/oliphaunt-wasix/engine/windows-x64-msvc'); +const output = path.join(engine, 'cache/sse41'); +const work = path.join(root, 'target/oliphaunt-wasix/engine/cache-producer'); +const source = JSON.parse(readFileSync(path.join(engine, 'source.json'), 'utf8')); +assert.equal(sha256File(path.join(engine, 'oliphaunt_wee8.dll')), source.dllSha256); +mkdirSync(output, { recursive: true }); +mkdirSync(work, { recursive: true }); +for (const file of ['cache-producer.exe', 'oliphaunt_wee8.dll']) { + copyFileSync(path.join(engine, file), path.join(work, file)); +} + +const env = { ...process.env, WINEPREFIX: path.join(work, 'wine-prefix'), WINEDEBUG: '-all' }; +function run(command: string[], emulated = false) { + const result = Bun.spawnSync(command, { + cwd: work, + env: emulated ? { ...env, WINELOADERNOEXEC: '1' } : env, + timeout: 900_000, + }); + assert.equal(result.exitCode, 0, result.stdout.toString() + result.stderr.toString()); + return result.stdout.toString(); +} +const wine = '/usr/lib/wine/wine64'; +run([wine, 'cmd', '/c', 'exit', '0']); +const receipts = []; +const products = Bun.argv.slice(2); +assert( + products.length > 0 && + products.every((product) => ['runtime', 'tools', 'extensions'].includes(product)), +); +for (const product of products) { + const plan = Bun.spawnSync( + [ + 'cargo', + 'run', + '-p', + 'xtask', + '--locked', + '--', + 'assets', + 'prepare-aot', + '--target-triple', + 'x86_64-pc-windows-msvc', + '--product', + product, + ], + { cwd: root }, + ); + assert.equal(plan.exitCode, 0, plan.stderr.toString()); + for (const row of plan.stdout.toString().trim().split('\n')) { + const [input, destination] = row.split('\t'); + assert(input && destination, 'incomplete AOT producer input'); + const guest = readFileSync(input); + copyFileSync(input, path.join(work, 'guest.wasm')); + const log = run(['qemu-x86_64', '-cpu', 'Penryn', wine, 'cache-producer.exe', 'write'], true); + assert.match(log, /cpu_mask=0xe /, 'the baseline must come from a real emulated SSE4.1 CPU'); + assert.match(log, /native_deserialize=PASS/); + const cache = readFileSync(path.join(work, 'cache.bin')); + let wireLength = 0, + offset = 0, + shift = 0; + while (offset < 10) { + const byte = cache[offset++]; + wireLength += (byte & 127) * 2 ** shift; + if (!(byte & 128)) break; + shift += 7; + } + assert(Number.isSafeInteger(wireLength)); + assert(cache.length - offset - wireLength >= 20, 'missing native cache'); + assert.deepEqual( + cache.subarray(offset, offset + wireLength), + guest, + 'native cache guest identity', + ); + assert.equal(cache.readUInt32LE(offset + wireLength), 0xc0de0689, 'pinned native cache format'); + assert.equal(cache.readUInt32LE(offset + wireLength + 8), 0xe, 'unmodified SSE4.1 CPU profile'); + const digest = sha256File(input); + const file = path.join(output, `${digest}.bin`); + writeFileSync(file, cache); + const read = run(['qemu-x86_64', '-cpu', 'Penryn', wine, 'cache-producer.exe', 'read'], true); + assert.match(read, /native_deserialize=PASS/, 'fresh-process native cache reader'); + receipts.push({ + product, + moduleSha256: digest, + cacheSha256: sha256File(file), + bytes: cache.length, + }); + console.log(`SSE4.1 AOT ${product}: ${path.basename(input)}`); + } +} +writeFileSync( + path.join(output, 'source.json'), + JSON.stringify( + { + sourceSha: source.sourceSha, + dllSha256: source.dllSha256, + cpu: 'Penryn', + nativeCpuMask: '0xe', + modules: receipts, + }, + null, + 2, + ) + '\n', +); diff --git a/src/wasix/runtime/engine/windows/build.mts b/src/wasix/runtime/engine/windows/build.mts new file mode 100644 index 000000000..982c97fe9 --- /dev/null +++ b/src/wasix/runtime/engine/windows/build.mts @@ -0,0 +1,168 @@ +import assert from 'node:assert/strict'; +import { + copyFileSync, + existsSync, + mkdirSync, + readFileSync, + statSync, + writeFileSync, +} from 'node:fs'; +import path from 'node:path'; +import { archiveTreeDigest, sha256File } from '../../../../third-party/tools/source-fetch-core.mts'; +import { renderDispatch, renderExports, V8_FLAGS } from './dispatch.mts'; +import { ENGINE_SOURCE_CRATES } from '../contract.mts'; + +const root = path.resolve(import.meta.dir, '../../../../..'); +const owner = path.join(root, 'src/wasix/runtime/engine'); +const pins = Bun.TOML.parse(readFileSync(path.join(owner, 'source.toml'), 'utf8')); +const inputs = path.join(root, 'target/oliphaunt-wasix/engine/inputs'); +const output = path.join(root, 'target/oliphaunt-wasix/engine/windows-x64-msvc'); +const inputNames = ['wee8-windows', 'v8-header', 'v8-license']; + +function verify(name: string, file: string) { + assert(inputNames.includes(name), `unknown engine input ${name}`); + assert.equal(statSync(file).size, pins[name].bytes, `${name}: incorrect byte size`); + assert.equal(sha256File(file), pins[name].sha256, `${name}: checksum mismatch`); +} + +function run(command: string[]) { + const result = Bun.spawnSync(command, { cwd: output, stdout: 'inherit', stderr: 'inherit' }); + assert.equal(result.exitCode, 0, `engine producer failed: ${command[0]}`); +} + +function build() { + assert.equal(process.platform, 'win32', 'the native engine producer requires Windows x64 MSVC'); + assert.equal(process.arch, 'x64'); + for (const name of inputNames) verify(name, path.join(inputs, name)); + const header = path.join(owner, 'crates/wasmer/upstream/third-party/wee8/wasm.h'); + assert.equal( + sha256File(header), + '97f49de5418f02c55633dbcdc96b72fb404659c9275e9a47a3b4d6bdce384b96', + 'regenerate C bindings for the pinned header', + ); + mkdirSync(output, { recursive: true }); + const archive = path.join(inputs, 'wee8-windows'); + const library = path.join(output, 'v8.lib'); + const pin = pins['wee8-windows']; + if ( + !existsSync(library) || + statSync(library).size !== pin['member-bytes'] || + sha256File(library) !== pin['member-sha256'] + ) { + const extracted = Bun.spawnSync(['tar', '-xOf', path.basename(archive), '--', pin.member], { + cwd: inputs, + stdout: Bun.file(library), + stderr: 'inherit', + }); + assert.equal(extracted.exitCode, 0, 'extract pinned V8 static archive'); + assert.equal(statSync(library).size, pin['member-bytes']); + assert.equal(sha256File(library), pin['member-sha256']); + } + copyFileSync(path.join(inputs, 'v8-header'), path.join(output, 'wasm.hh')); + copyFileSync(path.join(inputs, 'v8-license'), path.join(output, 'LICENSE.v8')); + const msvc = path.join(process.env.VCToolsInstallDir ?? '', 'bin/HostX64/x64'); + const compiler = path.join(msvc, 'cl.exe'), + linker = path.join(msvc, 'link.exe'); + assert(existsSync(compiler) && existsSync(linker), 'initialize the existing MSVC environment'); + const { symbols } = renderDispatch('0'.repeat(64)); + writeFileSync(path.join(output, 'engine.def'), renderExports(symbols)); + run([ + compiler, + '/nologo', + '/c', + '/O2', + '/MT', + '/EHsc', + '/std:c++20', + `/I${output}`, + path.join(import.meta.dir, 'bridge.cpp'), + `/Fo${path.join(output, 'bridge.obj')}`, + ]); + run([ + linker, + '/NOLOGO', + '/DLL', + '/Brepro', + `/OUT:${path.join(output, 'oliphaunt_wee8.dll')}`, + `/DEF:${path.join(output, 'engine.def')}`, + `/IMPLIB:${path.join(output, 'oliphaunt_wee8.lib')}`, + path.join(output, 'bridge.obj'), + library, + 'winmm.lib', + 'dbghelp.lib', + 'shlwapi.lib', + ]); + writeFileSync( + path.join(output, 'profile.h'), + `#define OLIPHAUNT_V8_FLAGS ${JSON.stringify(V8_FLAGS)}\n`, + ); + run([ + compiler, + '/nologo', + '/c', + '/O2', + '/GS-', + '/Zl', + `/I${output}`, + path.join(import.meta.dir, 'cache-producer.c'), + `/Fo${path.join(output, 'cache-producer.obj')}`, + ]); + run([ + linker, + '/nologo', + '/nodefaultlib', + '/machine:x64', + '/entry:mainCRTStartup', + '/subsystem:console', + '/Brepro', + path.join(output, 'cache-producer.obj'), + 'kernel32.lib', + path.join(output, 'oliphaunt_wee8.lib'), + `/out:${path.join(output, 'cache-producer.exe')}`, + ]); + const dll = path.join(output, 'oliphaunt_wee8.dll'); + const digest = sha256File(dll); + const prebuilt = path.join(owner, 'crates/wasmer/prebuilt'); + mkdirSync(prebuilt, { recursive: true }); + const bindings = path.join(prebuilt, 'embedded_bindings.rs'); + writeFileSync(bindings, renderDispatch(digest).generated); + run(['rustfmt', '--edition', '2024', bindings]); + writeFileSync( + path.join(output, 'source.json'), + JSON.stringify( + { + source: pins, + engineSources: Object.fromEntries( + ENGINE_SOURCE_CRATES.map((name) => [ + name, + archiveTreeDigest(path.join(owner, 'crates', name, 'upstream')), + ]), + ), + sourceSha: Bun.spawnSync(['git', 'rev-parse', 'HEAD'], { cwd: root }) + .stdout.toString() + .trim(), + dllSha256: digest, + bindingsSha256: sha256File(bindings), + flags: V8_FLAGS, + entries: symbols.length, + }, + null, + 2, + ) + '\n', + ); +} + +if (import.meta.main) { + const [operation, name, file] = Bun.argv.slice(2); + if (operation === 'acquisition-plan' && !name) { + for (const input of inputNames) console.log(`${input}\t${pins[input].url}`); + } else if (operation === 'verify-input' && name && file) { + try { + verify(name, file); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } + } else if (operation === 'build' && !name) build(); + else throw new Error('usage: build.mts acquisition-plan | verify-input NAME FILE | build'); +} diff --git a/src/wasix/runtime/engine/windows/build.sh b/src/wasix/runtime/engine/windows/build.sh new file mode 100644 index 000000000..eed727e3d --- /dev/null +++ b/src/wasix/runtime/engine/windows/build.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail +case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) ;; *) exit 0 ;; esac +cd "$(git rev-parse --show-toplevel)" +source tools/dev/acquisition.sh +oliphaunt_acquisition_start 'Windows WASIX engine' 1800 +owner=src/wasix/runtime/engine/windows +inputs=target/oliphaunt-wasix/engine/inputs +mkdir -p "$inputs" +plan="$(bun "$owner/build.mts" acquisition-plan)" +while IFS=$'\t' read -r name url; do + destination="$inputs/$name" + if bun "$owner/build.mts" verify-input "$name" "$destination"; then continue; fi + temporary="$(mktemp "$inputs/.download.XXXXXX")" + trap 'rm -f "$temporary"' EXIT + oliphaunt_acquisition_curl 1200 4 2 curl --fail --silent --show-error --location \ + --proto '=https' --proto-redir '=https' --output "$temporary" "$url" + bun "$owner/build.mts" verify-input "$name" "$temporary" + mv "$temporary" "$destination" + trap - EXIT +done <<<"$plan" +bun "$owner/build.mts" build diff --git a/src/wasix/runtime/engine/windows/cache-producer.c b/src/wasix/runtime/engine/windows/cache-producer.c new file mode 100644 index 000000000..4f37fcc75 --- /dev/null +++ b/src/wasix/runtime/engine/windows/cache-producer.c @@ -0,0 +1,166 @@ +#include "profile.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + +/* Producer-only Windows V8 cache control, linked without the CRT. */ +typedef SIZE_T usize; +typedef struct { + usize size; + unsigned char *data; +} Vec; + +#define IMP __declspec(dllimport) +IMP void oliphaunt_set_v8_flags(const char *, usize); +IMP void *wee8_wasm_engine_new(void); +IMP void *wee8_wasm_store_new(void *); +IMP void wee8_wasm_store_delete(void *); +IMP void wee8_wasm_engine_delete(void *); +IMP void wee8_wasm_byte_vec_new_uninitialized(Vec *, usize); +IMP void wee8_wasm_byte_vec_delete(Vec *); +IMP void *wee8_wasm_module_new(void *, const Vec *); +IMP void *wee8_wasm_module_deserialize(void *, const Vec *); +IMP void wee8_wasm_module_serialize(void *, Vec *); +IMP void wee8_wasm_module_delete(void *); + +static void print(const char *text) { + DWORD done; + usize length = 0; + while (text[length]) length++; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), text, (DWORD)length, &done, NULL); +} + +static void number(usize value, int hex) { + char out[32]; + unsigned length = 0, base = hex ? 16 : 10; + do { + unsigned digit = value % base; + out[length++] = (char)(digit < 10 ? '0' + digit : 'a' + digit - 10); + value /= base; + } while (value); + if (hex) print("0x"); + for (unsigned i = 0; i < length / 2; i++) { + char swap = out[i]; + out[i] = out[length - 1 - i]; + out[length - 1 - i] = swap; + } + out[length] = 0; + print(out); +} + +static DWORD word(const unsigned char *bytes) { + DWORD value; + CopyMemory(&value, bytes, sizeof(value)); + return value; +} + +static void finish(DWORD code) { + /* Wine/QEMU fails in process detach after all engine cleanup returns. + SDK applications retain ordinary Windows process shutdown. */ + TerminateProcess(GetCurrentProcess(), code); + ExitProcess(31); +} + +static void fail(const char *message, DWORD code) { + print(message); + print("\n"); + finish(code); +} + +static Vec read_bytes(const WCHAR *path) { + HANDLE file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + LARGE_INTEGER size; + DWORD got = 0; + Vec bytes; + if (file == INVALID_HANDLE_VALUE || !GetFileSizeEx(file, &size) || + size.QuadPart <= 0 || size.QuadPart > 0x7fffffff) { + fail("read open/size failed", 10); + } + wee8_wasm_byte_vec_new_uninitialized(&bytes, (usize)size.QuadPart); + if (!ReadFile(file, bytes.data, (DWORD)size.QuadPart, &got, NULL) || got != size.QuadPart) { + fail("read bytes failed", 11); + } + CloseHandle(file); + return bytes; +} + +static void write_bytes(const WCHAR *path, const Vec *bytes) { + HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + DWORD done = 0; + if (file == INVALID_HANDLE_VALUE || + !WriteFile(file, bytes->data, (DWORD)bytes->size, &done, NULL) || done != bytes->size) { + fail("write failed", 12); + } + CloseHandle(file); +} + +void mainCRTStartup(void) { + int cpu[4]; + __cpuidex(cpu, 1, 0); + print("cpuid_leaf1_ecx="); + number((DWORD)cpu[2], 1); + __cpuidex(cpu, 7, 0); + print(" cpuid_leaf7_ecx="); + number((DWORD)cpu[2], 1); + print("\n"); + + static const char flags[] = OLIPHAUNT_V8_FLAGS; + oliphaunt_set_v8_flags(flags, sizeof(flags) - 1); + int reader = 0; + const WCHAR *command = GetCommandLineW(); + for (usize i = 0; command[i]; i++) { + if (command[i] == ' ' && command[i + 1] == 'r' && command[i + 2] == 'e' && + command[i + 3] == 'a' && command[i + 4] == 'd') reader = 1; + } + print(reader ? "operation=read\n" : "operation=write\n"); + + void *engine = wee8_wasm_engine_new(); + if (!engine) fail("engine failed", 20); + void *store = wee8_wasm_store_new(engine); + if (!store) fail("store failed", 20); + Vec data = read_bytes(reader ? L"cache.bin" : L"guest.wasm"); + if (!reader) { + void *module = wee8_wasm_module_new(store, &data); + if (!module) fail("compile failed", 21); + Vec cache; + wee8_wasm_module_serialize(module, &cache); + wee8_wasm_module_delete(module); + wee8_wasm_byte_vec_delete(&data); + data = cache; + write_bytes(L"cache.bin", &data); + } + + usize position = 0, wire = 0; + unsigned shift = 0; + do { + if (position >= data.size || shift > 63) fail("invalid wire length", 22); + unsigned byte = data.data[position++]; + wire |= (usize)(byte & 127) << shift; + shift += 7; + if (!(byte & 128)) break; + } while (1); + if (wire > data.size - position || data.size - position - wire < 20) { + fail("missing native cache", 23); + } + usize offset = position + wire; + print("native_bytes="); + number(data.size - offset, 0); + print(" cpu_mask="); + number(word(data.data + offset + 8), 1); + print(" flag_hash="); + number(word(data.data + offset + 12), 1); + print("\n"); + + void *module = wee8_wasm_module_deserialize(store, &data); + if (!module) fail("native_deserialize=REJECT", 24); + print("native_deserialize=PASS (no compilation fallback)\n"); + wee8_wasm_module_delete(module); + wee8_wasm_byte_vec_delete(&data); + print("stage=module_deleted\n"); + wee8_wasm_store_delete(store); + print("stage=store_deleted\n"); + wee8_wasm_engine_delete(engine); + print("stage=engine_deleted\n"); + print("stage=emulated_producer_completed\n"); + finish(0); +} diff --git a/src/wasix/runtime/engine/windows/dispatch.mts b/src/wasix/runtime/engine/windows/dispatch.mts new file mode 100644 index 000000000..3cc52a982 --- /dev/null +++ b/src/wasix/runtime/engine/windows/dispatch.mts @@ -0,0 +1,140 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; + +const DECLARATION = + /unsafe extern "C" \{\s*#\[link_name = "\\u\{1\}(wee8_[^"]+)"\]\s*pub fn (\w+)\(([^;]*)\)\s*(->[^;]+)?;\s*\}/gs; +const UNSUPPORTED = new Set([ + 'wee8_wasm_tag_get', + 'wee8_wasm_tag_set', + 'wee8_wasm_tagtype_as_externtype', + 'wee8_wasm_tagtype_as_externtype_const', +]); +const BRIDGES = [ + [ + '?GetCurrent@Isolate@v8@@SAPEAV12@XZ', + 'oliphaunt_v8_current_isolate', + '', + '-> *mut std::ffi::c_void', + ], + [ + '?TerminateExecution@Isolate@v8@@QEAAXXZ', + 'oliphaunt_v8_terminate_execution', + 'ptr: *mut std::ffi::c_void', + '', + ], + ['oliphaunt_externtype_delete', 'oliphaunt_externtype_delete', 'ptr: *mut wasm_tagtype_t', ''], + [ + 'oliphaunt_set_v8_flags', + 'oliphaunt_set_v8_flags', + 'flags: *const std::ffi::c_char, size: usize', + '', + ], +]; + +export const V8_FLAGS = + '--mcpu=generic --no-enable-sse4-2 --no-enable-sahf --no-enable-avx --no-enable-avx2 --no-enable-avx-vnni --no-enable-avx-vnni-int8 --no-enable-fma3 --no-enable-f16c --no-enable-bmi1 --no-enable-bmi2 --no-enable-lzcnt --no-enable-popcnt --no-intel-jcc-erratum-mitigation'; + +function argumentNames(args: string) { + let depth = 0, + start = 0; + const names: string[] = []; + for (const [offset, char] of [...`${args},`].entries()) { + if ('(<['.includes(char)) depth++; + else if (')>]'.includes(char) && !(char === '>' && args[offset - 1] === '-')) depth--; + else if (char === ',' && depth === 0) { + const argument = args.slice(start, offset).trim(); + if (argument) names.push(argument.split(':', 1)[0].trim()); + start = offset + 1; + } + } + assert.equal(depth, 0, 'unbalanced C declaration'); + return names; +} + +export function renderDispatch(digest: string) { + assert.match(digest, /^[0-9a-f]{64}$/); + const symbols: string[] = []; + const omitted = new Set(); + function wrap(symbol: string, name: string, args: string, ret = '', visibility = 'pub') { + if (UNSUPPORTED.has(symbol)) { + omitted.add(symbol); + return ''; // Absent upstream exports stay absent: future calls fail to compile. + } + const index = symbols.length; + symbols.push(symbol); + return `${visibility} unsafe fn ${name}(${args}) ${ret} { + type Function = unsafe extern "C" fn(${args}) ${ret}; + let function = unsafe { + std::mem::transmute::<*mut std::ffi::c_void, Function>(embedded_engine::symbol(${index})) + }; + unsafe { function(${argumentNames(args).join(', ')}) } +}`; + } + let count = 0; + let generated = readFileSync(path.join(import.meta.dir, 'bindings.rs'), 'utf8').replace( + DECLARATION, + (_, symbol, name, args, ret) => { + count++; + return wrap(symbol, name, args, ret); + }, + ); + assert.equal(count, 314, 'pinned Wasmer C ABI changed'); + assert.deepEqual(omitted, UNSUPPORTED); + assert(!generated.includes('link_name'), 'unhandled foreign declaration'); + for (const [symbol, name, args, ret] of BRIDGES) { + generated += `\n${wrap(symbol, name, args, ret, 'pub(crate)')}\n`; + } + const loader = readFileSync(path.join(import.meta.dir, 'loader.rs'), 'utf8').replace( + 'ENGINE_DIGEST', + digest, + ); + generated += `\nmod embedded_engine {\n${loader} +const REQUIRED_SYMBOLS: &[&[u8]] = &[ +${symbols.map((symbol) => ` b"${symbol}\\0",`).join('\n')} +]; +} +pub(crate) fn oliphaunt_v8_prepare() -> std::io::Result<()> { + embedded_engine::prepare() +} +pub(crate) unsafe fn oliphaunt_v8_configure() { + const FLAGS: &[u8] = b"${V8_FLAGS}"; + unsafe { oliphaunt_set_v8_flags(FLAGS.as_ptr().cast(), FLAGS.len()) }; +} +`; + return { generated, symbols }; +} + +export function renderExports(symbols: string[]) { + const owners = new Set( + [ + 'store', + 'shared_memory', + 'shared_module', + 'functype', + 'module', + 'ref', + 'extern', + 'valtype', + 'memorytype', + 'globaltype', + 'tabletype', + 'trap', + ].map((name) => `wasm_${name}_delete`), + ); + owners.add('wasm_func_call'); + return ( + 'LIBRARY oliphaunt_wee8\nEXPORTS\n' + + symbols + .filter((symbol) => symbol.startsWith('wee8_')) + .map((symbol) => { + const original = symbol.slice('wee8_'.length); + const implementation = owners.has(original) + ? original.replace('wasm_', 'oliphaunt_') + : original; + return ` ${symbol}=${implementation}`; + }) + .join('\n') + + '\n oliphaunt_externtype_delete\n oliphaunt_set_v8_flags=?SetFlagsFromString@V8@v8@@SAXPEBD_K@Z\n' + ); +} diff --git a/src/wasix/runtime/engine/windows/dispatch.test.mts b/src/wasix/runtime/engine/windows/dispatch.test.mts new file mode 100644 index 000000000..6569a70df --- /dev/null +++ b/src/wasix/runtime/engine/windows/dispatch.test.mts @@ -0,0 +1,21 @@ +import { expect, test } from 'bun:test'; +import { renderDispatch, renderExports, V8_FLAGS } from './dispatch.mts'; + +test('resolves the complete pinned C ABI before V8 initialization', () => { + const { generated, symbols } = renderDispatch('a'.repeat(64)); + expect(new Set(symbols).size).toBe(314); + expect(generated).not.toContain('link_name'); + expect(generated).not.toContain('pub unsafe fn wasm_tag_get('); + expect(generated).toContain(V8_FLAGS); + expect(generated).toContain('Oliphaunt/engines/' + 'a'.repeat(64)); + expect(generated).toContain('type Function = unsafe extern "C" fn('); + // Nested callback types must not become extra call arguments. + expect(generated.includes('function(arg1, type_, arg2, env, finalizer)')).toBe(true); + expect(renderExports(symbols)).toContain('wee8_wasm_func_call=oliphaunt_func_call'); + expect(renderExports(symbols)).toContain('wee8_wasm_store_delete=oliphaunt_store_delete'); + expect(symbols.includes('wee8_wasm_tag_get')).toBe(false); +}); + +test('requires a content-addressed engine identity', () => { + expect(() => renderDispatch('../engine')).toThrow(); +}); diff --git a/src/wasix/runtime/engine/windows/loader.rs b/src/wasix/runtime/engine/windows/loader.rs new file mode 100644 index 000000000..6d7f4d858 --- /dev/null +++ b/src/wasix/runtime/engine/windows/loader.rs @@ -0,0 +1,259 @@ +// Prepared native Windows delivery. Resolve every entry before initializing V8. +use std::{ + ffi::{OsString, c_void}, + io::{self, Write}, + os::windows::ffi::{OsStrExt, OsStringExt}, + path::{Path, PathBuf}, + sync::{Mutex, OnceLock}, +}; + +#[repr(C)] +struct Guid { + data1: u32, + data2: u16, + data3: u16, + data4: [u8; 8], +} +#[link(name = "shell32")] +unsafe extern "system" { + fn SHGetKnownFolderPath( + folder: *const Guid, + flags: u32, + token: *mut c_void, + path: *mut *mut u16, + ) -> i32; +} +#[link(name = "ole32")] +unsafe extern "system" { + fn CoTaskMemFree(ptr: *mut c_void); +} +#[link(name = "kernel32")] +unsafe extern "system" { + fn LoadLibraryExW(path: *const u16, file: *mut c_void, flags: u32) -> *mut c_void; + fn GetProcAddress(module: *mut c_void, name: *const u8) -> *mut c_void; + fn FreeLibrary(module: *mut c_void) -> i32; +} +const DLL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/oliphaunt_wee8.dll")); +static PREPARED: OnceLock = OnceLock::new(); +static PREPARING: Mutex<()> = Mutex::new(()); + +struct Library(usize); +impl Drop for Library { + fn drop(&mut self) { + unsafe { FreeLibrary(self.0 as *mut c_void) }; + } +} +struct Prepared { + // OnceLock retains a successful DLL for process life. Failed resolution + // drops the library before V8 initializes any global state. + _library: Library, + symbols: Vec, +} + +#[derive(Debug)] +struct DeliveryError { + action: String, + path: PathBuf, + source: io::Error, +} +impl std::fmt::Display for DeliveryError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + formatter, + "{} '{}': {}", + self.action, + self.path.display(), + self.source + ) + } +} +impl std::error::Error for DeliveryError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.source) + } +} +fn context(error: io::Error, action: &str, path: &Path) -> io::Error { + io::Error::new( + error.kind(), + DeliveryError { + action: action.to_owned(), + path: path.to_owned(), + source: error, + }, + ) +} + +fn cache_path() -> io::Result { + const LOCAL_APP_DATA: Guid = Guid { + data1: 0xf1b32785, + data2: 0x6fba, + data3: 0x4fcf, + data4: [0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91], + }; + let mut folder = std::ptr::null_mut(); + let status = + unsafe { SHGetKnownFolderPath(&LOCAL_APP_DATA, 0x8000, std::ptr::null_mut(), &mut folder) }; + if status < 0 || folder.is_null() { + if !folder.is_null() { + unsafe { CoTaskMemFree(folder.cast()) }; + } + return Err(io::Error::other(format!( + "locate Windows local app data: HRESULT {status:#x}" + ))); + } + let mut len = 0; + unsafe { + while *folder.add(len) != 0 { + len += 1; + } + } + let root = PathBuf::from(OsString::from_wide(unsafe { + std::slice::from_raw_parts(folder, len) + })); + unsafe { CoTaskMemFree(folder.cast()) }; + Ok(root.join("Oliphaunt/engines/ENGINE_DIGEST/oliphaunt_wee8.dll")) +} + +fn valid_payload(path: &Path) -> io::Result { + match std::fs::read(path) { + Ok(bytes) => Ok(bytes == DLL), + Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(false), + Err(error) => Err(context(error, "read internal engine cache", path)), + } +} + +fn install(path: &Path) -> io::Result<()> { + // A valid warm cache also works without directory write permission. + if valid_payload(path)? { + return Ok(()); + } + let root = path.parent().expect("internal engine path has a parent"); + std::fs::create_dir_all(root) + .map_err(|error| context(error, "create internal engine cache", root))?; + let mut temporary = tempfile::NamedTempFile::new_in(root) + .map_err(|error| context(error, "create temporary engine", root))?; + temporary + .write_all(DLL) + .map_err(|error| context(error, "write temporary engine", temporary.path()))?; + temporary + .as_file() + .sync_all() + .map_err(|error| context(error, "sync temporary engine", temporary.path()))?; + if let Err(error) = temporary.persist(path) { + // Another process may have installed and loaded the same immutable DLL. + // Accept it only when every byte matches; otherwise leave it untouched. + if !valid_payload(path).unwrap_or(false) { + return Err(context(error.error, "replace internal engine cache", path)); + } + } + if !valid_payload(path)? { + return Err(io::Error::other(format!( + "internal engine cache identity differs: '{}'", + path.display() + ))); + } + Ok(()) +} + +fn load(path: &Path, names: &[&[u8]]) -> io::Result { + let wide: Vec<_> = path.as_os_str().encode_wide().chain(Some(0)).collect(); + let module = unsafe { LoadLibraryExW(wide.as_ptr(), std::ptr::null_mut(), 0x100 | 0x800) }; + if module.is_null() { + return Err(context( + io::Error::last_os_error(), + "load internal engine", + path, + )); + } + let library = Library(module as usize); + let mut symbols = Vec::with_capacity(names.len()); + for name in names { + let pointer = unsafe { GetProcAddress(module, name.as_ptr()) }; + if pointer.is_null() { + return Err(context( + io::Error::last_os_error(), + &format!( + "resolve internal engine entry {}", + String::from_utf8_lossy(&name[..name.len() - 1]) + ), + path, + )); + } + symbols.push(pointer as usize); + } + Ok(Prepared { + _library: library, + symbols, + }) +} + +pub(super) fn prepare() -> io::Result<()> { + if PREPARED.get().is_some() { + return Ok(()); + } + let _lock = PREPARING.lock().unwrap_or_else(|error| error.into_inner()); + if PREPARED.get().is_none() { + let path = cache_path()?; + install(&path)?; + let prepared = load(&path, REQUIRED_SYMBOLS)?; + PREPARED.get_or_init(|| prepared); + } + Ok(()) +} + +pub(super) fn symbol(index: usize) -> *mut c_void { + PREPARED + .get() + .expect("V8 engine must be prepared before dispatch") + .symbols[index] as *mut c_void +} + +#[cfg(test)] +mod oliphaunt_contract { + use super::*; + + #[test] + fn repairs_missing_truncated_and_wrong_engine_bytes() { + let root = tempfile::tempdir().unwrap(); + let path = root.path().join("engine.dll"); + for bytes in [None, Some(&DLL[..DLL.len() / 2]), Some(&b"wrong bytes"[..])] { + if let Some(bytes) = bytes { + std::fs::write(&path, bytes).unwrap(); + } + install(&path).unwrap(); + assert!(valid_payload(&path).unwrap()); + } + } + + #[test] + fn a_locked_bad_cache_reports_failure_and_can_be_retried() { + use std::os::windows::fs::OpenOptionsExt; + let root = tempfile::tempdir().unwrap(); + let path = root.path().join("engine.dll"); + std::fs::write(&path, b"wrong bytes").unwrap(); + let locked = std::fs::OpenOptions::new() + .read(true) + .share_mode(0) + .open(&path) + .unwrap(); + assert!(install(&path).is_err()); + drop(locked); + install(&path).unwrap(); + assert!(valid_payload(&path).unwrap()); + } + + #[test] + fn missing_exports_release_the_library_before_retry() { + let root = tempfile::tempdir().unwrap(); + let path = root.path().join("engine.dll"); + install(&path).unwrap(); + assert!(load(&path, &[b"oliphaunt_missing_entry\0"]).is_err()); + // Windows prevents removal of a DLL while its image is still loaded. + std::fs::remove_file(&path).unwrap(); + install(&path).unwrap(); + let prepared = load(&path, REQUIRED_SYMBOLS).unwrap(); + assert_eq!(prepared.symbols.len(), REQUIRED_SYMBOLS.len()); + drop(prepared); + std::fs::remove_file(&path).unwrap(); + } +} diff --git a/src/wasix/runtime/moon.yml b/src/wasix/runtime/moon.yml index d1b68546f..f97f56392 100644 --- a/src/wasix/runtime/moon.yml +++ b/src/wasix/runtime/moon.yml @@ -47,6 +47,8 @@ owners: - "@oliphaunt/wasix" tools/**: - "@oliphaunt/wasix" + engine/**: + - "@oliphaunt/wasix" fileGroups: version: - VERSION @@ -58,6 +60,14 @@ fileGroups: cargo-carrier-sources: - crates/**/*.rs - crates/**/Cargo.toml + - engine/crates/**/*.rs + - engine/crates/**/Cargo.toml + - "!engine/crates/*/upstream/**" + engine-source: + - engine/source.toml + - engine/contract.mts + - engine/prepare-sources.{mts,sh} + - engine/patches/**/* extension-smoke-inputs: - /src/wasix/pgwire-server/Cargo.toml - /src/wasix/pgwire-server/tests/extensions.rs @@ -82,6 +92,65 @@ fileGroups: - /src/wasix/runtime/tools/wasix-cargo-toolchain-policy.mts - /tools/dev/bun.sh tasks: + engine-test: + tags: [runtime, requires-rust] + deps: ["liboliphaunt-wasix:engine-build"] + script: | + set -eu + case "$(uname -s)" in + MINGW*|MSYS*|CYGWIN*) + bun src/wasix/runtime/engine/test-packaged.mts + ;; + esac + inputs: + - "@group(cargo-workspace)" + - "@group(packaging-source)" + - "@group(legal-files)" + - "@group(upstream-licenses)" + - engine/package.mts + - engine/test-packaged.mts + - engine/crates/**/Cargo.toml + - engine/crates/**/*.rs + - "!engine/crates/*/upstream/**" + - "!engine/crates/*/prebuilt/**" + - /src/third-party/tools/source-fetch-core.mts + - /tools/release/release-artifact-targets.mts + options: + cache: false + runFromWorkspaceRoot: true + engine-build: + tags: [runtime, artifact, requires-rust] + command: bash src/wasix/runtime/engine/windows/build.sh + deps: + - liboliphaunt-wasix:engine-sources + inputs: + - "@group(engine-source)" + - engine/windows/*.{cpp,c,mts,rs,sh} + - "!engine/windows/*.test.mts" + - "!engine/windows/build-caches.mts" + - /tools/dev/acquisition.sh + outputs: + - /target/oliphaunt-wasix/engine/windows-x64-msvc/oliphaunt_wee8.dll + - /target/oliphaunt-wasix/engine/windows-x64-msvc/cache-producer.exe + - /target/oliphaunt-wasix/engine/windows-x64-msvc/source.json + - /target/oliphaunt-wasix/engine/windows-x64-msvc/LICENSE.v8 + - engine/crates/wasmer/prebuilt/embedded_bindings.rs + options: + cache: false + runFromWorkspaceRoot: true + engine-sources: + command: bash src/wasix/runtime/engine/prepare-sources.sh + inputs: + - "@group(engine-source)" + - /src/third-party/tools/fetch-sources.sh + - /src/third-party/tools/source-fetch-core.mts + - /tools/dev/acquisition.sh + outputs: + - engine/crates/*/upstream/**/* + - engine/crates/*/.prepared.json + options: + cache: false + runFromWorkspaceRoot: true build-orchestration-test: tags: - quality @@ -241,6 +310,7 @@ tasks: command: bash src/wasix/runtime/tools/build-aot-target.sh deps: - liboliphaunt-wasix:runtime-portable + - liboliphaunt-wasix:engine-test inputs: - "@group(legal-files)" - "@group(cargo-workspace)" @@ -373,6 +443,7 @@ tasks: options: runFromWorkspaceRoot: true packaging-unit: + deps: ["liboliphaunt-wasix:engine-sources"] tags: - quality - unit @@ -386,7 +457,7 @@ tasks: - /src/extensions/external/**/upstream-licenses/**/* - /src/database-resources/contracts/*.mts - "@group(upstream-licenses)" - - THIRD_PARTY_NOTICES.md + - /src/wasix/sdks/rust/THIRD_PARTY_NOTICES.md - "@group(legal-files)" - "@group(release-target-contract)" - "@group(package-test-metadata)" diff --git a/src/wasix/runtime/release.toml b/src/wasix/runtime/release.toml index 09d642241..577275239 100644 --- a/src/wasix/runtime/release.toml +++ b/src/wasix/runtime/release.toml @@ -3,6 +3,12 @@ owner = "@oliphaunt/wasix" kind = "wasm-runtime" publish_targets = ["github-release-assets", "crates-io", "npm"] registry_packages = [ + "crates:oliphaunt-wasmer", + "crates:oliphaunt-wasmer-wasix-types", + "crates:oliphaunt-wasmer-journal", + "crates:oliphaunt-wasmer-wasix", + "crates:oliphaunt-wasmer-v8-windows-x64-msvc", + "crates:liboliphaunt-wasix-portable", "crates:liboliphaunt-wasix-aot-aarch64-apple-darwin", "crates:liboliphaunt-wasix-aot-aarch64-unknown-linux-gnu", diff --git a/src/wasix/runtime/tools/check-release-assets.test.mts b/src/wasix/runtime/tools/check-release-assets.test.mts index 9018295ca..afe8b7282 100644 --- a/src/wasix/runtime/tools/check-release-assets.test.mts +++ b/src/wasix/runtime/tools/check-release-assets.test.mts @@ -38,7 +38,7 @@ function archiveStage(stage, archive, archiveRoot) { } function stageAotPayload(stage, target, profile = 'wasix-aot') { - const canonical = canonicalWasixAotMetadata(); + const canonical = canonicalWasixAotMetadata(undefined, target); const raw = Buffer.from(`aot-payload:${target}\n`); const compressed = zstdCompressSync(raw); const manifest = { diff --git a/src/wasix/runtime/tools/download-assets.sh b/src/wasix/runtime/tools/download-assets.sh index bece7954f..6aca75653 100755 --- a/src/wasix/runtime/tools/download-assets.sh +++ b/src/wasix/runtime/tools/download-assets.sh @@ -70,5 +70,8 @@ else bash .github/scripts/download-build-artifacts.sh CI "$sha" "$payload/target/oliphaunt-wasix/aot/$triple" "${download_args[@]}" --artifact "$artifact" install_args+=(--target-triple "$triple") done <<<"$rows" + if [[ "$rows" = *x86_64-pc-windows-msvc* ]]; then + bash .github/scripts/download-build-artifacts.sh CI "$sha" "$root" "${download_args[@]}" --artifact liboliphaunt-wasix-windows-engine + fi fi cargo run --quiet --locked -p xtask -- assets import-download "${install_args[@]}" diff --git a/src/wasix/runtime/tools/package-carriers.mts b/src/wasix/runtime/tools/package-carriers.mts index 1bc3b6535..abce97d3a 100644 --- a/src/wasix/runtime/tools/package-carriers.mts +++ b/src/wasix/runtime/tools/package-carriers.mts @@ -15,6 +15,7 @@ import { TOOL, } from '../../../../tools/packaging/release-carrier.mts'; import { writeChecksumManifest } from '../../../../tools/packaging/write-checksum-manifest.mts'; +import { aotPayloadPartParent } from '../../../../tools/packaging/wasix-cargo-payload.mts'; import { compareText, contribCarrierDescriptor, @@ -35,6 +36,8 @@ import { } from './wasix-extension-cargo-artifact-inventory.mts'; import { packWasixRuntimeNpmCarrier } from './wasix-runtime-npm-carrier.mts'; +import { ENGINE_PAYLOAD_PACKAGE, isEnginePayloadPart } from '../engine/contract.mts'; + export const WASIX_PRODUCT = 'liboliphaunt-wasix'; function hasWasixReleaseArchive(assetDir) { @@ -109,6 +112,7 @@ export function validateWasixCargoArtifacts(outputDir) { } const packages = []; const allowedKinds = new Set([ + 'wasix-engine', 'wasix-runtime', 'wasix-aot', 'wasix-extension', @@ -138,6 +142,8 @@ export function validateWasixCargoArtifacts(outputDir) { } if ( !expectedBaseCrates.has(name) && + !(kind === 'wasix-engine' && isEnginePayloadPart(name)) && + !(kind === 'wasix-aot' && expectedBaseCrates.has(aotPayloadPartParent(name))) && !isExpectedWasixExtensionPackage(name, kind, expectedExtensionInventory) ) { fail(`unexpected ${WASIX_PRODUCT} Cargo artifact crate ${name}`); @@ -166,6 +172,32 @@ export function validateWasixCargoArtifacts(outputDir) { `generated ${WASIX_PRODUCT} Cargo artifacts are missing configured runtime crates: ${missingBaseCrates.join(', ')}`, ); } + const engine = packages.find((row) => row.name === ENGINE_PAYLOAD_PACKAGE); + const engineManifest = Bun.TOML.parse(readFileSync(engine.manifestPath, 'utf8')); + const engineParts = Object.keys(engineManifest['build-dependencies'] ?? {}).filter( + isEnginePayloadPart, + ); + if (engineParts.length === 0) + fail('Windows engine carrier must declare its frozen payload parts'); + assertSameStringSet( + 'Windows engine payload parts must match the facade dependency closure', + new Set(packages.map((row) => row.name).filter(isEnginePayloadPart)), + new Set(engineParts), + ); + for (const parentName of expectedBaseCrates) { + if (!parentName.startsWith('liboliphaunt-wasix-aot-')) continue; + const parent = packages.find((row) => row.name === parentName); + const manifest = Bun.TOML.parse(readFileSync(parent.manifestPath, 'utf8')); + const declaredParts = Object.keys(manifest['build-dependencies'] ?? {}).filter( + (name) => aotPayloadPartParent(name) === parentName, + ); + const generatedParts = packages.filter((row) => aotPayloadPartParent(row.name) === parentName); + assertSameStringSet( + `${parentName} AOT payload parts must match the facade dependency closure`, + new Set(generatedParts.map((row) => row.name)), + new Set(declaredParts), + ); + } const unexpected = readdirSync(outputDir) .filter((name) => name.endsWith('.crate')) .map((name) => path.join(outputDir, name)) diff --git a/src/wasix/runtime/tools/package-liboliphaunt-wasix-cargo-artifacts.test.mts b/src/wasix/runtime/tools/package-liboliphaunt-wasix-cargo-artifacts.test.mts index 1f5b6e0b3..696b30751 100644 --- a/src/wasix/runtime/tools/package-liboliphaunt-wasix-cargo-artifacts.test.mts +++ b/src/wasix/runtime/tools/package-liboliphaunt-wasix-cargo-artifacts.test.mts @@ -172,8 +172,8 @@ function aggregateFixture(root, { nestedOwner = false } = {}) { 2, )}\n`, ); - const canonicalAot = canonicalWasixAotMetadata(); for (const [targetId, targetTriple] of Object.entries(AOT_TARGET_TRIPLES)) { + const canonicalAot = canonicalWasixAotMetadata(undefined, targetTriple); for (const sqlName of ['cube', 'earthdistance']) { const directory = path.join(productRoot, 'wasix-aot', targetId, sqlName); const artifactName = `${sqlName}.bin.zst`; @@ -464,8 +464,8 @@ liboliphaunt-wasix-portable = { path = ${JSON.stringify(path.join(ROOT, 'src/was test('runtime Cargo packaging rejects an AOT carrier that cannot initialize fresh databases', () => { const root = mkdtempSync(path.join(scratch, 'runtime-aot-closure-')); - const canonical = canonicalWasixAotMetadata(); const target = supportedRustcHostTriple(); + const canonical = canonicalWasixAotMetadata(undefined, target); const raw = Buffer.from('core AOT fixture'); const bytes = zstdCompressSync(raw); const manifest = { diff --git a/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts b/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts index 1df7e1a00..9f48c3431 100755 --- a/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts +++ b/src/wasix/runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts @@ -37,6 +37,7 @@ import { import { RUST_BUILD_SCRIPT_SHA256 } from '../../../../tools/packaging/rust-build-script-sha256.mts'; import { cargoPackage, + packageAotSpec, packageSpec, validateCrateSize, } from '../../../../tools/packaging/wasix-cargo-payload.mts'; @@ -66,6 +67,8 @@ import { wasixExtensionPackageName, } from './wasix-cargo-artifact-contract.mts'; +import { packageEngine } from '../engine/package.mts'; + const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..'); const PRODUCT = 'liboliphaunt-wasix'; const PREFIX = 'package_liboliphaunt_wasix_cargo_artifacts.mts'; @@ -405,7 +408,7 @@ function rewriteRuntimeCoreManifest(root) { writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); } -function runtimeCorePayload(runtimeRoot, extractRoot) { +export function runtimeCorePayload(runtimeRoot, extractRoot) { const coreRoot = path.join(extractRoot, 'runtime-core-payload'); rmSync(coreRoot, { recursive: true, force: true }); cpSync(runtimeRoot, coreRoot, { recursive: true }); @@ -1766,14 +1769,15 @@ export function packageWasixCargoArtifacts(argv) { ); const specs = args.extensionsOnly ? [] : packageSpecs(assetDir, extractRoot, args.version); const packages = [ + ...(args.extensionsOnly ? [] : packageEngine({ sourceRoot, outputDir, version: args.version })), ...extensionSources.flatMap((source) => packageExtensionSource(source, { outputDir, cargoTargetDir }), ), ...extensionAotSources.flatMap((source) => packageExtensionAotSource(source, { outputDir, cargoTargetDir }), ), - ...specs.map((spec) => - packageSpec(spec, { + ...specs.flatMap((spec) => { + const options = { version: args.version, sourceRoot, outputDir, @@ -1783,8 +1787,11 @@ export function packageWasixCargoArtifacts(argv) { ? (text) => injectRuntimeExtensionDependencies(text, extensionSources, extensionAotSources) : undefined, - }), - ), + }; + return spec.kind === 'wasix-aot' + ? packageAotSpec(spec, options) + : [packageSpec(spec, options)]; + }), ]; writePackagesManifest(packages, outputDir); console.log( diff --git a/src/wasix/runtime/tools/serialize-aot.sh b/src/wasix/runtime/tools/serialize-aot.sh index acb05b2da..a29b3ebec 100644 --- a/src/wasix/runtime/tools/serialize-aot.sh +++ b/src/wasix/runtime/tools/serialize-aot.sh @@ -10,10 +10,6 @@ suffix= case "$(uname -s)" in MINGW* | MSYS* | CYGWIN*) suffix=.exe - llvm_prefix="${LLVM_SYS_221_PREFIX:-${LLVM_PATH:-}}" - if [[ -n "$llvm_prefix" && -d "$llvm_prefix/lib" ]]; then - export LIB="$(cygpath -aw "$llvm_prefix/lib")${LIB:+;$LIB}" - fi ;; esac diff --git a/src/wasix/runtime/tools/serialize-aot.test.sh b/src/wasix/runtime/tools/serialize-aot.test.sh index 55a6293be..0beaf2728 100644 --- a/src/wasix/runtime/tools/serialize-aot.test.sh +++ b/src/wasix/runtime/tools/serialize-aot.test.sh @@ -12,11 +12,12 @@ printf '%s\n' "$AOT_FIXTURE_ROOT" SH cat >"$fixture/bin/uname" <<'SH' #!/usr/bin/env bash -printf 'Linux\n' +printf '%s\n' "${AOT_FIXTURE_UNAME:-Linux}" SH cat >"$fixture/bin/cargo" <<'SH' #!/usr/bin/env bash set -euo pipefail +if [[ -n "${AOT_FIXTURE_LIB:-}" ]]; then [[ "${LIB:-}" == "$AOT_FIXTURE_LIB" ]]; fi if [[ "$1" == build ]]; then exit "${FAIL_BUILD:-0}"; fi [[ "$*" == 'run -p xtask --locked -- assets prepare-aot --target-triple fixture' ]] printf 'input one.wasm\toutput one.zst\ninput two.wasm\toutput two.zst\n' @@ -34,6 +35,13 @@ bash "$script" --target-triple fixture printf 'input one.wasm\toutput one.zst\ninput two.wasm\toutput two.zst\n' >"$fixture/expected" cmp "$fixture/expected" "$fixture/serialized" rm "$fixture/serialized" +cp "$CARGO_TARGET_DIR/release/xtask" "$CARGO_TARGET_DIR/release/xtask.exe" +mkdir -p "$fixture/llvm/lib" +AOT_FIXTURE_UNAME=MINGW64_NT LLVM_SYS_221_PREFIX="$fixture/llvm" \ + LIB='existing MSVC libraries' AOT_FIXTURE_LIB='existing MSVC libraries' \ + bash "$script" --target-triple fixture +cmp "$fixture/expected" "$fixture/serialized" +rm "$fixture/serialized" if FAIL_BUILD=1 bash "$script" --target-triple fixture; then exit 1; fi [[ ! -e "$fixture/serialized" ]] if FAIL_SERIALIZE=1 bash "$script" --target-triple fixture; then exit 1; fi diff --git a/src/wasix/runtime/tools/test-packaging.sh b/src/wasix/runtime/tools/test-packaging.sh index 719d33b2e..4254e15c0 100644 --- a/src/wasix/runtime/tools/test-packaging.sh +++ b/src/wasix/runtime/tools/test-packaging.sh @@ -17,6 +17,7 @@ if bun "$contract" unsupported > /dev/null 2>&1; then exit 1 fi bash tools/dev/bun.sh test ./src/wasix/runtime/tools +bash tools/dev/bun.sh test ./src/wasix/runtime/engine node src/wasix/runtime/tools/wasix-runtime-npm.test-consumer.mts for scenario in nested-owner aggregate independent-extension; do root="$OLIPHAUNT_WASIX_PACKAGING_TEST_ROOT/$scenario" diff --git a/src/wasix/runtime/tools/wasix-aot-manifest.mts b/src/wasix/runtime/tools/wasix-aot-manifest.mts index e81559ba5..01c0ae7aa 100644 --- a/src/wasix/runtime/tools/wasix-aot-manifest.mts +++ b/src/wasix/runtime/tools/wasix-aot-manifest.mts @@ -8,6 +8,10 @@ export { WASIX_TOOLCHAIN_PATH }; export const STABLE_WASIX_SOURCE_LANE = 'stable'; export const WASIX_AOT_ENGINE = 'llvm-opta'; +export function wasixAotEngine(target) { + return target === 'x86_64-pc-windows-msvc' ? 'v8' : WASIX_AOT_ENGINE; +} + function requiredString(value, context) { if (typeof value !== 'string' || value.length === 0) { throw new Error(`${context} must be a non-empty string`); @@ -15,11 +19,11 @@ function requiredString(value, context) { return value; } -export function canonicalWasixAotMetadata(root = ROOT) { +export function canonicalWasixAotMetadata(root = ROOT, target) { const toolchain = canonicalWasixCargoToolchainVersions(root); return { sourceLane: STABLE_WASIX_SOURCE_LANE, - engine: WASIX_AOT_ENGINE, + engine: wasixAotEngine(target), wasmerVersion: toolchain.wasmer, wasmerWasixVersion: toolchain.wasmerWasix, }; @@ -27,11 +31,12 @@ export function canonicalWasixAotMetadata(root = ROOT) { export function assertCanonicalWasixAotManifest( manifest, - { context = 'WASIX AOT manifest', expectedTarget, canonical = canonicalWasixAotMetadata() } = {}, + { context = 'WASIX AOT manifest', expectedTarget, canonical } = {}, ) { if (manifest === null || typeof manifest !== 'object' || Array.isArray(manifest)) { throw new Error(`${context} must be a JSON object`); } + canonical ??= canonicalWasixAotMetadata(undefined, expectedTarget ?? manifest['target-triple']); const expected = [ ['format-version', 1], ['source-lane', canonical.sourceLane], diff --git a/src/wasix/runtime/tools/wasix-cargo-artifact-contract.mts b/src/wasix/runtime/tools/wasix-cargo-artifact-contract.mts index 238a65614..4cd3696e2 100644 --- a/src/wasix/runtime/tools/wasix-cargo-artifact-contract.mts +++ b/src/wasix/runtime/tools/wasix-cargo-artifact-contract.mts @@ -1,3 +1,4 @@ +import { ENGINE_CARGO_PACKAGES } from '../engine/contract.mts'; import { compareText } from '../../../../tools/release/release-graph.mts'; export const WASIX_CARGO_ARTIFACT_SCHEMA = 'oliphaunt-liboliphaunt-wasix-cargo-artifacts-v2'; @@ -105,7 +106,9 @@ export const AOT_TARGET_CFGS = { }; export function publicCargoPackageNames() { - return [RUNTIME_PACKAGE, ...Object.values(AOT_PACKAGES)].sort(compareText); + return [RUNTIME_PACKAGE, ...Object.values(AOT_PACKAGES), ...ENGINE_CARGO_PACKAGES].sort( + compareText, + ); } export function publicAotCargoDependencies() { diff --git a/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.mts b/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.mts index b74886d30..062fd4eec 100644 --- a/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.mts +++ b/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.mts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { ENGINE_SOURCE_CRATES } from '../engine/contract.mts'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..'); const CARGO_DEPENDENCY_SOURCE_KEYS = Object.freeze([ @@ -34,10 +35,14 @@ const REQUIRED_WASIX_TOOLCHAIN_PACKAGES = new Map([ const REQUIRED_CONSUMER_PIN_POLICIES = new Map( [...REQUIRED_WASIX_TOOLCHAIN_PACKAGES] - .filter(([, versionKey]) => versionKey === 'wasmerWasix') + .filter(([name, versionKey]) => versionKey === 'wasmerWasix' || name === 'wasmer') .map(([name, versionKey]) => [ name, - Object.freeze({ versionKey, defaultFeaturesDisabled: true }), + Object.freeze({ + versionKey: ENGINE_SOURCE_CRATES.includes(name) ? 'engine' : versionKey, + packageName: ENGINE_SOURCE_CRATES.includes(name) ? `oliphaunt-${name}` : name, + defaultFeaturesDisabled: true, + }), ]), ); REQUIRED_CONSUMER_PIN_POLICIES.set( @@ -70,6 +75,7 @@ export function canonicalWasixCargoToolchainVersions(root = ROOT) { } const toolchain = objectTable(data.toolchain); return Object.freeze({ + engine: readFileSync(path.join(root, 'src/wasix/runtime/VERSION'), 'utf8').trim(), wasmer: requiredString(toolchain.wasmer, `${WASIX_TOOLCHAIN_PATH} toolchain.wasmer`), wasmerWasix: requiredString( toolchain['wasmer-wasix'], @@ -95,6 +101,7 @@ export function validateWasixConsumerDependencyPins( const failures = []; const dependencies = objectTable(manifest?.dependencies); for (const [name, policy] of REQUIRED_CONSUMER_PIN_POLICIES) { + const packageName = policy.packageName ?? name; const expectedVersion = toolchainVersions?.[policy.versionKey]; if (typeof expectedVersion !== 'string' || expectedVersion.length === 0) { failures.push( @@ -103,7 +110,7 @@ export function validateWasixConsumerDependencyPins( continue; } const matches = Object.entries(dependencies).filter( - ([key, spec]) => dependencyName(key, spec) === name, + ([key, spec]) => dependencyName(key, spec) === packageName, ); if (matches.length !== 1) { failures.push( diff --git a/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.test.mts b/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.test.mts index 541654a99..6e1a13485 100644 --- a/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.test.mts +++ b/src/wasix/runtime/tools/wasix-cargo-toolchain-policy.test.mts @@ -3,15 +3,31 @@ import { REQUIRED_WASIX_CONSUMER_PINS, validateWasixConsumerDependencyPins, } from './wasix-cargo-toolchain-policy.mts'; -const toolchainVersions = { wasmer: '7.2.1', wasmerWasix: '0.702.1', webc: '12.0.0' }; +import { ENGINE_SOURCE_CRATES } from '../engine/contract.mts'; +const toolchainVersions = { + engine: '0.3.1', + wasmer: '7.2.1', + wasmerWasix: '0.702.1', + webc: '12.0.0', +}; -test('requires exact non-optional pins for the published WASIX family closure', () => { - const dependencies = Object.fromEntries( +function pinnedDependencies() { + return Object.fromEntries( REQUIRED_WASIX_CONSUMER_PINS.map((name) => [ name, - name === 'webc' ? '=12.0.0' : { version: '=0.702.1', 'default-features': false }, + name === 'webc' + ? '=12.0.0' + : { + version: ENGINE_SOURCE_CRATES.includes(name) ? '=0.3.1' : '=0.702.1', + 'default-features': false, + ...(ENGINE_SOURCE_CRATES.includes(name) ? { package: 'oliphaunt-' + name } : {}), + }, ]), ); +} + +test('requires exact non-optional pins for the published WASIX family closure', () => { + const dependencies = pinnedDependencies(); expect( validateWasixConsumerDependencyPins( { dependencies }, @@ -39,12 +55,7 @@ test('requires exact non-optional pins for the published WASIX family closure', }); test('rejects default-feature and source substitutions in published WASIX pins', () => { - const dependencies = Object.fromEntries( - REQUIRED_WASIX_CONSUMER_PINS.map((name) => [ - name, - name === 'webc' ? '=12.0.0' : { version: '=0.702.1', 'default-features': false }, - ]), - ); + const dependencies = pinnedDependencies(); delete dependencies['wasmer-config']['default-features']; dependencies['wasmer-journal']['default-features'] = true; dependencies['wasmer-package'].path = '../../substituted'; @@ -66,12 +77,7 @@ test('rejects default-feature and source substitutions in published WASIX pins', }); test('rejects missing, ranged, optional, and source-substituted WebC pins', () => { - const dependencies = Object.fromEntries( - REQUIRED_WASIX_CONSUMER_PINS.map((name) => [ - name, - name === 'webc' ? '=12.0.0' : { version: '=0.702.1', 'default-features': false }, - ]), - ); + const dependencies = pinnedDependencies(); delete dependencies.webc; expect( diff --git a/src/wasix/runtime/tools/xtask/Cargo.toml b/src/wasix/runtime/tools/xtask/Cargo.toml index dbdde65d1..46274cbd3 100644 --- a/src/wasix/runtime/tools/xtask/Cargo.toml +++ b/src/wasix/runtime/tools/xtask/Cargo.toml @@ -10,8 +10,6 @@ publish = false default = [] aot-serializer = [ "dep:wasmer", - "wasmer/llvm", - "wasmer/wasmer-artifact-create", "dep:wasmer-types", ] @@ -24,9 +22,15 @@ sha2 = "0.10" tar = "0.4" toml = "0.9" walkdir = "2" -wasmer = { version = "=7.5.0", default-features = false, features = [ - "sys", -], optional = true } +wasmer = { package = "oliphaunt-wasmer", version = "*", path = "../../engine/crates/wasmer", default-features = false, optional = true } wasmer-types = { version = "=7.5.0", optional = true } wasmparser = "0.261.0" zstd = "0.13" + +[target.'cfg(not(windows))'.dependencies] +wasmer = { package = "oliphaunt-wasmer", version = "*", path = "../../engine/crates/wasmer", default-features = false, optional = true, features = [ + "sys", "llvm", "wasmer-artifact-create", +] } + +[target.'cfg(windows)'.dependencies] +wasmer = { package = "oliphaunt-wasmer", version = "*", path = "../../engine/crates/wasmer", default-features = false, optional = true, features = ["v8"] } diff --git a/src/wasix/runtime/tools/xtask/moon.yml b/src/wasix/runtime/tools/xtask/moon.yml index 9192860da..81f31e663 100644 --- a/src/wasix/runtime/tools/xtask/moon.yml +++ b/src/wasix/runtime/tools/xtask/moon.yml @@ -34,6 +34,7 @@ tasks: aot-serializer-typecheck: tags: ["quality", "static", "requires-rust", "requires-wasmer-llvm"] command: "cargo check -p xtask --features aot-serializer --locked" + deps: ["liboliphaunt-wasix:engine-sources"] env: CARGO_TARGET_DIR: "target" inputs: diff --git a/src/wasix/runtime/tools/xtask/src/aot_serializer.rs b/src/wasix/runtime/tools/xtask/src/aot_serializer.rs index 5849e7e5e..5f2f83950 100644 --- a/src/wasix/runtime/tools/xtask/src/aot_serializer.rs +++ b/src/wasix/runtime/tools/xtask/src/aot_serializer.rs @@ -18,7 +18,18 @@ use crate::value_after; // This profile names the fixed codegen policy in our published manifests; // Wasmer's deterministic_id independently identifies the compiler settings. +#[cfg(not(windows))] pub(crate) const AOT_ENGINE_PROFILE: &str = "llvm-opta"; +#[cfg(windows)] +pub(crate) const AOT_ENGINE_PROFILE: &str = "v8"; + +pub(crate) fn aot_engine_profile(target: &str) -> &'static str { + if target == "x86_64-pc-windows-msvc" { + "v8" + } else { + "llvm-opta" + } +} pub(crate) fn check_aot_codegen_environment() -> Result<()> { for (name, expected) in [ @@ -86,14 +97,14 @@ fn probe_aot_serializer_in_process() -> Result<()> { #[cfg(feature = "aot-serializer")] fn probe_aot_serializer_in_process() -> Result<()> { - let engine = llvm_aot_engine(); + let engine = aot_engine()?; let store = wasmer::Store::new(engine.clone()); const EMPTY_WASM: &[u8] = b"\0asm\x01\0\0\0"; - let module = - wasmer::Module::new(&store, EMPTY_WASM).context("compile LLVM AOT probe module")?; + let module = wasmer::Module::new(&store, EMPTY_WASM) + .context("compile engine serialization probe module")?; let serialized = module .serialize() - .context("serialize LLVM AOT probe module")?; + .context("serialize engine probe module")?; print_aot_engine_config(&engine); println!("serialized-probe-bytes: {}", serialized.len()); Ok(()) @@ -101,7 +112,7 @@ fn probe_aot_serializer_in_process() -> Result<()> { #[cfg(feature = "aot-serializer")] fn serialize_aot_module(input: &Path, output: &Path) -> Result<()> { - let engine = llvm_aot_engine(); + let engine = aot_engine()?; print_aot_engine_config(&engine); println!("host-target: {}-{}", env::consts::OS, env::consts::ARCH); @@ -109,7 +120,35 @@ fn serialize_aot_module(input: &Path, output: &Path) -> Result<()> { let bytes = fs::read(input).with_context(|| format!("read {}", input.display()))?; let module = wasmer::Module::new(&store, &bytes) .with_context(|| format!("compile {}", input.display()))?; + #[cfg(not(windows))] let serialized = module.serialize().context("serialize module")?; + #[cfg(windows)] + let serialized = { + let digest = crate::fs_utils::sha256_bytes(&bytes); + let engine = PathBuf::from("target/oliphaunt-wasix/engine/windows-x64-msvc"); + let path = engine.join("cache/sse41").join(format!("{digest}.bin")); + let baseline = fs::read(&path) + .with_context(|| format!("read qualified SSE4.1 native cache {}", path.display()))?; + let source: serde_json::Value = + serde_json::from_slice(&fs::read(engine.join("source.json"))?)?; + let receipt: serde_json::Value = + serde_json::from_slice(&fs::read(engine.join("cache/sse41/source.json"))?)?; + let head = std::process::Command::new("git") + .args(["rev-parse", "HEAD"]) + .output()?; + anyhow::ensure!(head.status.success(), "read producer source commit"); + let source_sha = String::from_utf8(head.stdout)?; + anyhow::ensure!( + source["sourceSha"] == source_sha.trim(), + "stale Windows engine source" + ); + let dll_sha = crate::fs_utils::sha256_file(&engine.join("oliphaunt_wee8.dll"))?; + validate_windows_cache_receipt(&receipt, source_sha.trim(), &dll_sha, &digest, &baseline)?; + module + .as_v8() + .serialize_with_native_variants(&[baseline]) + .context("serialize Windows CPU cache profiles")? + }; if let Some(parent) = output.parent() { fs::create_dir_all(parent).with_context(|| format!("create {}", parent.display()))?; @@ -131,8 +170,51 @@ fn serialize_aot_module(input: &Path, output: &Path) -> Result<()> { Ok(()) } -#[cfg(feature = "aot-serializer")] -fn llvm_aot_engine() -> wasmer::Engine { +#[cfg(any(all(feature = "aot-serializer", windows), test))] +fn validate_windows_cache_receipt( + receipt: &serde_json::Value, + source_sha: &str, + dll_sha: &str, + guest_sha: &str, + cache: &[u8], +) -> Result<()> { + use anyhow::ensure; + ensure!( + receipt["sourceSha"] == source_sha, + "stale Windows baseline cache source" + ); + ensure!( + receipt["dllSha256"] == dll_sha, + "Windows baseline cache engine differs" + ); + ensure!( + receipt["nativeCpuMask"] == "0xe", + "Windows baseline CPU profile differs" + ); + let row = receipt["modules"] + .as_array() + .and_then(|rows| rows.iter().find(|row| row["moduleSha256"] == guest_sha)) + .ok_or_else(|| anyhow::anyhow!("Windows baseline cache has no guest receipt"))?; + ensure!( + row["bytes"] == cache.len(), + "Windows baseline cache byte size differs" + ); + ensure!( + row["cacheSha256"] == crate::fs_utils::sha256_bytes(cache), + "Windows baseline cache digest differs" + ); + Ok(()) +} + +#[cfg(all(feature = "aot-serializer", windows))] +fn aot_engine() -> Result { + Ok(wasmer::v8::V8::try_new() + .context("prepare Windows AOT producer engine")? + .into()) +} + +#[cfg(all(feature = "aot-serializer", not(windows)))] +fn aot_engine() -> Result { use wasmer::sys::{CompilerConfig, EngineBuilder, Features, LLVM}; let mut features = Features::new(); @@ -145,14 +227,14 @@ fn llvm_aot_engine() -> wasmer::Engine { // correctness/performance change, not part of patch consolidation. llvm.enable_non_volatile_memops(); llvm.enable_readonly_funcref_table(); - EngineBuilder::new(llvm) + Ok(EngineBuilder::new(llvm) .set_target(Some(portable_aot_target())) .set_features(Some(features)) .engine() - .into() + .into()) } -#[cfg(feature = "aot-serializer")] +#[cfg(all(feature = "aot-serializer", not(windows)))] fn portable_aot_target() -> wasmer_types::target::Target { use wasmer_types::target::{Architecture, CpuFeature, Target, Triple}; @@ -161,6 +243,9 @@ fn portable_aot_target() -> wasmer_types::target::Target { match triple.architecture { Architecture::X86_64 => { cpu_features.insert(CpuFeature::SSE2); + cpu_features.insert(CpuFeature::SSE3); + cpu_features.insert(CpuFeature::SSSE3); + cpu_features.insert(CpuFeature::SSE41); } Architecture::Aarch64(_) => { cpu_features.insert(CpuFeature::NEON); @@ -171,7 +256,17 @@ fn portable_aot_target() -> wasmer_types::target::Target { Target::new(triple, cpu_features) } -#[cfg(feature = "aot-serializer")] +#[cfg(all(feature = "aot-serializer", windows))] +fn print_aot_engine_config(_engine: &wasmer::Engine) { + println!("wasmer-engine: v8"); + println!("wasmer-engine-id: {AOT_ENGINE_PROFILE}"); + println!("wasmer-target-triple: x86_64-pc-windows-msvc"); + println!("wasmer-wee8-version: 11.9.7"); + println!("wasmer-v8-version: 13.6.233.17"); + println!("wasmer-feature-exceptions: enabled"); +} + +#[cfg(all(feature = "aot-serializer", not(windows)))] fn print_aot_engine_config(engine: &wasmer::Engine) { let target = portable_aot_target(); println!("wasmer-engine: llvm"); @@ -188,7 +283,7 @@ fn print_aot_engine_config(engine: &wasmer::Engine) { println!("wasmer-llvm-readonly-funcref-table: enabled"); } -#[cfg(feature = "aot-serializer")] +#[cfg(all(feature = "aot-serializer", not(windows)))] fn format_aot_cpu_features(target: &wasmer_types::target::Target) -> String { let mut features = target .cpu_features() @@ -203,7 +298,7 @@ fn format_aot_cpu_features(target: &wasmer_types::target::Target) -> String { } } -#[cfg(feature = "aot-serializer")] +#[cfg(all(feature = "aot-serializer", not(windows)))] fn env_flag(name: &str) -> bool { env::var(name) .map(|value| { @@ -221,9 +316,31 @@ fn env_flag(name: &str) -> bool { mod tests { use super::*; + #[test] + fn windows_cache_receipt_rejects_stale_engine_source_and_corrupt_bytes() { + let cache = b"native cache"; + let receipt = serde_json::json!({ + "sourceSha": "source", "dllSha256": "engine", "nativeCpuMask": "0xe", + "modules": [{ "moduleSha256": "guest", "bytes": cache.len(), + "cacheSha256": crate::fs_utils::sha256_bytes(cache) }] + }); + validate_windows_cache_receipt(&receipt, "source", "engine", "guest", cache).unwrap(); + for (source, engine, guest, bytes) in [ + ("other", "engine", "guest", &cache[..]), + ("source", "other", "guest", &cache[..]), + ("source", "engine", "other", &cache[..]), + ("source", "engine", "guest", &b"broken cache"[..]), + ] { + assert!( + validate_windows_cache_receipt(&receipt, source, engine, guest, bytes).is_err() + ); + } + } + #[test] fn fixed_codegen_profile_rejects_conflicting_and_ambiguous_overrides() { - assert_eq!(AOT_ENGINE_PROFILE, "llvm-opta"); + assert_eq!(aot_engine_profile("x86_64-pc-windows-msvc"), "v8"); + assert_eq!(aot_engine_profile("x86_64-unknown-linux-gnu"), "llvm-opta"); for expected in [false, true] { validate_profile_override("test", None, expected).unwrap(); validate_profile_override("test", Some(if expected { "1" } else { "0" }), expected) diff --git a/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs b/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs index a9da24168..f98b5b007 100644 --- a/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs +++ b/src/wasix/runtime/tools/xtask/src/asset_pipeline.rs @@ -1279,6 +1279,16 @@ fn has_wasm_export(link: &WasmLinkMetadataOut, name: &str) -> bool { .any(|export| export.name == name || export.name == format!("_{name}")) } +fn aot_file(module: &BuildModuleOutput, target: &str) -> String { + module.aot_file.replace( + "-llvm-opta.bin.zst", + &format!( + "-{}.bin.zst", + crate::aot_serializer::aot_engine_profile(target) + ), + ) +} + fn aot_source_dir(product: AssetProduct, target: &str) -> PathBuf { // Old producer outputs used a different memory-codegen policy. A fresh // profile directory prevents packaging those bytes under the new identity. @@ -1286,7 +1296,7 @@ fn aot_source_dir(product: AssetProduct, target: &str) -> PathBuf { .root() .join("aot-source") .join(target) - .join(crate::aot_serializer::AOT_ENGINE_PROFILE) + .join(crate::aot_serializer::aot_engine_profile(target)) } pub(crate) fn prepare_aot_artifacts( @@ -1309,7 +1319,7 @@ pub(crate) fn prepare_aot_artifacts( .iter() .filter(|module| module.requires_aot && product.owns(&module.name)) { - let output = source_dir.join(&module.aot_file); + let output = source_dir.join(aot_file(module, target)); let input = fs::canonicalize(&module.path) .with_context(|| format!("canonicalize {}", module.path.display()))?; let input = input.to_str().context("AOT input path is not UTF-8")?; @@ -1960,7 +1970,8 @@ fn package_aot_artifacts( .filter(|module| module.requires_aot && outputs.product.owns(&module.name)) { let name = module.name.as_str(); - let file = module.aot_file.as_str(); + let file = aot_file(module, target); + let file = file.as_str(); let source = source_dir.join(file); if !source.exists() { bail!( @@ -2000,7 +2011,7 @@ fn package_aot_artifacts( source_fingerprint: outputs.source_fingerprint.clone(), postgres_version: Some(outputs.postgres_version.clone()), target_triple: target.to_owned(), - engine: crate::aot_serializer::AOT_ENGINE_PROFILE.to_owned(), + engine: crate::aot_serializer::aot_engine_profile(target).to_owned(), wasmer_version: sources.toolchain.wasmer.clone(), wasmer_wasix_version: sources.toolchain.wasmer_wasix.clone(), artifacts: manifest_artifacts, @@ -2053,7 +2064,7 @@ pub(crate) fn package_extension_aot_artifacts( let Some(sql_name) = extension_module_sql_name(&module.name) else { bail!("extension AOT module has invalid name {}", module.name); }; - let source = source_dir.join(&module.aot_file); + let source = source_dir.join(aot_file(module, target)); if !source.exists() { bail!( "missing extension AOT artifact {}; run AOT generation for target {target} before packaging", @@ -2063,7 +2074,7 @@ pub(crate) fn package_extension_aot_artifacts( let extension_dir = artifacts_root.join(sql_name); fs::create_dir_all(&extension_dir) .with_context(|| format!("create {}", extension_dir.display()))?; - let destination = extension_dir.join(&module.aot_file); + let destination = extension_dir.join(aot_file(module, target)); copy_file(&source, &destination)?; let raw_artifact = decode_zstd_file(&destination) .with_context(|| format!("decode extension AOT artifact {}", destination.display()))?; @@ -2072,7 +2083,7 @@ pub(crate) fn package_extension_aot_artifacts( .or_default() .push(AotManifestArtifact { name: module.name.clone(), - path: module.aot_file.clone(), + path: aot_file(module, target), sha256: sha256_file(&destination)?, raw_sha256: sha256_bytes(&raw_artifact), raw_size: raw_artifact.len() as u64, @@ -2094,7 +2105,7 @@ pub(crate) fn package_extension_aot_artifacts( source_fingerprint: outputs.source_fingerprint.clone(), postgres_version: Some(outputs.postgres_version.clone()), target_triple: target.to_owned(), - engine: crate::aot_serializer::AOT_ENGINE_PROFILE.to_owned(), + engine: crate::aot_serializer::aot_engine_profile(target).to_owned(), wasmer_version: sources.toolchain.wasmer.clone(), wasmer_wasix_version: sources.toolchain.wasmer_wasix.clone(), artifacts, @@ -2161,7 +2172,7 @@ pub(crate) fn check_aot_product_manifest( )?; ensure_eq( &manifest.engine, - crate::aot_serializer::AOT_ENGINE_PROFILE, + crate::aot_serializer::aot_engine_profile(target), "AOT manifest engine", )?; ensure_eq( @@ -2586,7 +2597,7 @@ mod tests { let legacy = product.root().join("aot-source").join(target); assert_eq!( aot_source_dir(product, target), - legacy.join(crate::aot_serializer::AOT_ENGINE_PROFILE) + legacy.join(crate::aot_serializer::aot_engine_profile(target)) ); assert_ne!(aot_source_dir(product, target), legacy); } @@ -2725,7 +2736,7 @@ mod tests { source_fingerprint: source_fingerprint.map(str::to_owned), postgres_version: postgres_version.map(str::to_owned), target_triple: "aarch64-apple-darwin".to_owned(), - engine: crate::aot_serializer::AOT_ENGINE_PROFILE.to_owned(), + engine: crate::aot_serializer::aot_engine_profile("aarch64-apple-darwin").to_owned(), wasmer_version: toolchain.wasmer, wasmer_wasix_version: toolchain.wasmer_wasix, artifacts: vec![AotManifestArtifact { diff --git a/src/wasix/sdks/rust/Cargo.toml b/src/wasix/sdks/rust/Cargo.toml index 214676143..e558e14f2 100644 --- a/src/wasix/sdks/rust/Cargo.toml +++ b/src/wasix/sdks/rust/Cargo.toml @@ -102,32 +102,38 @@ liboliphaunt-wasix-portable = { version = "*", path = "../../runtime/crates/asse oliphaunt-wasix-tools = { version = "*", path = "../../postgres-tools/crates/tools", optional = true } oliphaunt-icu = { version = "*", path = "../../../database-resources/icu/cargo", optional = true } tokio = { version = "1", features = ["io-util", "rt-multi-thread", "sync"] } -wasmer = { version = "=7.5.0", default-features = false, features = [ - "sys", - "headless", - "compiler", - "wasmer-artifact-load", -] } +wasmer = { package = "oliphaunt-wasmer", version = "*", path = "../../runtime/engine/crates/wasmer", default-features = false } # Wasmer-WASIX 0.x crates use compatible ranges for packages in their own # release family. Keep the complete family constrained here so a fresh # consumer cannot silently mix a later patch generation with the runtime and # AOT artifacts built against this exact toolchain. wasmer-config = { version = "=0.705.0", default-features = false } -wasmer-journal = { version = "=0.705.0", default-features = false } +wasmer-journal = { package = "oliphaunt-wasmer-journal", version = "*", path = "../../runtime/engine/crates/wasmer-journal", default-features = false } wasmer-package = { version = "=0.705.0", default-features = false } wasmer-types = "=7.5.0" -wasmer-wasix = { version = "=0.705.0", default-features = false, features = [ +wasmer-wasix = { package = "oliphaunt-wasmer-wasix", version = "*", path = "../../runtime/engine/crates/wasmer-wasix", default-features = false, features = [ "sys-minimal", "sys-poll", "host-vnet", "time", ] } -wasmer-wasix-types = { version = "=0.705.0", default-features = false } +wasmer-wasix-types = { package = "oliphaunt-wasmer-wasix-types", version = "*", path = "../../runtime/engine/crates/wasmer-wasix-types", default-features = false } virtual-fs = { version = "=0.705.0", default-features = false } virtual-mio = { version = "=0.705.0", default-features = false } virtual-net = { version = "=0.705.0", default-features = false } webc = "=12.0.1" +[target.'cfg(not(windows))'.dependencies] +wasmer = { package = "oliphaunt-wasmer", version = "*", path = "../../runtime/engine/crates/wasmer", default-features = false, features = [ + "sys", "headless", "compiler", "wasmer-artifact-load", +] } + +[target.'cfg(windows)'.dependencies] +# WASIX sys-minimal retains Sys types. Headless satisfies that dependency +# without enabling a Windows compiler; runtime stores explicitly use V8. +wasmer = { package = "oliphaunt-wasmer", version = "*", path = "../../runtime/engine/crates/wasmer", default-features = false, features = ["v8", "headless"] } +wasmer-wasix = { package = "oliphaunt-wasmer-wasix", version = "*", path = "../../runtime/engine/crates/wasmer-wasix", default-features = false, features = ["v8"] } + [target.'cfg(all(target_os = "macos", target_arch = "aarch64"))'.dependencies] liboliphaunt-wasix-aot-aarch64-apple-darwin = { version = "*", path = "../../runtime/crates/aot/aarch64-apple-darwin" } oliphaunt-wasix-tools-aot-aarch64-apple-darwin = { version = "*", path = "../../postgres-tools/crates/aot/aarch64-apple-darwin", optional = true } diff --git a/src/wasix/sdks/rust/THIRD_PARTY_NOTICES.md b/src/wasix/sdks/rust/THIRD_PARTY_NOTICES.md index 30c809e64..6bf80856e 100644 --- a/src/wasix/sdks/rust/THIRD_PARTY_NOTICES.md +++ b/src/wasix/sdks/rust/THIRD_PARTY_NOTICES.md @@ -16,9 +16,14 @@ bytes under `THIRD_PARTY_LICENSES/`: - `ICU-LICENSE` — ICU commit `8eca245c7484ac6cc179e3e5f7c1ea7680810f39`. - `OpenSSL-LICENSE.txt` — OpenSSL commit `286ddeaac037533bbdce65b3c689e3f7ffebf0f6`. +- `Wasmer-LICENSE` — Wasmer 7.5.0 and WASIX 0.705.0, with the compatibility + patches owned by `src/wasix/runtime/engine/`. +- `V8-LICENSES.txt` — Windows wee8 11.9.7 / V8 13.6.233.17, including the + licenses of the libraries bundled into that engine. Third-party source pins for optional external extensions are maintained in `src/third-party/`, and WASIX toolchain inputs are maintained in -`tools/dev/`. Exact SQL extension selection is modeled in -`extensions/`; generated WASM assets must include only the +`src/wasix/runtime/toolchain.toml` and `src/wasix/runtime/engine/source.toml`. +Exact SQL extension selection is modeled in +`src/extensions/`; generated WASM assets must include only the extension artifacts explicitly selected for the release payload. diff --git a/src/wasix/sdks/rust/moon.yml b/src/wasix/sdks/rust/moon.yml index 5cd5a3370..6876d3174 100644 --- a/src/wasix/sdks/rust/moon.yml +++ b/src/wasix/sdks/rust/moon.yml @@ -38,6 +38,8 @@ fileGroups: tasks: cargo-sources: + deps: + - liboliphaunt-wasix:engine-build inputs: - project: "liboliphaunt-wasix" group: "cargo-carrier-sources" @@ -48,15 +50,20 @@ tasks: test-regression: tags: ["regression", "runtime", "requires-rust", "ci-wasix-release-regression"] command: "bash src/wasix/runtime/tools/runtime-smoke.sh regression" - deps: [{target: "cargo-sources", cacheStrategy: hash}, "liboliphaunt-wasix:runtime-aot", "extension-artifacts-wasix:build-target", "extension-artifacts-wasix:build-aot", "postgres-tools-wasix:build-aot"] + deps: [{target: "cargo-sources", cacheStrategy: hash}, "liboliphaunt-wasix:engine-sources", "liboliphaunt-wasix:runtime-aot", "extension-artifacts-wasix:build-target", "extension-artifacts-wasix:build-aot", "postgres-tools-wasix:build-aot"] inputs: ["@group(code)", "@group(cargo-workspace)", "/src/wasix/runtime/tools/{runtime-smoke,runtime-preflight,cargo-test-filter}.sh", "/src/wasix/runtime/tools/wasix-extension-features.mts", "/src/extensions/artifacts/packages/tools/**/*"] - options: {runFromWorkspaceRoot: true, cache: false} + options: + cache: false + # The full lifecycle also builds the pgwire CLI with catalog features. + mutex: "oliphaunt-pgwire-server" + runFromWorkspaceRoot: true test-integration: tags: ["runtime", "integration", "requires-rust", "ci-wasix-release-regression", "platform-linux-x64-gnu"] command: "bash src/wasix/sdks/rust/tools/test-resources.sh" deps: - target: cargo-sources cacheStrategy: hash + - "liboliphaunt-wasix:engine-sources" - "liboliphaunt-wasix:runtime-aot" - "database-resources:build-wasix-standard" - "database-resources:build-wasix-icu" @@ -82,6 +89,10 @@ tasks: deps: - target: cargo-sources cacheStrategy: hash + - target: "oliphaunt-wasix-rust:package" + cacheStrategy: ignored + - target: "oliphaunt-query:package" + cacheStrategy: ignored - "liboliphaunt-wasix:runtime-aot" - "extension-artifacts-wasix:build-aot" - "postgres-tools-wasix:build-aot" @@ -90,6 +101,7 @@ tasks: - "@group(cargo-workspace)" - "@group(rust-test-config)" - "@group(legal-files)" + - "@group(packaging-source)" - project: "liboliphaunt-wasix" group: "extension-smoke-inputs" - project: "liboliphaunt-wasix" @@ -98,6 +110,7 @@ tasks: - "/src/wasix/runtime/tools/xtask/**/*" options: cache: false + mutex: "oliphaunt-pgwire-server" runFromWorkspaceRoot: true build: @@ -188,6 +201,7 @@ tasks: runFromWorkspaceRoot: true package: + deps: ["liboliphaunt-wasix:engine-sources"] tags: ["release", "artifact-package", "ci-wasix-rust-package"] script: | set -eu diff --git a/src/wasix/sdks/rust/src/oliphaunt/aot.rs b/src/wasix/sdks/rust/src/oliphaunt/aot.rs index 7296b9c09..c4a11eb4d 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/aot.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/aot.rs @@ -8,6 +8,7 @@ use anyhow::{Context, Result, bail, ensure}; use directories::ProjectDirs; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +#[cfg(not(windows))] use wasmer::sys::{EngineBuilder, Features, NativeEngineExt}; use wasmer::{Engine, Module}; use zstd::stream::read::Decoder as ZstdDecoder; @@ -15,18 +16,17 @@ use zstd::stream::read::Decoder as ZstdDecoder; use super::assets; const RUNTIME_ARTIFACT: &str = "runtime:oliphaunt"; +#[cfg(not(windows))] const EXPECTED_AOT_ENGINE: &str = "llvm-opta"; +#[cfg(windows)] +const EXPECTED_AOT_ENGINE: &str = "v8"; const EXPECTED_WASMER_VERSION: &str = "7.5.0"; const EXPECTED_WASMER_WASIX_VERSION: &str = "0.705.0"; -const AOT_ENGINE_ID: &str = concat!( - "engine=", - "llvm-opta", - ";wasmer=", - "7.5.0", - ";wasmer-wasix=", - "0.705.0", - ";cpu=generic-baseline" -); +#[cfg(not(windows))] +const AOT_ENGINE_ID: &str = "engine=llvm-opta;wasmer=7.5.0;wasmer-wasix=0.705.0;cpu=sse41-or-neon"; +#[cfg(windows)] +const AOT_ENGINE_ID: &str = + "engine=v8;wasmer=7.5.0;wasmer-wasix=0.705.0;wee8=11.9.7;v8=13.6.233.17;cpu=sse41"; const ZSTD_MAGIC: &[u8] = &[0x28, 0xb5, 0x2f, 0xfd]; const CACHE_RECEIPT_FORMAT_VERSION: u32 = 1; const TOOL_AOT_ARTIFACTS: &[&str] = &["tool:pg_dump", "tool:psql"]; @@ -47,21 +47,29 @@ enum AotVerifyMode { Full, } -pub(crate) fn headless_engine() -> Engine { - HEADLESS_ENGINE - .get_or_init(|| { - let mut features = Features::new(); - features.exceptions(true); - EngineBuilder::headless() - .set_features(Some(features)) - .engine() - .into() - }) - .clone() +pub(crate) fn headless_engine() -> Result { + if let Some(engine) = HEADLESS_ENGINE.get() { + return Ok(engine.clone()); + } + #[cfg(windows)] + let engine: Engine = wasmer::v8::V8::try_new() + .context("prepare Windows runtime engine")? + .into(); + #[cfg(not(windows))] + let engine: Engine = { + let mut features = Features::new(); + features.exceptions(true); + EngineBuilder::headless() + .set_features(Some(features)) + .engine() + .into() + }; + // Cache successful construction only so delivery failures can be retried. + Ok(HEADLESS_ENGINE.get_or_init(|| engine).clone()) } pub(crate) fn load_runtime_module() -> Result<(Engine, Module)> { - let engine = headless_engine(); + let engine = headless_engine()?; let module = load_artifact_module(&engine, RUNTIME_ARTIFACT)?; Ok((engine, module)) } @@ -368,7 +376,7 @@ fn artifact_raw_bytes( bytes.to_vec() } else { bail!( - "no package-manager-resolved Wasmer LLVM AOT artifact named '{name}' is available for target {}; publish and stage the matching liboliphaunt-wasix AOT artifact crate with the application", + "no package-manager-resolved Wasmer runtime artifact named '{name}' is available for target {}; publish and stage the matching liboliphaunt-wasix AOT artifact crate with the application", target_triple() ) }; @@ -557,7 +565,7 @@ fn target_aot_manifest() -> Result { return Ok(manifest); } bail!( - "no package-manager-resolved Wasmer LLVM AOT manifest is available for target {}; publish and stage the matching liboliphaunt-wasix AOT artifact crate with the application", + "no package-manager-resolved Wasmer runtime manifest is available for target {}; publish and stage the matching liboliphaunt-wasix AOT artifact crate with the application", target_triple() ) } @@ -803,9 +811,20 @@ fn aot_verify_mode() -> Result { #[allow(unsafe_code)] fn deserialize_headless(engine: &Engine, path: &Path) -> Result { - deserialize_headless_mmap(engine, path) + #[cfg(windows)] + { + // SAFETY: these are the same trusted package bytes validated and + // materialized by the artifact installer. V8 has no Sys mmap loader. + unsafe { Module::deserialize_from_file(engine, path) } + .with_context(|| format!("deserialize Wasmer V8 artifact {}", path.display())) + } + #[cfg(not(windows))] + { + deserialize_headless_mmap(engine, path) + } } +#[cfg(not(windows))] #[allow(unsafe_code)] fn deserialize_headless_mmap(engine: &Engine, path: &Path) -> Result { // SAFETY: same artifact ownership and cache-key constraints as the file diff --git a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs index 9b7c15620..e634df795 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/postgres_mod.rs @@ -1207,6 +1207,7 @@ fn build_wasix_runtime( let task_manager = guest_wasm_tasks.apply(task_manager); let mut wasix_runtime = PluggableRuntime::new(task_manager); wasix_runtime.set_engine(engine.clone()); + wasix_runtime.module_cache_only = true; wasix_runtime.set_module_cache(module_cache); Arc::new(wasix_runtime) } diff --git a/src/wasix/sdks/rust/src/oliphaunt/tools.rs b/src/wasix/sdks/rust/src/oliphaunt/tools.rs index d509373af..4d6f4b81f 100644 --- a/src/wasix/sdks/rust/src/oliphaunt/tools.rs +++ b/src/wasix/sdks/rust/src/oliphaunt/tools.rs @@ -603,7 +603,7 @@ where stdin, args, } = invocation; - let engine = aot::headless_engine(); + let engine = aot::headless_engine()?; let module = match assets { // SAFETY: ToolAssets construction requires trusted compiler output. Some(assets) => unsafe { diff --git a/src/wasix/sdks/rust/tools/prepare-rust-release-source.mts b/src/wasix/sdks/rust/tools/prepare-rust-release-source.mts index e7c2cdba3..0d946ec4a 100644 --- a/src/wasix/sdks/rust/tools/prepare-rust-release-source.mts +++ b/src/wasix/sdks/rust/tools/prepare-rust-release-source.mts @@ -1,6 +1,7 @@ #!/usr/bin/env bun import fs from 'node:fs/promises'; import path from 'node:path'; +import { ENGINE_SOURCE_CRATES } from '../../../runtime/engine/contract.mts'; import { canonicalWasixCargoToolchainVersions, validateWasixConsumerDependencyPins, @@ -74,14 +75,26 @@ export function renderOliphauntWasixReleaseCargoToml( return `${before}=${version}${after}`; }); } + for (const key of ENGINE_SOURCE_CRATES) { + text = text.replace( + new RegExp( + '^(' + escapeRegExp(key) + '\\s*=\\s*\\{[^}\\n]*version\\s*=\\s*")[^"]+(".*)$', + 'gmu', + ), + (_, before, after) => before + '=' + runtimeVersion + after, + ); + } return text; } -function validateGeneratedOliphauntWasixReleaseArtifactCoverage(manifestText) { +function validateGeneratedOliphauntWasixReleaseArtifactCoverage(manifestText, runtimeVersion) { if (/=\s*\{[^}\n]*path\s*=/u.test(manifestText)) { fail('generated oliphaunt-wasix release source must not contain local path dependencies'); } - const toolchainVersions = canonicalWasixCargoToolchainVersions(root); + const toolchainVersions = { + ...canonicalWasixCargoToolchainVersions(root), + engine: runtimeVersion, + }; const toolchainFailures = validateWasixConsumerDependencyPins(Bun.TOML.parse(manifestText), { manifestPath: 'generated oliphaunt-wasix release source', toolchainVersions, @@ -108,7 +121,7 @@ export async function prepareOliphauntWasixReleaseSource(version) { if (generatedPackage.version !== version) { fail(`generated oliphaunt-wasix release source must keep SDK version ${version}`); } - validateGeneratedOliphauntWasixReleaseArtifactCoverage(rendered); + validateGeneratedOliphauntWasixReleaseArtifactCoverage(rendered, runtimeVersion); await fs.writeFile(cargoToml, rendered); stageReleaseNotices(stageDir, SOURCE_NOTICE_OPTIONS); assertReleaseNoticesInDirectory(stageDir, SOURCE_NOTICE_OPTIONS); diff --git a/src/wasix/sdks/rust/tools/test-aot.sh b/src/wasix/sdks/rust/tools/test-aot.sh index 8636d7184..f8bb51a39 100644 --- a/src/wasix/sdks/rust/tools/test-aot.sh +++ b/src/wasix/sdks/rust/tools/test-aot.sh @@ -26,13 +26,20 @@ fi # Compiling and running the SDK consumes the host's actual AOT carrier crate; # producer tasks only serialize/package/check payload bytes. -bash src/wasix/runtime/tools/runtime-smoke.sh core-smoke +if [ "$host" = x86_64-pc-windows-msvc ]; then + # V8 has different import, memory, and terminal handling from the LLVM hosts. + # Qualify the same full catalog lifecycle against this backend as on Linux. + bash src/wasix/runtime/tools/runtime-smoke.sh regression + tools/dev/bun.sh src/wasix/sdks/rust/tools/test-packaged-aot.mts +else + bash src/wasix/runtime/tools/runtime-smoke.sh core-smoke +fi -# The portable/Linux regression exercises every catalogued extension. Each host +# The Linux regression and Windows V8 lane exercise every catalogued extension. Each host # must also deserialize and execute machine code produced for that exact host, # including a side module and the split pg_dump/psql tool artifacts. Keep this -# bounded representative lane on all four AOT builders so cross-host coverage -# does not multiply the exhaustive 39-extension lifecycle suite by four. +# bounded representative lane on the LLVM builders so cross-host coverage +# does not repeat the exhaustive lifecycle suite on every LLVM host. proof_root="$root/target/wasix-target-aot-smoke" rm -rf "$proof_root" OLIPHAUNT_WASIX_GENERATED_ASSET_ROOT="$root/target/extensions/wasix/assets" \ diff --git a/src/wasix/sdks/rust/tools/test-packaged-aot.mts b/src/wasix/sdks/rust/tools/test-packaged-aot.mts new file mode 100644 index 000000000..89d4bde14 --- /dev/null +++ b/src/wasix/sdks/rust/tools/test-packaged-aot.mts @@ -0,0 +1,116 @@ +import assert from 'node:assert/strict'; +import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { preparePackagedCargoTestClosure } from '../../../../../tools/packaging/cargo-package-test-closure.mts'; +import { + packageAotSpec, + packageSpec, +} from '../../../../../tools/packaging/wasix-cargo-payload.mts'; +import { + currentProductVersionSync, + ROOT, +} from '../../../../../tools/release/release-artifact-targets.mts'; +import { packageEngine } from '../../../runtime/engine/package.mts'; +import { + postgresSourceFingerprint, + stageAotAssets, + stagePortableAssets, +} from '../../../runtime/tools/package-release-assets.mts'; +import { runtimeCorePayload } from '../../../runtime/tools/package_liboliphaunt_wasix_cargo_artifacts.mts'; +import { + AOT_PACKAGES, + AOT_TARGET_TRIPLES, + RUNTIME_PACKAGE, +} from '../../../runtime/tools/wasix-cargo-artifact-contract.mts'; + +assert.equal(process.platform, 'win32'); +const targetId = 'windows-x64-msvc'; +const target = AOT_TARGET_TRIPLES[targetId]; +const version = currentProductVersionSync('liboliphaunt-wasix'); +const scratch = mkdtempSync(path.join(os.tmpdir(), 'oliphaunt-wasix-installed-aot-')); +try { + const sourceRoot = path.join(scratch, 'source'); + const outputDir = path.join(scratch, 'packages'); + mkdirSync(outputDir); + const fingerprint = postgresSourceFingerprint(); + const portable = path.join(scratch, 'portable'); + const aot = path.join(scratch, 'aot'); + stagePortableAssets(path.join(ROOT, 'target/oliphaunt-wasix/assets'), portable, fingerprint); + stageAotAssets(path.join(ROOT, 'target/oliphaunt-wasix/aot', target), aot, target, fingerprint); + const packages = packageEngine({ sourceRoot, outputDir, version }); + for (const spec of [ + { + name: RUNTIME_PACKAGE, + kind: 'wasix-runtime', + target: 'portable', + templateDir: path.join(ROOT, 'src/wasix/runtime/crates/assets'), + payloadRoot: runtimeCorePayload(portable, scratch), + payloadDirName: 'payload', + }, + { + name: AOT_PACKAGES[targetId], + kind: 'wasix-aot', + target, + templateDir: path.join(ROOT, 'src/wasix/runtime/crates/aot', target), + payloadRoot: aot, + payloadDirName: 'artifacts', + }, + ]) { + const options = { version, sourceRoot, outputDir, cargoTargetDir: scratch }; + packages.push( + ...(spec.kind === 'wasix-aot' ? packageAotSpec(spec, options) : [packageSpec(spec, options)]), + ); + } + const sdkVersion = currentProductVersionSync('oliphaunt-wasix-rust'); + const queryVersion = currentProductVersionSync('oliphaunt-query'); + const consumer = path.join(scratch, 'consumer'); + const manifest = preparePackagedCargoTestClosure({ + cratePath: path.join( + ROOT, + `target/sdk-artifacts/oliphaunt-wasix-rust/oliphaunt-wasix-${sdkVersion}.crate`, + ), + scratch: consumer, + dependencyCrates: [ + ...packages.map((row) => row.cratePath), + path.join(ROOT, `target/sdk-artifacts/oliphaunt-query/oliphaunt-query-${queryVersion}.crate`), + ], + // Inactive platform and optional tool/ICU declarations still participate in + // Cargo resolution; the selected host's runtime and engine are real archives. + stubDependencyPrefixes: ['liboliphaunt-wasix-aot-', 'oliphaunt-wasix-tools'], + stubDependencies: ['oliphaunt-icu'], + }); + const env = Object.fromEntries( + Object.entries(process.env).filter(([name]) => !name.startsWith('OLIPHAUNT_')), + ); + env.CARGO_TARGET_DIR = path.join(scratch, 'target'); + function run(command: string[]) { + const result = Bun.spawnSync(command, { + cwd: consumer, + env, + stdout: 'inherit', + stderr: 'inherit', + timeout: 1_200_000, + }); + assert.equal(result.exitCode, 0, `packaged SDK AOT: ${command.join(' ')}`); + } + run(['cargo', '--config', 'net.offline=false', 'fetch', '--manifest-path', manifest]); + run([ + 'cargo', + 'nextest', + 'run', + '--manifest-path', + manifest, + '--locked', + '--offline', + '--no-default-features', + '--test', + 'runtime_smoke', + '-E', + 'test(direct_api_) | test(async_api_)', + '--no-tests=fail', + '--test-threads=1', + ]); +} finally { + rmSync(scratch, { recursive: true, force: true }); +} diff --git a/tools/ci/workflow-moon-transfers.test.mts b/tools/ci/workflow-moon-transfers.test.mts index 6214c3e74..7590b45b2 100644 --- a/tools/ci/workflow-moon-transfers.test.mts +++ b/tools/ci/workflow-moon-transfers.test.mts @@ -347,6 +347,47 @@ if (!process.env.OLIPHAUNT_TRANSFER_FIXTURE_PHASE) } }); +if (!process.env.OLIPHAUNT_TRANSFER_FIXTURE_PHASE) + test('WASIX AOT consumers reuse the same-run engine qualification', () => { + const host = Bun.YAML.parse( + readFileSync(path.join(ROOT, '.github/workflows/wasix-host.yml'), 'utf8'), + ); + const step = host.jobs.build.steps.find((step) => + step.run?.includes('run-planned-moon-job.sh liboliphaunt-wasix-aot'), + ); + const job = 'liboliphaunt-wasix-aot'; + for (const roots of [['liboliphaunt-wasix:runtime-aot'], CI_JOB_TARGETS[job]]) { + const result = Bun.spawnSync( + ['bun', '.github/scripts/resolve-planned-moon-execution.mts', job], + { + cwd: ROOT, + env: { + ...process.env, + ...step.env, + OLIPHAUNT_CI_JOB_TARGETS_JSON: JSON.stringify({ [job]: roots }), + }, + }, + ); + assert.equal(result.exitCode, 0, result.stderr.toString()); + const execution = result.stdout + .toString() + .trim() + .split('\n') + .map((line) => line.split('\t')); + const executed = execution + .filter(([kind]) => kind !== 'transferred') + .flatMap(([, targets]) => targets.split(' ')); + for (const root of roots) assert(executed.includes(root), `${root} must still execute`); + for (const task of ['liboliphaunt-wasix:engine-test', 'liboliphaunt-wasix:engine-build']) + assert(!executed.includes(task), `${task} must consume the producer's qualification`); + assert( + execution.some( + ([kind, target]) => kind === 'transferred' && target === 'liboliphaunt-wasix:engine-test', + ), + ); + } + }); + if (!process.env.OLIPHAUNT_TRANSFER_FIXTURE_PHASE) test('SDK runtime suites execute in artifact-consuming jobs without rebuilding their producers', () => { for (const [job, roots, forbidden] of [ @@ -365,7 +406,7 @@ if (!process.env.OLIPHAUNT_TRANSFER_FIXTURE_PHASE) ], [ 'wasix-release-regression', - ['oliphaunt-wasix-rust:test-integration'], + ['oliphaunt-wasix-rust:test-integration', 'oliphaunt-wasix-rust:test-regression'], [ 'liboliphaunt-wasix:compiler-output', 'liboliphaunt-wasix:runtime-aot', @@ -389,6 +430,12 @@ if (!process.env.OLIPHAUNT_TRANSFER_FIXTURE_PHASE) ); assert(execution.targets.includes(root)); const executed = [...execution.targets, ...execution.localDependencies]; + if (job === 'wasix-release-regression') { + assert( + executed.includes('liboliphaunt-wasix:engine-sources'), + `${root} must prepare patched engine sources despite the transferred AOT boundary`, + ); + } for (const target of forbidden) assert(!executed.includes(target), `${root} rebuilds ${target}`); assert.equal( diff --git a/tools/packaging/cargo-package-test-closure.mts b/tools/packaging/cargo-package-test-closure.mts index 67d1349b3..48bcdc051 100644 --- a/tools/packaging/cargo-package-test-closure.mts +++ b/tools/packaging/cargo-package-test-closure.mts @@ -13,6 +13,7 @@ import { import path from 'node:path'; import { extractPortableArchiveTree, readPortableArchiveEntries } from './portable-archive.mts'; +import { packagedCargoManifestText } from './cargo-source-package.mts'; const TOOL = 'cargo-package-test-closure.mts'; const CARGO_PACKAGE_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u; @@ -172,6 +173,44 @@ function pathDependencyPatches(manifests, scratch, packagedManifests, packagedNa const patches = new Map(); const sourceDirectories = new Map(); const packagedDependencies = packagedManifests.flatMap(dependencyRows); + function stageSource(directory, name) { + if (packagedNames.has(name)) return; + const realSource = realpathSync(directory); + const previousSource = sourceDirectories.get(name); + if (previousSource !== undefined) { + if (previousSource !== realSource) { + throw error( + `${name} has conflicting path-dependency sources: ${previousSource} and ${realSource}`, + ); + } + return; + } + const file = path.join(realSource, 'Cargo.toml'); + const manifest = parseManifest(file, `local patch for ${name}`); + const identity = packageIdentity(manifest, file); + if (identity.name !== name) { + throw error(`local patch ${file} does not declare ${name}`); + } + if ( + packagedDependencies.some( + (row) => row.name === name && exactVersion(row.version, name) !== identity.version, + ) + ) + return; + sourceDirectories.set(name, realSource); + const staged = path.join(scratch, 'path-dependency-sources', name); + copyCleanDependencySource(realSource, staged); + writeFileSync( + path.join(staged, 'Cargo.toml'), + packagedCargoManifestText(readFileSync(file, 'utf8')), + ); + addPatch(patches, name, staged, file); + // Cargo resolves the entire manifest closure, including inactive target and + // optional build dependencies. Preserve that closure without sibling paths. + for (const dependency of dependencyRows(manifest).filter((row) => row.path !== null)) { + stageSource(path.resolve(realSource, dependency.path), dependency.name); + } + } for (const manifestFile of manifests) { const resolvedManifest = path.resolve(manifestFile); const manifest = parseManifest(resolvedManifest, 'path-dependency source manifest'); @@ -200,17 +239,7 @@ function pathDependencyPatches(manifests, scratch, packagedManifests, packagedNa // source hint is usable only for that version; otherwise Cargo fetches // the declared published dependency instead of substituting workspace code. if (!packagedVersions.has(local.version)) continue; - const realSource = realpathSync(directory); - const previousSource = sourceDirectories.get(dependency.name); - if (previousSource !== undefined && previousSource !== realSource) { - throw error( - `${dependency.name} has conflicting path-dependency sources: ${previousSource} and ${realSource}`, - ); - } - sourceDirectories.set(dependency.name, realSource); - const staged = path.join(scratch, 'path-dependency-sources', dependency.name); - if (!patches.has(dependency.name)) copyCleanDependencySource(directory, staged); - addPatch(patches, dependency.name, staged, resolvedManifest); + stageSource(directory, dependency.name); } } return patches; diff --git a/tools/packaging/cargo-package-test-closure.test.mts b/tools/packaging/cargo-package-test-closure.test.mts index 5f91c5b9a..453d0f9cf 100644 --- a/tools/packaging/cargo-package-test-closure.test.mts +++ b/tools/packaging/cargo-package-test-closure.test.mts @@ -1,5 +1,12 @@ import assert from 'node:assert/strict'; -import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { + appendFileSync, + mkdtempSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; @@ -140,6 +147,73 @@ test('package qualification retains exact published pins after a local dependenc } }); +test('stages sibling, optional build, and cyclic path dependencies as a closed Cargo graph', (t) => { + const root = fixture(t, 'cargo-closure-transitive'); + const cratePath = closureCrate(root); + const source = path.join(root, 'engine'); + for (const [directory, name, dependencies] of [ + [ + 'carrier', + 'carrier', + '[features]\nneeded = []\n[dependencies]\npeer = { package = "renamed-peer", version = "*", path = "../peer" }\n', + ], + [ + 'peer', + 'renamed-peer', + '[features]\nunused = ["dep:build-peer"]\n[build-dependencies]\nbuild-peer = { version = "*", path = "../build-peer", optional = true }\n', + ], + [ + 'build-peer', + 'build-peer', + '[dependencies]\ncarrier = { version = "*", path = "../carrier", optional = true }\n', + ], + ]) { + const directoryPath = path.join(source, directory); + writePackage(directoryPath, name); + appendFileSync(path.join(directoryPath, 'Cargo.toml'), dependencies); + } + const hint = path.join(root, 'Cargo.toml'); + writeFileSync(hint, '[dependencies]\ncarrier = { version = "*", path = "engine/carrier" }\n'); + const scratch = path.join(root, 'consumer'); + const manifest = preparePackagedCargoTestClosure({ + cratePath, + scratch, + pathDependencyManifests: [hint], + }); + rmSync(source, { recursive: true }); + const result = Bun.spawnSync( + [ + 'cargo', + 'metadata', + '--offline', + '--features', + 'carrier', + '--format-version', + '1', + '--manifest-path', + manifest, + ], + { cwd: scratch }, + ); + assert.equal(result.exitCode, 0, result.stderr.toString()); + const packages = JSON.parse(result.stdout.toString()).packages; + assert.deepEqual(packages.map((row) => row.name).sort(), [ + 'carrier', + 'closure-fixture', + 'renamed-peer', + ]); + for (const row of packages) { + assert.ok(row.manifest_path.startsWith(scratch)); + assert.ok(row.dependencies.every((dependency) => !dependency.path)); + } + const config = Bun.TOML.parse(readFileSync(path.join(scratch, '.cargo/config.toml'), 'utf8')); + assert.deepEqual(Object.keys(config.patch['crates-io']), [ + 'build-peer', + 'carrier', + 'renamed-peer', + ]); +}); + test('rejects unsafe packaged names', (t) => { const root = fixture(t, 'cargo-closure-unsafe'); const stage = path.join(root, 'stage'); diff --git a/tools/packaging/cargo-source-package.mts b/tools/packaging/cargo-source-package.mts index f8527149a..4bca74237 100644 --- a/tools/packaging/cargo-source-package.mts +++ b/tools/packaging/cargo-source-package.mts @@ -78,6 +78,16 @@ export function packagedCargoManifestText(source) { text = text .replace(/(\{\s*)path\s*=\s*"[^"]+",\s*/gu, '$1') .replace(/,\s*path\s*=\s*"[^"]+"/gu, ''); + let dependencyTable = false; + text = text + .split('\n') + .filter((line) => { + const section = line.match(/^\s*\[{1,2}([^\]]+)\]{1,2}\s*$/u); + if (section) + dependencyTable = /(?:^|\.)(?:build-|dev-)?dependencies(?:\.|$)/u.test(section[1]); + return !(dependencyTable && /^\s*path\s*=/u.test(line)); + }) + .join('\n'); if (!text.includes('\n[workspace]')) { text = `${text.trimEnd()}\n\n[workspace]\n`; } @@ -120,8 +130,15 @@ function portablePackagePath(value, manifest, { fail, rel }) { } } -function parseCargoPackageFiles(text, manifest, { fail, rel }) { - const files = text.split(/\r?\n/u).filter(Boolean); +export function parseCargoPackageFiles( + text, + manifest, + { fail = null, rel = String, platform = process.platform } = {}, +) { + const files = text + .split(/\r?\n/u) + .filter(Boolean) + .map((file) => (platform === 'win32' ? file.replaceAll('\\', '/') : file)); if (files.length === 0) { abort(fail, `cargo package --list returned no files for ${rel(manifest)}`); } @@ -405,12 +422,12 @@ if (import.meta.main) { const [phase, stateFile, ...args] = process.argv.slice(2); if (phase === 'prepare' && args.length === 3) { const [manifest, outputDir, listing] = args; - const state = prepareCargoPackageSource( - manifest, - outputDir, - parseCargoPackageFiles(readFileSync(listing, 'utf8'), manifest, { fail: null, rel: String }), - { noticeProfile: process.env.OLIPHAUNT_CARGO_NOTICE_PROFILE ?? null }, - ); + const files = parseCargoPackageFiles(readFileSync(listing, 'utf8'), manifest); + const state = prepareCargoPackageSource(manifest, outputDir, files, { + noticeProfile: process.env.OLIPHAUNT_CARGO_NOTICE_PROFILE ?? null, + }); + // The caller retains this listing for cross-platform archive validation. + writeFileSync(listing, files.join('\n') + '\n'); writeFileSync(stateFile, JSON.stringify(state)); console.log(state.stagedManifest); } else if (phase === 'finish' && args.length === 1) { diff --git a/tools/packaging/cargo-source-package.test.mts b/tools/packaging/cargo-source-package.test.mts index 078010e97..ad57bb493 100644 --- a/tools/packaging/cargo-source-package.test.mts +++ b/tools/packaging/cargo-source-package.test.mts @@ -20,9 +20,12 @@ import { createDeterministicTar, fitCargoPayloadParts, packageGeneratedCargoSource, + packagedCargoManifestText, + parseCargoPackageFiles, } from './cargo-source-package.mts'; import { readPortableArchiveEntries } from './portable-archive.mts'; import { assertReleaseNoticesInArchive } from './release-notices.mts'; +import { requireCrateMatchesCargoListing } from './release-carrier.mts'; function fixture(t, name) { const root = mkdtempSync(path.join(os.tmpdir(), `oliphaunt-${name}-`)); @@ -85,6 +88,16 @@ if (['prepare', 'verify'].includes(process.argv[2])) { const first = readFileSync(path.join(root, 'first.path'), 'utf8').trim(); const second = readFileSync(path.join(root, 'second.path'), 'utf8').trim(); assert.deepEqual(readFileSync(first), readFileSync(second)); + for (const output of ['first', 'second']) { + const listing = path.join(root, `${output}-files.txt`); + assert.equal(readFileSync(listing, 'utf8').includes('\\'), false); + requireCrateMatchesCargoListing( + readFileSync(path.join(root, `${output}.path`), 'utf8').trim(), + listing, + 'selected-package', + '0.1.0', + ); + } assert.equal( readFileSync(first).subarray(0, 10).toString('hex'), '1f8b0800000000000003', @@ -122,6 +135,18 @@ if (['prepare', 'verify'].includes(process.argv[2])) { process.exit(0); } +test('published manifests remove dependency paths while retaining library and build paths', () => { + const text = packagedCargoManifestText( + '[package]\nname = "example"\nversion = "1.0.0"\nbuild = "build.rs"\n[lib]\npath = "upstream/src/lib.rs"\n[dependencies.peer]\npackage = "private-peer"\npath = "../peer"\nversion = "=1.0.0"\n[target.\'cfg(windows)\'.build-dependencies]\nengine = { path = "../engine", version = "=1.0.0" }\n[[bin]]\nname = "example-tool"\npath = "src/tool.rs"\n', + ); + const cargo = Bun.TOML.parse(text); + assert.equal(cargo.lib.path, 'upstream/src/lib.rs'); + assert.equal(cargo.package.build, 'build.rs'); + assert.equal(cargo.bin[0].path, 'src/tool.rs'); + assert.equal(cargo.dependencies.peer.path, undefined); + assert.equal(cargo.target['cfg(windows)']['build-dependencies'].engine.path, undefined); +}); + test('payload splitting follows compressed crate size and preserves every byte', (t) => { const { root } = fixture(t, 'compressed-parts'); for (const payload of [Buffer.alloc(20 * 1024 * 1024, 42), randomBytes(20 * 1024 * 1024)]) { @@ -219,6 +244,47 @@ test('rejects absolute, parent, backslash, and non-portable Cargo member paths', } }); +test('normalizes native Cargo listings before validating portable member paths', () => { + const expected = ['Cargo.toml', 'src/async_api.rs', 'tests/runtime_smoke.rs']; + const manifest = 'fixture/Cargo.toml'; + assert.deepEqual(parseCargoPackageFiles(expected.join('\n'), manifest), expected); + assert.deepEqual( + parseCargoPackageFiles( + 'Cargo.toml\r\nsrc\\async_api.rs\r\ntests\\runtime_smoke.rs\r\n', + manifest, + { + platform: 'win32', + }, + ), + expected, + ); + for (const candidate of [ + '..\\escape', + 'src\\..\\escape', + '\\absolute', + 'C:\\absolute', + '\\\\server\\share', + 'src\\\\empty', + 'bad:name', + ]) { + assert.throws( + () => parseCargoPackageFiles(`Cargo.toml\n${candidate}\n`, manifest, { platform: 'win32' }), + /Cargo package path/u, + ); + } + assert.throws( + () => + parseCargoPackageFiles('Cargo.toml\nsrc\\lib.rs\nsrc/lib.rs\n', manifest, { + platform: 'win32', + }), + /repeated src\/lib.rs/u, + ); + assert.throws( + () => parseCargoPackageFiles('Cargo.toml\nsrc\\lib.rs\n', manifest, { platform: 'linux' }), + /unsafe Cargo package path/u, + ); +}); + test('generated Cargo carriers match Cargo-selected source bytes and reject links', (t) => { const { root, source } = fixture(t, 'generated-cargo'); writeFileSync(path.join(source, 'Cargo.toml'), manifest('generated-carrier')); diff --git a/tools/packaging/cargo-source-package.test.sh b/tools/packaging/cargo-source-package.test.sh index 39de67f04..ea3ac378e 100644 --- a/tools/packaging/cargo-source-package.test.sh +++ b/tools/packaging/cargo-source-package.test.sh @@ -6,7 +6,7 @@ scratch=$(mktemp -d "${TMPDIR:-/tmp}/oliphaunt-cargo-source-XXXXXX") trap 'rm -rf "$scratch"' EXIT bun tools/packaging/cargo-source-package.test.mts prepare "$scratch" for output in first second; do - bash tools/packaging/package-cargo-source.sh "$scratch/source/Cargo.toml" "$scratch/$output" > "$scratch/$output.path" + bash tools/packaging/package-cargo-source.sh "$scratch/source/Cargo.toml" "$scratch/$output" "$scratch/$output-files.txt" > "$scratch/$output.path" done bash tools/packaging/package-cargo-source.sh "$scratch/generated/Cargo.toml" "$scratch/cargo-generated" > "$scratch/generated.path" OLIPHAUNT_CARGO_NOTICE_PROFILE=source-sdk bash tools/packaging/package-cargo-source.sh "$scratch/source/Cargo.toml" "$scratch/notices" > "$scratch/notices.path" diff --git a/tools/packaging/moon.yml b/tools/packaging/moon.yml index 9801426f8..3194f3922 100644 --- a/tools/packaging/moon.yml +++ b/tools/packaging/moon.yml @@ -36,7 +36,8 @@ tasks: - /src/extensions/tools/extension-upstream-licenses.mts - /src/extensions/external/**/upstream-license-data.json - "@group(upstream-licenses)" - - "/src/{native,wasix}/runtime/THIRD_PARTY_NOTICES.md" + - "/src/native/runtime/THIRD_PARTY_NOTICES.md" + - "/src/wasix/sdks/rust/THIRD_PARTY_NOTICES.md" - "/src/third-party/{icu,openssl}/source.toml" - "**/*.mts" - "**/*.sh" diff --git a/tools/packaging/release-notices.mts b/tools/packaging/release-notices.mts index a59a9c33e..a1f4a5399 100644 --- a/tools/packaging/release-notices.mts +++ b/tools/packaging/release-notices.mts @@ -21,7 +21,13 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..') const PREFIX = 'release-notices.mts'; export const RELEASE_NOTICE_PRODUCTS = Object.freeze(['native', 'wasix']); -export const RELEASE_LICENSE_COMPONENTS = Object.freeze(['postgresql', 'icu', 'openssl']); +export const RELEASE_LICENSE_COMPONENTS = Object.freeze([ + 'postgresql', + 'icu', + 'openssl', + 'wasmer', + 'v8', +]); export const RELEASE_CARRIER_PROFILES = Object.freeze({ 'source-sdk': Object.freeze({ products: Object.freeze([]), components: Object.freeze([]) }), 'code-facade': Object.freeze({ products: Object.freeze([]), components: Object.freeze([]) }), @@ -31,7 +37,19 @@ export const RELEASE_CARRIER_PROFILES = Object.freeze({ }), 'wasix-napi-addon': Object.freeze({ products: Object.freeze(['wasix']), - components: Object.freeze(['postgresql', 'icu', 'openssl']), + components: Object.freeze(['postgresql', 'icu', 'openssl', 'wasmer']), + }), + 'wasix-engine-source': Object.freeze({ + products: Object.freeze([]), + components: Object.freeze(['wasmer']), + }), + 'wasix-engine-windows': Object.freeze({ + products: Object.freeze([]), + components: Object.freeze(['v8']), + }), + 'wasix-napi-addon-windows': Object.freeze({ + products: Object.freeze(['wasix']), + components: Object.freeze(['postgresql', 'icu', 'openssl', 'wasmer', 'v8']), }), broker: Object.freeze({ products: Object.freeze([]), components: Object.freeze([]) }), 'native-runtime': Object.freeze({ @@ -113,6 +131,36 @@ const PRODUCT_NOTICE_ROWS = Object.freeze({ }); const LICENSE_COMPONENT_ROWS = Object.freeze({ + wasmer: Object.freeze({ + id: 'wasmer', + spdx: 'MIT', + name: 'Wasmer / WASIX License', + member: 'THIRD_PARTY_LICENSES/Wasmer-LICENSE', + source: path.join(ROOT, 'src/wasix/runtime/engine/licenses/Wasmer-LICENSE'), + sourceManifest: path.join(ROOT, 'src/wasix/runtime/engine/source.toml'), + archiveSourceKey: 'wasmer', + sourceVersion: '7.5.0', + sha256: '76dc7d305458d07478bc62669fe53dbfd3b94b95c5e00fbb45af1f492cbd7284', + sourceUrl: 'https://static.crates.io/crates/wasmer/wasmer-7.5.0.crate', + sourceIdentity: 'sha256:ddfde3972691f1082774c71c42e21add1d1838cb6482a6921a0ba8a993e0046a', + licenseUrl: + 'https://github.com/wasmerio/wasmer/blob/82ff099e082da586b38af79986abbb8b677f8baf/LICENSE', + }), + v8: Object.freeze({ + id: 'v8', + spdx: 'BSD-3-Clause AND BSD-2-Clause AND Apache-2.0 AND Unicode-3.0 AND SunPro AND CC0-1.0 AND Zlib AND MIT AND Python-2.0', + name: 'V8 / wee8 Windows runtime licenses', + member: 'THIRD_PARTY_LICENSES/V8-LICENSES.txt', + source: path.join(ROOT, 'src/wasix/runtime/engine/licenses/V8-LICENSES.txt'), + sourceManifest: path.join(ROOT, 'src/wasix/runtime/engine/source.toml'), + archiveSourceKey: 'wee8-windows', + sourceVersion: '11.9.7', + sha256: '11b776696544c9ed9d11dc02b0f929cee703cfdb68d4895aaf2af1eb78e0116b', + sourceUrl: + 'https://github.com/wasmerio/wee8-custom-builds/releases/download/11.9.7/v8-windows-amd64.tar.xz', + sourceIdentity: 'sha256:2aee8b6c3e8cecae2ce0325ac01b9bcaea4bef49e8f2aac599e1729d60c17285', + licenseUrl: 'https://github.com/v8/v8/blob/b0a55a7dad7f536cce1f9aaddba89894c8533946/LICENSE', + }), postgresql: Object.freeze({ id: 'postgresql', spdx: 'PostgreSQL', @@ -269,16 +317,14 @@ function validateRuntimeLicenseSource(row) { `runtime license source manifest ${row.sourceManifest} cannot be parsed: ${cause.message}`, ); } - if (row.id === 'postgresql') { - const source = manifest?.postgresql; + if (row.id === 'postgresql' || row.archiveSourceKey) { + const source = manifest?.[row.archiveSourceKey ?? 'postgresql']; if ( source?.version !== row.sourceVersion || source?.url !== row.sourceUrl || `sha256:${source?.sha256}` !== row.sourceIdentity ) { - throw new Error( - `PostgreSQL runtime license snapshot no longer matches ${row.sourceManifest}`, - ); + throw new Error(`${row.id} runtime license snapshot no longer matches ${row.sourceManifest}`); } } else { if ( @@ -471,7 +517,7 @@ export function releasePackageLicense({ components = [], includeOliphaunt = true } entries.push(...releaseLicenseComponents(components)); return Object.freeze({ - spdx: entries.map((entry) => entry.spdx).join(' AND '), + spdx: [...new Set(entries.flatMap((entry) => entry.spdx.split(' AND ')))].join(' AND '), entries: Object.freeze(entries), }); } diff --git a/tools/packaging/wasix-cargo-payload.mts b/tools/packaging/wasix-cargo-payload.mts index ebe760ff1..f96e896df 100644 --- a/tools/packaging/wasix-cargo-payload.mts +++ b/tools/packaging/wasix-cargo-payload.mts @@ -1,8 +1,19 @@ import { createHash } from 'node:crypto'; -import { copyFileSync, cpSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; +import { + copyFileSync, + cpSync, + existsSync, + lstatSync, + mkdirSync, + readdirSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { packageGeneratedCargoSource } from './cargo-source-package.mts'; +import { fitCargoPayloadParts, packageGeneratedCargoSource } from './cargo-source-package.mts'; import { assertReleaseNoticesInArchive, assertReleaseNoticesInDirectory, @@ -120,7 +131,15 @@ export function packageSpec( { version, sourceRoot, outputDir, cargoTargetDir, transformManifest }, ) { const crateDir = copyPackageSource(spec, sourceRoot, version, transformManifest); - const cratePath = cargoPackage(crateDir, cargoTargetDir); + return freezeSpec(spec, crateDir, { version, outputDir, cargoTargetDir }); +} + +function freezeSpec( + spec, + crateDir, + { version, outputDir, cargoTargetDir }, + cratePath = cargoPackage(crateDir, cargoTargetDir), +) { validateCrateSize(cratePath); const output = path.join(outputDir, path.basename(cratePath)); copyFileSync(cratePath, output); @@ -139,3 +158,116 @@ export function packageSpec( sha256: sha256File(output), }; } + +export function aotPayloadPartParent(name) { + return ( + name.match( + /^(liboliphaunt-wasix-aot-x86_64-pc-windows-msvc|oliphaunt-wasix-tools-aot-x86_64-pc-windows-msvc)-part-(?!000)[0-9]{3}$/u, + )?.[1] ?? null + ); +} + +/** Preserve the AOT facade; Cargo privately reconstructs oversized V8 payloads. */ +export function packageAotSpec(spec, options) { + if (spec.target !== 'x86_64-pc-windows-msvc') return [packageSpec(spec, options)]; + const { version, sourceRoot, cargoTargetDir, transformManifest } = options; + const crateDir = copyPackageSource(spec, sourceRoot, version, transformManifest); + const probe = (directory) => + packageGeneratedCargoSource( + path.join(directory, 'Cargo.toml'), + path.join(cargoTargetDir, 'size-probe'), + { packageSizeLimitBytes: Number.MAX_SAFE_INTEGER }, + ); + const candidate = probe(crateDir); + if (statSync(candidate).size <= CRATES_IO_MAX_BYTES) { + return [freezeSpec(spec, crateDir, options, candidate)]; + } + const payloadRoot = path.join(crateDir, spec.payloadDirName); + const files = readdirSync(payloadRoot, { recursive: true }) + .sort() + .flatMap((relative) => { + const file = path.join(payloadRoot, relative); + const metadata = lstatSync(file); + if (metadata.isDirectory()) return []; + if (!metadata.isFile()) fail(`AOT payload must contain regular files: ${file}`); + return [{ relative: relative.split(path.sep).join('/'), bytes: readFileSync(file) }]; + }); + const payload = Buffer.concat(files.map((file) => file.bytes)); + const profile = noticeProfileForSpec(spec); + const parts = fitCargoPayloadParts( + (budget) => { + const result = []; + for (let offset = 0; offset < payload.length; offset += budget) { + if (result.length >= 999) fail('AOT payload exceeds supported part count'); + const name = `${spec.name}-part-${String(result.length + 1).padStart(3, '0')}`; + const directory = path.join(sourceRoot, name); + mkdirSync(path.join(directory, 'src'), { recursive: true }); + writeFileSync( + path.join(directory, 'Cargo.toml'), + `[package]\nname = ${JSON.stringify(name)}\nversion = ${JSON.stringify(version)}\nedition = "2024"\nrust-version = "1.96"\ndescription = "Internal Oliphaunt V8 AOT payload part"\nrepository = "https://github.com/f0rr0/oliphaunt"\nlicense = ${JSON.stringify(releaseProfilePackageLicense(profile).spdx)}\n\n[workspace]\n`, + ); + writeFileSync( + path.join(directory, 'payload.part'), + payload.subarray(offset, offset + budget), + ); + writeFileSync( + path.join(directory, 'src/lib.rs'), + 'pub const PAYLOAD: &[u8] = include_bytes!("../payload.part");\n', + ); + stageReleaseNotices(directory, { profile }); + result.push({ ...spec, name, directory }); + } + return result; + }, + (part) => probe(part.directory), + ); + const manifest = path.join(crateDir, 'Cargo.toml'); + writeFileSync( + manifest, + readFileSync(manifest, 'utf8').replace( + '[build-dependencies]', + `[build-dependencies]\n${parts.map(({ name }) => `${name} = "=${version}"`).join('\n')}`, + ), + ); + let offset = 0; + const writes = files + .map(({ relative, bytes }) => { + const begin = offset; + offset += bytes.length; + return ` let file = root.join(${JSON.stringify(relative)});\n fs::create_dir_all(file.parent().unwrap()).expect("create AOT directory");\n fs::write(file, &payload[${begin}..${offset}]).expect("write bundled AOT artifact");`; + }) + .join('\n'); + writeFileSync( + path.join(crateDir, 'build.rs'), + `use std::{env, fs, path::PathBuf}; +use sha2::{Digest, Sha256}; +mod aot { + const PACKAGE_LOCAL: bool = true; + include!("build-support.rs"); + pub fn generate() { main(); } +} +fn main() { + let parts: &[&[u8]] = &[${parts.map(({ name }) => `${name.replaceAll('-', '_')}::PAYLOAD`).join(', ')}]; + let payload = parts.concat(); + assert_eq!(format!("{:x}", Sha256::digest(&payload)), ${JSON.stringify(createHash('sha256').update(payload).digest('hex'))}, "bundled AOT payload digest"); + let root = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR")).join("artifacts"); + if root.exists() { fs::remove_dir_all(&root).expect("remove stale AOT payload"); } +${writes} + println!("cargo::rerun-if-changed=build.rs"); + aot::generate(); +} +`, + ); + rmSync(payloadRoot, { recursive: true }); + return [ + ...parts.map((part) => + freezeSpec( + part, + part.directory, + options, + path.join(cargoTargetDir, 'size-probe', `${part.name}-${version}.crate`), + ), + ), + freezeSpec(spec, crateDir, options), + ]; +} diff --git a/tools/packaging/wasix-cargo-payload.test.mts b/tools/packaging/wasix-cargo-payload.test.mts index aa079305c..2eb0fa24b 100644 --- a/tools/packaging/wasix-cargo-payload.test.mts +++ b/tools/packaging/wasix-cargo-payload.test.mts @@ -1,3 +1,5 @@ +import assert from 'node:assert/strict'; +import { createHash, randomBytes } from 'node:crypto'; import { cpSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { @@ -6,7 +8,8 @@ import { } from '../../src/database-resources/contracts/native-manifest.mts'; import { createDeterministicTar } from './cargo-source-package.mts'; import { extractPortableArchiveTree, releaseZstdCompressSync } from './portable-archive.mts'; -import { packageSpec } from './wasix-cargo-payload.mts'; +import { packageAotSpec, packageSpec } from './wasix-cargo-payload.mts'; +import { preparePackagedCargoTestClosure } from './cargo-package-test-closure.mts'; import { WASIX_AOT_ENGINE } from '../../src/wasix/runtime/tools/wasix-aot-manifest.mts'; const root = path.resolve(import.meta.dir, '../..'); @@ -149,3 +152,80 @@ for (const [id, template, kind, payloadDirName, ancestorAssets, variable, expres rows.push([crate, payloadDirName, variable, external].join('\t')); } writeFileSync(path.join(scratch, 'cases.tsv'), `${rows.join('\n')}\n`); + +const splitRows = []; +for (const [owner, kind, artifact] of [ + ['runtime', 'wasix-aot', 'runtime:oliphaunt'], + ['postgres-tools', 'wasix-tools-aot', 'tool:pg_dump'], +]) { + const target = 'x86_64-pc-windows-msvc'; + const templateDir = path.join(root, `src/wasix/${owner}/crates/aot/${target}`); + const { name, version } = Bun.TOML.parse( + readFileSync(path.join(templateDir, 'Cargo.toml'), 'utf8'), + ).package; + const base = path.join(scratch, `split-${owner}`); + const payloadRoot = path.join(base, 'payload'); + mkdirSync(payloadRoot, { recursive: true }); + const bytes = randomBytes(11 * 1024 * 1024); + const file = owner === 'runtime' ? 'oliphaunt-v8.bin.zst' : 'pg_dump-v8.bin.zst'; + writeFileSync(path.join(payloadRoot, file), bytes); + writeFileSync( + path.join(payloadRoot, 'manifest.json'), + JSON.stringify({ + engine: 'v8', + artifacts: [{ name: artifact, path: file }], + }), + ); + const spec = { name, kind, target, templateDir, payloadRoot, payloadDirName: 'artifacts' }; + const options = { + version, + sourceRoot: path.join(base, 'source'), + outputDir: path.join(base, 'packages'), + cargoTargetDir: path.join(base, 'package-target'), + }; + mkdirSync(options.outputDir); + assert.throws( + () => packageSpec(spec, { ...options, sourceRoot: path.join(base, 'unsplit') }), + /10 MiB/u, + ); + const packages = packageAotSpec(spec, options); + assert.equal(packages.length, 3); + assert(packages.every(({ size }) => size < 10 * 1024 * 1024)); + assert.deepEqual( + packages.map(({ name }) => name), + [`${name}-part-001`, `${name}-part-002`, name], + ); + const parent = packages.at(-1); + const buildDependencies = Bun.TOML.parse(readFileSync(parent.manifestPath, 'utf8'))[ + 'build-dependencies' + ]; + for (const part of packages.slice(0, -1)) + assert.equal(buildDependencies[part.name], `=${version}`); + const manifest = preparePackagedCargoTestClosure({ + cratePath: parent.cratePath, + dependencyCrates: packages.slice(0, -1).map(({ cratePath }) => cratePath), + scratch: path.join(base, 'consumer'), + }); + const crate = path.dirname(manifest); + mkdirSync(path.join(crate, 'examples')); + writeFileSync(manifest, readFileSync(manifest, 'utf8') + '\n[dev-dependencies]\nsha2 = "0.10"\n'); + writeFileSync( + path.join(crate, 'examples/probe.rs'), + `use sha2::{Digest, Sha256}; +fn main() { + let bytes = ${name.replaceAll('-', '_')}::artifact_bytes(${JSON.stringify(artifact)}).unwrap(); + assert_eq!(format!("{:x}", Sha256::digest(bytes)), ${JSON.stringify(createHash('sha256').update(bytes).digest('hex'))}); + assert!(${name.replaceAll('-', '_')}::HAS_EMBEDDED_AOT); + assert_eq!(${name.replaceAll('-', '_')}::ENGINE, "v8"); +} +`, + ); + const part = path.join( + base, + 'consumer/dependencies', + `${packages[0].name}-${version}`, + 'payload.part', + ); + splitRows.push([crate, part].join('\t')); +} +writeFileSync(path.join(scratch, 'split-cases.tsv'), `${splitRows.join('\n')}\n`); diff --git a/tools/packaging/wasix-cargo-payload.test.sh b/tools/packaging/wasix-cargo-payload.test.sh index 96fec9862..fbfce578f 100644 --- a/tools/packaging/wasix-cargo-payload.test.sh +++ b/tools/packaging/wasix-cargo-payload.test.sh @@ -50,4 +50,27 @@ while IFS=$'\t' read -r crate payload variable external; do CARGO_TARGET_DIR="$scratch/cargo-target" cargo check --locked --offline --quiet \ --manifest-path "$crate/Cargo.toml" --lib done < "$scratch/cases.tsv" +while IFS=$'\t' read -r crate part; do + ( + cd "$crate" + cargo --config net.offline=false fetch --manifest-path "$crate/Cargo.toml" + CARGO_TARGET_DIR="$scratch/cargo-target" cargo run --locked --offline --quiet \ + --manifest-path "$crate/Cargo.toml" --example probe + CARGO_TARGET_DIR="$scratch/cargo-target" cargo run --locked --offline --quiet \ + --manifest-path "$crate/Cargo.toml" --example probe --message-format=json > "$scratch/fresh.json" + if rg -q '"fresh":false' "$scratch/fresh.json"; then + echo "Published V8 carrier rebuilt without input changes: $crate" >&2 + exit 1 + fi + cp "$part" "$part.saved" + printf 'corrupt' >> "$part" + if CARGO_TARGET_DIR="$scratch/cargo-target" cargo check --locked --offline --quiet \ + --manifest-path "$crate/Cargo.toml" --lib > "$scratch/corrupt-part.log" 2>&1; then + echo "Published V8 carrier accepted a corrupt AOT payload part: $crate" >&2 + exit 1 + fi + rg -q 'bundled AOT payload digest' "$scratch/corrupt-part.log" + mv "$part.saved" "$part" + ) +done < "$scratch/split-cases.tsv" printf 'WASIX extracted Cargo carrier payload isolation passed\n' diff --git a/tools/release/artifact-target-matrix.mts b/tools/release/artifact-target-matrix.mts index 7e9dc33a7..ffda2f2d8 100644 --- a/tools/release/artifact-target-matrix.mts +++ b/tools/release/artifact-target-matrix.mts @@ -296,6 +296,9 @@ function wasixHostTargetMatrixRow(target) { if (!target.triple) { fail(PREFIX, `${target.id} must declare triple`); } + if (target.kind === 'wasix-aot-runtime' && target.target === 'windows-x64-msvc') { + return { os: target.runner, target: target.triple, target_id: target.target }; + } if (!target.llvmUrl) { fail(PREFIX, `${target.id} must declare llvm_url`); } diff --git a/tools/release/check_artifact_targets.mts b/tools/release/check_artifact_targets.mts index c3bb39123..95bfeb8ca 100644 --- a/tools/release/check_artifact_targets.mts +++ b/tools/release/check_artifact_targets.mts @@ -210,6 +210,13 @@ export function validateMatrixCoverage(targets, extensions, matrices) { for (const row of matrices.wasixAot.include) { const target = wasixAotTargets.get(row.target_id); invariant(target !== undefined, `WASIX AOT CI matrix has unknown target ${row.target_id}`); + if (row.target_id === 'windows-x64-msvc') { + invariant( + row.llvm_url === undefined && row.llvm_sha256 === undefined && row.llvm_bytes === undefined, + 'Windows WASIX AOT uses the owned V8 producer', + ); + continue; + } invariant( row.llvm_url === target.llvmUrl, `WASIX AOT CI matrix ${row.target_id} must bind its declared LLVM URL`, diff --git a/tools/release/download-wasix-runtime-build-artifacts.test.mts b/tools/release/download-wasix-runtime-build-artifacts.test.mts index 6961aa6ab..d694aa2e4 100644 --- a/tools/release/download-wasix-runtime-build-artifacts.test.mts +++ b/tools/release/download-wasix-runtime-build-artifacts.test.mts @@ -15,10 +15,11 @@ if (mode === 'targets') { assert.match(commands, /import-download/); } else { const sha = 'b'.repeat(40); - assert.equal(commands.split(`CI\n${sha}\n`).length - 1, count); + assert.equal(commands.split(`CI\n${sha}\n`).length - 1, count + 1); assert.ok(!commands.includes('a'.repeat(40))); const run = mode === 'selected' ? '777' : '30358387218'; - assert.equal(commands.split(`--run-id\n${run}\n--job\nBuilds\n`).length - 1, count); + assert.equal(commands.split(`--run-id\n${run}\n--job\nBuilds\n`).length - 1, count + 1); + assert.ok(commands.includes('--artifact\nliboliphaunt-wasix-windows-engine\n')); if (mode === 'selected') assert.ok(commands.includes(`--commit\n${sha}\n--status\nsuccess`)); const install = commands.slice(commands.indexOf('import-download\n')); for (const [id, triple] of Object.entries(AOT_TARGET_TRIPLES)) { diff --git a/tools/release/prepare-release-candidate.test.mts b/tools/release/prepare-release-candidate.test.mts index 1f4b80459..7a0d33f29 100644 --- a/tools/release/prepare-release-candidate.test.mts +++ b/tools/release/prepare-release-candidate.test.mts @@ -156,7 +156,12 @@ test('Cargo owns transitive, inherited, renamed, build, target and custom source }); test('embedded generated payloads follow private producer ownership for source-pin changes', () => { - for (const file of ['src/third-party/postgres/source.toml', 'src/third-party/icu/source.toml']) { + for (const file of [ + 'src/third-party/postgres/source.toml', + 'src/third-party/icu/source.toml', + 'src/wasix/runtime/engine/source.toml', + 'src/wasix/runtime/engine/patches/wasmer/series', + ]) { expect(existsSync(path.join(ROOT, file))).toBe(true); const plan = buildPlan(graph, [file]); expect(plan.releaseProducts).toContain('liboliphaunt-wasix'); @@ -164,6 +169,43 @@ test('embedded generated payloads follow private producer ownership for source-p } }); +test('release planning does not require generated sources owned by an embedded payload', () => { + const scratch = mkdtempSync(path.join(ROOT, 'target/embedded-payload-')); + const relative = path.relative(ROOT, scratch).split(path.sep).join('/'); + try { + mkdirSync(path.join(scratch, 'consumer/src'), { recursive: true }); + mkdirSync(path.join(scratch, 'producer'), { recursive: true }); + writeFileSync( + path.join(scratch, 'Cargo.toml'), + '[workspace]\nmembers = ["consumer", "producer"]\n', + ); + writeFileSync(path.join(scratch, 'consumer/src/lib.rs'), 'pub fn consumer() {}\n'); + writeFileSync( + path.join(scratch, 'consumer/Cargo.toml'), + '[package]\nname = "consumer"\nversion = "1.0.0"\n[dependencies]\nproducer = { path = "../producer" }\n', + ); + writeFileSync( + path.join(scratch, 'producer/Cargo.toml'), + '[package]\nname = "producer"\nversion = "1.0.0"\n[lib]\npath = "upstream/src/lib.rs"\n', + ); + const impacts = declaredSharedSourceImpacts({ + 'oliphaunt-wasix-napi': { + embedded_cargo_manifests: [`${relative}/consumer/Cargo.toml`], + embedded_payload_products: ['liboliphaunt-wasix'], + }, + 'liboliphaunt-wasix': { path: `${relative}/producer` }, + }); + expect(impacts.some((row) => row.source_paths.includes(`${relative}/consumer/src`))).toBe(true); + expect(impacts.some((row) => row.source_paths.some((file) => file.includes('/upstream')))).toBe( + false, + ); + expect(existsSync(path.join(scratch, 'producer/upstream'))).toBe(false); + expect(existsSync(path.join(scratch, 'Cargo.lock'))).toBe(false); + } finally { + rmSync(scratch, { recursive: true, force: true }); + } +}); + async function generate(commits, baselines = { [native]: first, [wasix]: first }) { useSourceDate('2026-09-11'); const config = Object.fromEntries( diff --git a/tools/release/publication-catalog.mts b/tools/release/publication-catalog.mts index fb5bf20d2..cbb9a95cc 100644 --- a/tools/release/publication-catalog.mts +++ b/tools/release/publication-catalog.mts @@ -146,7 +146,10 @@ function isSplittableCargoCarrier({ ecosystem, name, product, role }) { (SPLITTABLE_CARGO_ROLES.has(role) || (product === 'postgres-tools-native' && role === 'tool-leaf' && - name.startsWith('oliphaunt-tools-'))) + name.startsWith('oliphaunt-tools-')) || + (product === 'postgres-tools-wasix' && + role === 'tool-leaf' && + name === 'oliphaunt-wasix-tools-aot-x86_64-pc-windows-msvc')) ); } diff --git a/tools/release/publication-catalog.test.mts b/tools/release/publication-catalog.test.mts index 8448bad03..c38861a8c 100644 --- a/tools/release/publication-catalog.test.mts +++ b/tools/release/publication-catalog.test.mts @@ -64,8 +64,11 @@ test('native tool target leaves admit exact payload parts while facades remain n const otherToolLeaves = catalog.carriers .filter( - ({ ecosystem, product, role }) => - ecosystem === 'cargo' && product !== 'postgres-tools-native' && role === 'tool-leaf', + ({ ecosystem, name, product, role }) => + ecosystem === 'cargo' && + product !== 'postgres-tools-native' && + role === 'tool-leaf' && + name !== 'oliphaunt-wasix-tools-aot-x86_64-pc-windows-msvc', ) .map(({ name }) => name) .sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)); @@ -77,6 +80,23 @@ test('native tool target leaves admit exact payload parts while facades remain n } }); +test('Windows V8 AOT payload parts retain their declared runtime or tools owner', () => { + const catalog = loadPublicationCatalog('publication-catalog.test'); + for (const [name, product] of [ + ['liboliphaunt-wasix-aot-x86_64-pc-windows-msvc', 'liboliphaunt-wasix'], + ['oliphaunt-wasix-tools-aot-x86_64-pc-windows-msvc', 'postgres-tools-wasix'], + ]) { + expect(resolveActualCarrier(catalog, 'cargo', `${name}-part-001`)).toMatchObject({ + parentCarrier: `cargo:${name}`, + product, + part: 1, + role: 'payload-part', + declared: false, + }); + expect(() => resolveActualCarrier(catalog, 'cargo', `${name}-part-000`)).toThrow(/1-based/u); + } +}); + describe('WASIX extension portable publication carriers', () => { test('assigns every independently versioned portable carrier an explicit canonical target', () => { const products = exactExtensionProducts('publication-catalog.test'); diff --git a/tools/release/release-artifact-targets.mts b/tools/release/release-artifact-targets.mts index ba26a83e0..083f281c7 100644 --- a/tools/release/release-artifact-targets.mts +++ b/tools/release/release-artifact-targets.mts @@ -541,9 +541,13 @@ function liboliphauntWasixRows(prefix) { target, triple: platform.triple, runner: platform.runner, - llvm_url: platform.wasixLlvmUrl, - llvm_sha256: platform.wasixLlvmSha256, - llvm_bytes: platform.wasixLlvmBytes, + ...(target === 'windows-x64-msvc' + ? {} + : { + llvm_url: platform.wasixLlvmUrl, + llvm_sha256: platform.wasixLlvmSha256, + llvm_bytes: platform.wasixLlvmBytes, + }), asset: `liboliphaunt-wasix-{version}-runtime-aot-${target}.tar.zst`, surfaces: ['github-release'], _source_file: 'Moon release metadata', diff --git a/tools/release/release-graph.mts b/tools/release/release-graph.mts index 9cabfaa15..ac9e1986b 100644 --- a/tools/release/release-graph.mts +++ b/tools/release/release-graph.mts @@ -426,6 +426,15 @@ export function declaredSharedSourceImpacts(products, prefix = 'release-graph') const visit = (manifestPath) => { if (visited.has(manifestPath)) return; visited.add(manifestPath); + // Independently owned payloads already propagate through + // embedded_payload_products. Plan from their checked-in producer inputs, + // rather than requiring generated Cargo sources before acquisition. + if ( + (config.embedded_payload_products ?? []).some((owner) => + manifestPath.startsWith(`${products[owner]?.path}/`), + ) + ) + return; const directory = path.posix.dirname(manifestPath); const pkg = cargoPackage(manifestPath); // Local runtime and build dependencies become part of the shipped binary.