From 4c89fc06c3257a8fe49e981c04e0384eee9f027a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 3 Mar 2025 13:09:50 -0800 Subject: [PATCH 01/13] Refactor futures/streams support for Rust Mostly remove the old implementations for futures/streams and replace them with new versions which address a number of preexisting issues and additionally adapt to upcoming ABI changes. --- Cargo.lock | 283 ++++--- Cargo.toml | 11 +- crates/core/src/abi.rs | 2 +- crates/guest-rust/rt/src/async_support.rs | 106 +-- .../rt/src/async_support/abi_buffer.rs | 196 +++++ .../rt/src/async_support/future_support.rs | 702 ++++++++-------- .../rt/src/async_support/stream_support.rs | 749 +++++++++--------- .../rt/src/async_support/waitable.rs | 331 ++++++++ crates/guest-rust/rt/src/lib.rs | 58 ++ crates/guest-rust/src/lib.rs | 2 + crates/rust/src/bindgen.rs | 126 +-- crates/rust/src/interface.rs | 513 +++++------- crates/rust/src/lib.rs | 30 +- crates/test/Cargo.toml | 9 +- .../future-cancel-write-then-read/runner.rs | 11 + .../future-cancel-write-then-read/test.rs | 17 + .../future-cancel-write-then-read/test.wit | 12 + .../future-close-after-coming-back/runner.rs | 11 + .../future-close-after-coming-back/test.rs | 15 + .../future-close-after-coming-back/test.wit | 12 + .../future-close-then-receive-read/runner.rs | 14 + .../future-close-then-receive-read/test.rs | 22 + .../future-close-then-receive-read/test.wit | 13 + .../async/future-closes-with-error/runner.rs | 11 + .../async/future-closes-with-error/test.rs | 17 + .../async/future-closes-with-error/test.wit | 12 + .../runner.rs | 16 + .../future-write-then-read-comes-back/test.rs | 15 + .../test.wit | 12 + .../future-write-then-read-remote/runner.rs | 16 + .../future-write-then-read-remote/runner2.rs | 16 + .../future-write-then-read-remote/test.rs | 19 + .../future-write-then-read-remote/test.wit | 12 + 33 files changed, 2018 insertions(+), 1373 deletions(-) create mode 100644 crates/guest-rust/rt/src/async_support/abi_buffer.rs create mode 100644 crates/guest-rust/rt/src/async_support/waitable.rs create mode 100644 tests/runtime-new/async/future-cancel-write-then-read/runner.rs create mode 100644 tests/runtime-new/async/future-cancel-write-then-read/test.rs create mode 100644 tests/runtime-new/async/future-cancel-write-then-read/test.wit create mode 100644 tests/runtime-new/async/future-close-after-coming-back/runner.rs create mode 100644 tests/runtime-new/async/future-close-after-coming-back/test.rs create mode 100644 tests/runtime-new/async/future-close-after-coming-back/test.wit create mode 100644 tests/runtime-new/async/future-close-then-receive-read/runner.rs create mode 100644 tests/runtime-new/async/future-close-then-receive-read/test.rs create mode 100644 tests/runtime-new/async/future-close-then-receive-read/test.wit create mode 100644 tests/runtime-new/async/future-closes-with-error/runner.rs create mode 100644 tests/runtime-new/async/future-closes-with-error/test.rs create mode 100644 tests/runtime-new/async/future-closes-with-error/test.wit create mode 100644 tests/runtime-new/async/future-write-then-read-comes-back/runner.rs create mode 100644 tests/runtime-new/async/future-write-then-read-comes-back/test.rs create mode 100644 tests/runtime-new/async/future-write-then-read-comes-back/test.wit create mode 100644 tests/runtime-new/async/future-write-then-read-remote/runner.rs create mode 100644 tests/runtime-new/async/future-write-then-read-remote/runner2.rs create mode 100644 tests/runtime-new/async/future-write-then-read-remote/test.rs create mode 100644 tests/runtime-new/async/future-write-then-read-remote/test.wit diff --git a/Cargo.lock b/Cargo.lock index d65bedd13..1c81d456c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,7 +35,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -126,9 +126,9 @@ checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -233,7 +233,7 @@ checksum = "4ac68674a6042af2bcee1adad9f6abd432642cf03444ce3a5b36c3f39f23baf8" dependencies = [ "cap-primitives", "cap-std", - "rustix", + "rustix 0.38.44", "smallvec", ] @@ -249,7 +249,7 @@ dependencies = [ "io-lifetimes", "ipnet", "maybe-owned", - "rustix", + "rustix 0.38.44", "windows-sys 0.59.0", "winx", ] @@ -273,7 +273,7 @@ dependencies = [ "cap-primitives", "io-extras", "io-lifetimes", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -286,15 +286,15 @@ dependencies = [ "cap-primitives", "iana-time-zone", "once_cell", - "rustix", + "rustix 0.38.44", "winx", ] [[package]] name = "cc" -version = "1.2.16" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ "jobserver", "libc", @@ -309,9 +309,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.31" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ "clap_builder", "clap_derive", @@ -319,9 +319,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ "anstream", "anstyle", @@ -332,9 +332,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -703,12 +703,12 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fd-lock" -version = "4.0.3" +version = "4.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c44818c96aec5cadc9dacfb97bbcbcfc19a0de75b218412d56f57fbaab94e439" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix", + "rustix 1.0.3", "windows-sys 0.59.0", ] @@ -736,9 +736,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -751,12 +751,12 @@ dependencies = [ [[package]] name = "fs-set-times" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" dependencies = [ "io-lifetimes", - "rustix", + "rustix 1.0.3", "windows-sys 0.59.0", ] @@ -943,14 +943,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1005,9 +1006,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -1029,9 +1030,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -1050,9 +1051,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -1111,9 +1112,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1185,9 +1186,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" dependencies = [ "jiff-static", "log", @@ -1198,9 +1199,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" dependencies = [ "proc-macro2", "quote", @@ -1246,9 +1247,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libm" @@ -1272,6 +1273,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + [[package]] name = "litemap" version = "0.7.5" @@ -1280,9 +1287,9 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "logos" @@ -1344,7 +1351,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix", + "rustix 0.38.44", ] [[package]] @@ -1405,9 +1412,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b" [[package]] name = "paste" @@ -1478,18 +1485,18 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.24", ] [[package]] name = "prettyplease" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" +checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", "syn", @@ -1526,9 +1533,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -1658,11 +1665,24 @@ dependencies = [ "errno", "itoa", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", "once_cell", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.3", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -1686,18 +1706,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -1814,9 +1834,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1845,7 +1865,7 @@ dependencies = [ "cap-std", "fd-lock", "io-lifetimes", - "rustix", + "rustix 0.38.44", "windows-sys 0.59.0", "winx", ] @@ -1867,11 +1887,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix", + "rustix 1.0.3", "windows-sys 0.59.0", ] @@ -1929,9 +1949,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", @@ -2080,9 +2100,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" [[package]] name = "version_check" @@ -2092,9 +2112,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wac-graph" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d94268a683b67ae20210565b5f91e106fe05034c36b931e739fe90377ed80b98" +version = "0.7.0-dev" dependencies = [ "anyhow", "id-arena", @@ -2104,16 +2122,14 @@ dependencies = [ "semver", "thiserror", "wac-types", - "wasm-encoder 0.202.0", - "wasm-metadata 0.202.0", - "wasmparser 0.202.0", + "wasm-encoder 0.227.1", + "wasm-metadata", + "wasmparser 0.227.1", ] [[package]] name = "wac-parser" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec0c4f63641fa095b4a551263fe35a15c72c9680b650b8f08f70db0fdbd19" +version = "0.7.0-dev" dependencies = [ "anyhow", "id-arena", @@ -2125,23 +2141,21 @@ dependencies = [ "serde", "thiserror", "wac-graph", - "wasm-encoder 0.202.0", - "wasm-metadata 0.202.0", - "wasmparser 0.202.0", + "wasm-encoder 0.227.1", + "wasm-metadata", + "wasmparser 0.227.1", ] [[package]] name = "wac-types" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5028a15e266f4c8fed48beb95aebb76af5232dcd554fd849a305a4e5cce1563" +version = "0.7.0-dev" dependencies = [ "anyhow", "id-arena", "indexmap", "semver", - "wasm-encoder 0.202.0", - "wasmparser 0.202.0", + "wasm-encoder 0.227.1", + "wasmparser 0.227.1", ] [[package]] @@ -2214,15 +2228,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.202.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd106365a7f5f7aa3c1916a98cbb3ad477f5ff96ddb130285a91c6e7429e67a" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.217.1" @@ -2235,34 +2240,14 @@ dependencies = [ [[package]] name = "wasm-encoder" version = "0.227.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822" dependencies = [ "leb128fmt", "wasmparser 0.227.1", ] -[[package]] -name = "wasm-metadata" -version = "0.202.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "094aea3cb90e09f16ee25a4c0e324b3e8c934e7fd838bfa039aef5352f44a917" -dependencies = [ - "anyhow", - "indexmap", - "serde", - "serde_derive", - "serde_json", - "spdx", - "wasm-encoder 0.202.0", - "wasmparser 0.202.0", -] - [[package]] name = "wasm-metadata" version = "0.227.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1ef0faabbbba6674e97a56bee857ccddf942785a336c8b47b42373c922a91d" dependencies = [ "anyhow", "auditable-serde", @@ -2277,17 +2262,6 @@ dependencies = [ "wasmparser 0.227.1", ] -[[package]] -name = "wasmparser" -version = "0.202.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6998515d3cf3f8b980ef7c11b29a9b1017d4cf86b99ae93b546992df9931413" -dependencies = [ - "bitflags", - "indexmap", - "semver", -] - [[package]] name = "wasmparser" version = "0.217.1" @@ -2305,8 +2279,6 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.227.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ "bitflags", "hashbrown 0.15.2", @@ -2356,7 +2328,7 @@ dependencies = [ "postcard", "psm", "rayon", - "rustix", + "rustix 0.38.44", "semver", "serde", "serde_derive", @@ -2402,7 +2374,7 @@ dependencies = [ "directories-next", "log", "postcard", - "rustix", + "rustix 0.38.44", "serde", "serde_derive", "sha2", @@ -2493,7 +2465,7 @@ dependencies = [ "anyhow", "cc", "cfg-if", - "rustix", + "rustix 0.38.44", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", "windows-sys 0.52.0", @@ -2507,7 +2479,7 @@ checksum = "106731c6ebe1d551362ee8c876d450bdc2d517988b20eb3653dc4837b1949437" dependencies = [ "object", "once_cell", - "rustix", + "rustix 0.38.44", "wasmtime-versioned-export-macros", ] @@ -2574,7 +2546,7 @@ dependencies = [ "io-extras", "io-lifetimes", "once_cell", - "rustix", + "rustix 0.38.44", "system-interface", "thiserror", "tokio", @@ -2626,8 +2598,6 @@ dependencies = [ [[package]] name = "wast" version = "227.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c14e5042b16c9d267da3b9b0f4529870455178415286312c25c34dfc1b2816" dependencies = [ "bumpalo", "leb128fmt", @@ -2639,8 +2609,6 @@ dependencies = [ [[package]] name = "wat" version = "1.227.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d394d5bef7006ff63338d481ca10f1af76601e65ebdf5ed33d29302994e9cc" dependencies = [ "wast 227.0.1", ] @@ -2828,9 +2796,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] @@ -2861,7 +2829,7 @@ dependencies = [ "clap", "heck 0.5.0", "wasm-encoder 0.227.1", - "wasm-metadata 0.227.1", + "wasm-metadata", "wit-bindgen-core", "wit-component", ] @@ -2907,7 +2875,7 @@ dependencies = [ "clap", "heck 0.5.0", "indexmap", - "wasm-metadata 0.227.1", + "wasm-metadata", "wit-bindgen-core", "wit-component", "wit-parser 0.227.1", @@ -2957,7 +2925,7 @@ dependencies = [ "serde_json", "syn", "test-helpers", - "wasm-metadata 0.227.1", + "wasm-metadata", "wit-bindgen", "wit-bindgen-core", "wit-bindgen-rt", @@ -3005,8 +2973,6 @@ dependencies = [ [[package]] name = "wit-component" version = "0.227.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" dependencies = [ "anyhow", "bitflags", @@ -3016,7 +2982,7 @@ dependencies = [ "serde_derive", "serde_json", "wasm-encoder 0.227.1", - "wasm-metadata 0.227.1", + "wasm-metadata", "wasmparser 0.227.1", "wat", "wit-parser 0.227.1", @@ -3043,8 +3009,6 @@ dependencies = [ [[package]] name = "wit-parser" version = "0.227.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" dependencies = [ "anyhow", "id-arena", @@ -3112,8 +3076,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -3127,6 +3099,17 @@ dependencies = [ "syn", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" @@ -3181,18 +3164,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.3" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.14+zstd.1.5.7" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 0a2388fd3..64c83877e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,9 @@ futures = "0.3.31" wat = "1.227.0" wasmparser = "0.227.0" wasm-encoder = "0.227.0" -wasm-metadata = "0.227.0" +wasm-metadata = { version = "0.227.0", default-features = false } wit-parser = "0.227.0" wit-component = "0.227.0" -wasm-compose = "0.227.0" wit-bindgen-core = { path = 'crates/core', version = '0.41.0' } wit-bindgen-c = { path = 'crates/c', version = '0.41.0' } @@ -51,6 +50,14 @@ wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.41.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.41.0', default-features = false } wit-bindgen-test = { path = 'crates/test', version = '0.41.0' } +[patch.crates-io] +wat = { path = '../wasm-tools/crates/wat' } +wasmparser = { path = '../wasm-tools/crates/wasmparser' } +wasm-encoder = { path = '../wasm-tools/crates/wasm-encoder' } +wasm-metadata = { path = '../wasm-tools/crates/wasm-metadata' } +wit-parser = { path = '../wasm-tools/crates/wit-parser' } +wit-component = { path = '../wasm-tools/crates/wit-component' } + [[bin]] name = "wit-bindgen" diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 5fcc39140..fc7de9353 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -753,7 +753,7 @@ pub fn lower_to_memory( // future/stream callbacks so it's appropriate to skip realloc here as it's // all "lower for wasm import", but this might get reused for something else // in the future. - generator.realloc = Some(Realloc::None); + generator.realloc = Some(Realloc::Export("cabi_realloc")); generator.stack.push(value); generator.write_to_memory(ty, address, Default::default()); } diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index 399e90ddc..ca8068bb4 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -8,7 +8,7 @@ extern crate std; use core::sync::atomic::{AtomicBool, Ordering}; use std::any::Any; use std::boxed::Box; -use std::collections::{hash_map, HashMap}; +use std::collections::HashMap; use std::fmt::{self, Debug, Display}; use std::future::Future; use std::mem; @@ -24,13 +24,14 @@ use futures::future::FutureExt; use futures::stream::{FuturesUnordered, StreamExt}; use once_cell::sync::Lazy; +mod abi_buffer; mod future_support; mod stream_support; +mod waitable; -pub use { - future_support::{future_new, FutureReader, FutureVtable, FutureWriter}, - stream_support::{stream_new, StreamReader, StreamVtable, StreamWriter}, -}; +pub use abi_buffer::*; +pub use future_support::*; +pub use stream_support::*; pub use futures; @@ -48,6 +49,8 @@ struct FutureState { tasks: Option>, /// The waitable set containing waitables created by this task, if any. waitable_set: Option, + /// TODO + wakers: HashMap)>, } impl FutureState { @@ -62,6 +65,10 @@ impl FutureState { fn remove_waitable(&mut self, waitable: u32) { unsafe { waitable_join(waitable, 0) } } + + fn remaining_work(&self) -> bool { + self.todo > 0 || !self.wakers.is_empty() + } } impl Drop for FutureState { @@ -97,9 +104,6 @@ static mut CALLS: Lazy>> = Lazy::new(HashMap:: /// polling the current task. static mut SPAWNED: Vec = Vec::new(); -/// The states of all currently-open streams and futures. -static mut HANDLES: Lazy> = Lazy::new(HashMap::new); - const EVENT_NONE: i32 = 0; const _EVENT_CALL_STARTING: i32 = 1; const EVENT_CALL_STARTED: i32 = 2; @@ -118,11 +122,6 @@ const STATUS_STARTING: u32 = 1; const STATUS_STARTED: u32 = 2; const STATUS_RETURNED: u32 = 3; -#[doc(hidden)] -pub fn with_entry(handle: u32, fun: impl FnOnce(hash_map::Entry<'_, u32, Handle>) -> T) -> T { - fun(unsafe { HANDLES.entry(handle) }) -} - /// Poll the specified task until it either completes or can't make immediate /// progress. unsafe fn poll(state: *mut FutureState) -> Poll<()> { @@ -193,6 +192,7 @@ pub fn first_poll( .collect(), ), waitable_set: None, + wakers: HashMap::new(), })); let done = unsafe { poll(state).is_ready() }; unsafe { callback_code(state, done) } @@ -282,58 +282,6 @@ impl AsyncWaitResult { } } -/// Await the completion of a future read or write. -#[doc(hidden)] -pub async unsafe fn await_future_result( - import: unsafe extern "C" fn(u32, *mut u8) -> u32, - future: u32, - address: *mut u8, -) -> AsyncWaitResult { - let result = import(future, address); - match result { - results::BLOCKED => { - assert!(!CURRENT.is_null()); - (*CURRENT).todo += 1; - - (*CURRENT).add_waitable(future); - - let (tx, rx) = oneshot::channel(); - CALLS.insert(future as _, tx); - AsyncWaitResult::from_nonblocked_async_result(rx.await.unwrap()) - } - v => AsyncWaitResult::from_nonblocked_async_result(v), - } -} - -/// Await the completion of a stream read or write. -#[doc(hidden)] -pub async unsafe fn await_stream_result( - import: unsafe extern "C" fn(u32, *mut u8, u32) -> u32, - stream: u32, - address: *mut u8, - count: u32, -) -> AsyncWaitResult { - let result = import(stream, address, count); - match result { - results::BLOCKED => { - assert!(!CURRENT.is_null()); - (*CURRENT).todo += 1; - - (*CURRENT).add_waitable(stream); - - let (tx, rx) = oneshot::channel(); - CALLS.insert(stream as _, tx); - let v = rx.await.unwrap(); - if let results::CLOSED | results::CANCELED = v { - AsyncWaitResult::End - } else { - AsyncWaitResult::Values(usize::try_from(v).unwrap()) - } - } - v => AsyncWaitResult::from_nonblocked_async_result(v), - } -} - /// Call the `subtask.drop` canonical built-in function. fn subtask_drop(subtask: u32) { #[cfg(not(target_arch = "wasm32"))] @@ -351,7 +299,7 @@ fn subtask_drop(subtask: u32) { } unsafe fn callback_code(state: *mut FutureState, done: bool) -> i32 { - if done && (*state).todo == 0 { + if done && !(*state).remaining_work() { context_set(0); drop(Box::from_raw(state)); CALLBACK_CODE_EXIT @@ -383,8 +331,7 @@ unsafe fn callback_with_state( callback_code(state, done) } EVENT_CALL_STARTED => callback_code(state, false), - EVENT_CALL_RETURNED | EVENT_STREAM_READ | EVENT_STREAM_WRITE | EVENT_FUTURE_READ - | EVENT_FUTURE_WRITE => { + EVENT_CALL_RETURNED => { (*state).remove_waitable(event1 as _); if let Some(call) = CALLS.remove(&event1) { @@ -397,19 +344,21 @@ unsafe fn callback_with_state( subtask_drop(event1 as u32); } - if matches!( - event0, - EVENT_CALL_RETURNED - | EVENT_STREAM_READ - | EVENT_STREAM_WRITE - | EVENT_FUTURE_READ - | EVENT_FUTURE_WRITE - ) { - (*state).todo -= 1; - } + (*state).todo -= 1; callback_code(state, done) } + + EVENT_STREAM_READ | EVENT_STREAM_WRITE | EVENT_FUTURE_READ | EVENT_FUTURE_WRITE => { + (*state).remove_waitable(event1 as u32); + let (waker, code) = (*state).wakers.remove(&(event1 as u32)).unwrap(); + *code = Some(event2 as u32); + waker.wake(); + + let done = poll(state).is_ready(); + callback_code(state, done) + } + _ => unreachable!(), } } @@ -540,6 +489,7 @@ pub fn block_on(future: impl Future + 'static) -> T { .collect(), ), waitable_set: None, + wakers: HashMap::new(), }; loop { match unsafe { poll(state) } { diff --git a/crates/guest-rust/rt/src/async_support/abi_buffer.rs b/crates/guest-rust/rt/src/async_support/abi_buffer.rs new file mode 100644 index 000000000..848a610c2 --- /dev/null +++ b/crates/guest-rust/rt/src/async_support/abi_buffer.rs @@ -0,0 +1,196 @@ +use crate::async_support::StreamVtable; +use crate::Cleanup; +use std::alloc::Layout; +use std::mem::{self, MaybeUninit}; +use std::ptr; +use std::vec::Vec; + +/// A helper structure used with a stream to handle the canonical ABI +/// representation of lists and track partial writes. +/// +/// This structure is returned whenever a write to a stream completes. This +/// keeps track of the original buffer used to perform a write (`Vec`) and +/// additionally tracks any partial writes. Writes can then be resumed with +/// this buffer again or the partial write can be converted back to `Vec` to +/// get access to the remaining values. +pub struct AbiBuffer { + rust_storage: Vec>, + vtable: &'static StreamVtable, + alloc: Option, + cursor: usize, +} + +impl AbiBuffer { + pub(crate) fn new(mut vec: Vec, vtable: &'static StreamVtable) -> AbiBuffer { + assert_eq!(vtable.lower.is_some(), vtable.lift.is_some()); + + // SAFETY: We're converting `Vec` to `Vec>`, which + // should be safe. + let rust_storage = unsafe { + let ptr = vec.as_mut_ptr(); + let len = vec.len(); + let cap = vec.capacity(); + mem::forget(vec); + Vec::>::from_raw_parts(ptr.cast(), len, cap) + }; + + // If `lower` is provided then the canonical ABI format is different + // from the native format, so all items are converted at this time. + // + // Note that this is probably pretty inefficient for "big" use cases + // but it's hoped that "big" use cases are using `u8` and therefore + // skip this entirely. + let alloc = vtable.lower.and_then(|lower| { + let layout = Layout::from_size_align( + vtable.layout.size() * rust_storage.len(), + vtable.layout.align(), + ) + .unwrap(); + let (mut ptr, cleanup) = Cleanup::new(layout); + let cleanup = cleanup?; + // SAFETY: All items in `rust_storage` are already initialized so + // it should be safe to read them and move ownership into the + // canonical ABI format. + unsafe { + for item in rust_storage.iter() { + let item = item.assume_init_read(); + lower(item, ptr); + ptr = ptr.add(vtable.layout.size()); + } + } + + Some(cleanup) + }); + AbiBuffer { + rust_storage, + alloc, + vtable, + cursor: 0, + } + } + + /// Returns the canonical ABI pointer/length to pass off to a write + /// operation. + pub(crate) fn abi_ptr_and_len(&self) -> (*const u8, usize) { + // If there's no `lower` operation then it menas that `T`'s layout is + // the same in the canonical ABI so it can be used as-is. In this + // situation the list would have been un-tampered with above. + if self.vtable.lower.is_none() { + // SAFETY: this should be in-bounds, so it should be safe. + let ptr = unsafe { self.rust_storage.as_ptr().add(self.cursor).cast() }; + let len = self.rust_storage.len() - self.cursor; + return (ptr, len.try_into().unwrap()); + } + + // Othereise when `lower` is present that means that `self.alloc` has + // the ABI pointer we should pass along. + let ptr = self + .alloc + .as_ref() + .map(|c| c.ptr.as_ptr()) + .unwrap_or(ptr::null_mut()); + ( + // SAFETY: this should be in-bounds, so it should be safe. + unsafe { ptr.add(self.cursor * self.vtable.layout.size()) }, + self.rust_storage.len() - self.cursor, + ) + } + + /// Converts this `AbiBuffer` back into a `Vec` + /// + /// This commit consumes this buffer and yields back unwritten values as a + /// `Vec`. The remaining items in `Vec` have not yet been written and + /// all written items have been removed from the front of the list. + /// + /// Note that the backing storage of the returned `Vec` has not changed + /// from whe this buffer was created. + /// + /// Also note that this can be an expensive operation if a partial write + /// occurred as this will involve shifting items from the end of the vector + /// to the start of the vector. + pub fn into_vec(mut self) -> Vec { + self.take_vec() + } + + /// Returns the number of items remaining in this buffer. + pub fn remaining(&self) -> usize { + self.rust_storage.len() - self.cursor + } + + /// Advances this buffer by `amt` items. + /// + /// This signals that `amt` items are no longer going to be yielded from + /// `abi_ptr_and_len`. Additionally this will perform any deallocation + /// necessary for the starting `amt` items in this list. + pub(crate) fn advance(&mut self, amt: usize) { + assert!(amt + self.cursor <= self.rust_storage.len()); + let Some(dealloc_lists) = self.vtable.dealloc_lists else { + self.cursor += amt; + return; + }; + let (mut ptr, len) = self.abi_ptr_and_len(); + assert!(amt <= len); + for _ in 0..amt { + // SAFETY: we're managing the pointer passed to `dealloc_lists` and + // it was initialized with a `lower`, and then the pointer + // arithmetic should all be in-bounds. + unsafe { + dealloc_lists(ptr.cast_mut()); + ptr = ptr.add(self.vtable.layout.size()); + } + } + self.cursor += amt; + } + + fn take_vec(&mut self) -> Vec { + // First, if necessary, convert remaining values within `self.alloc` + // back into `self.rust_storage`. This is necessary when a lift + // operation is available meaning that the representation of `T` is + // different in the canonical ABI. + // + // Note that when `lift` is provided then when this original + // `AbiBuffer` was created it moved ownership of all values from the + // original vector into the `alloc` value. This is the reverse + // operation, moving all the values back into the vector. + if let Some(lift) = self.vtable.lift { + let (mut ptr, mut len) = self.abi_ptr_and_len(); + // SAFETY: this should be safe as `lift` is operating on values that + // were initialized with a previous `lower`, and the pointer + // arithmetic here should all be in-bounds. + unsafe { + for dst in self.rust_storage[self.cursor..].iter_mut() { + dst.write(lift(ptr.cast_mut())); + ptr = ptr.add(self.vtable.layout.size()); + len -= 1; + } + assert_eq!(len, 0); + } + } + + // Next extract the rust storage and zero out this struct's fields. + // This is also the location where a "shift" happens to remove items + // from the beginning of the returned vector as those have already been + // transferred somewhere else. + let mut storage = mem::take(&mut self.rust_storage); + storage.drain(..self.cursor); + self.cursor = 0; + self.alloc = None; + + // SAFETY: we're casting `Vec>` here to `Vec`. The + // elements were either always initialized (`lift` is `None`) or we just + // re-initialized them above from `self.alloc`. + unsafe { + let ptr = storage.as_mut_ptr(); + let len = storage.len(); + let cap = storage.capacity(); + mem::forget(storage); + Vec::::from_raw_parts(ptr.cast(), len, cap) + } + } +} + +impl Drop for AbiBuffer { + fn drop(&mut self) { + let _ = self.take_vec(); + } +} diff --git a/crates/guest-rust/rt/src/async_support/future_support.rs b/crates/guest-rust/rt/src/async_support/future_support.rs index 5e1aedce6..c11ff33b9 100644 --- a/crates/guest-rust/rt/src/async_support/future_support.rs +++ b/crates/guest-rust/rt/src/async_support/future_support.rs @@ -1,18 +1,21 @@ -extern crate std; +//! Runtime support for `future` in the component model. +//! +//! TODO: +//! +//! * leaking requires owned values +//! * owned values means we can't return back sent items +//! * intimately used in implementation details. use { - super::ErrorContext, - super::Handle, - futures::{ - channel::oneshot, - future::{self, FutureExt}, - }, + super::waitable::{WaitableOp, WaitableOperation}, + crate::Cleanup, std::{ - boxed::Box, - collections::hash_map::Entry, + alloc::Layout, fmt, future::{Future, IntoFuture}, + marker, pin::Pin, + ptr, sync::atomic::{AtomicU32, Ordering::Relaxed}, task::{Context, Poll}, }, @@ -20,13 +23,17 @@ use { #[doc(hidden)] pub struct FutureVtable { - pub write: fn(future: u32, value: T) -> Pin>>, - pub read: fn(future: u32) -> Pin>>>>, + pub layout: Layout, + pub lower: unsafe fn(value: T, dst: *mut u8), + pub dealloc_lists: unsafe fn(dst: *mut u8), + pub lift: unsafe fn(dst: *mut u8) -> T, + pub start_write: unsafe extern "C" fn(future: u32, val: *const u8) -> u32, + pub start_read: unsafe extern "C" fn(future: u32, val: *mut u8) -> u32, pub cancel_write: unsafe extern "C" fn(future: u32) -> u32, pub cancel_read: unsafe extern "C" fn(future: u32) -> u32, - pub close_writable: unsafe extern "C" fn(future: u32, err_ctx: u32), - pub close_readable: unsafe extern "C" fn(future: u32, err_ctx: u32), - pub new: unsafe extern "C" fn() -> u32, + pub close_writable: unsafe extern "C" fn(future: u32), + pub close_readable: unsafe extern "C" fn(future: u32), + pub new: unsafe extern "C" fn() -> u64, } /// Helper function to create a new read/write pair for a component model @@ -34,17 +41,13 @@ pub struct FutureVtable { pub unsafe fn future_new( vtable: &'static FutureVtable, ) -> (FutureWriter, FutureReader) { - let handle = unsafe { (vtable.new)() }; - super::with_entry(handle, |entry| match entry { - Entry::Vacant(entry) => { - entry.insert(Handle::LocalOpen); - } - Entry::Occupied(_) => unreachable!(), - }); - ( - FutureWriter::new(handle, vtable), - FutureReader::new(handle, vtable), - ) + unsafe { + let handles = (vtable.new)(); + ( + FutureWriter::new(handles as u32, vtable), + FutureReader::new((handles >> 32) as u32, vtable), + ) + } } /// Represents the writable end of a Component Model `future`. @@ -53,6 +56,20 @@ pub struct FutureWriter { vtable: &'static FutureVtable, } +impl FutureWriter { + #[doc(hidden)] + pub unsafe fn new(handle: u32, vtable: &'static FutureVtable) -> Self { + Self { handle, vtable } + } + + /// Write the specified `value` to this `future`. + pub fn write(self, value: T) -> FutureWrite { + FutureWrite { + op: WaitableOperation::new((self, value)), + } + } +} + impl fmt::Debug for FutureWriter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FutureWriter") @@ -61,227 +78,237 @@ impl fmt::Debug for FutureWriter { } } +impl Drop for FutureWriter { + fn drop(&mut self) { + unsafe { + (self.vtable.close_writable)(self.handle); + } + } +} + /// Represents a write operation which may be canceled prior to completion. -pub struct CancelableWrite { - writer: Option>, - future: Pin>>, +pub struct FutureWrite { + op: WaitableOperation>, +} + +struct FutureWriteOp(marker::PhantomData); + +enum WriteComplete { + Written, + Closed(T), + Cancelled(T), } -impl Future for CancelableWrite { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { - let me = self.get_mut(); - match me.future.poll_unpin(cx) { - Poll::Ready(()) => { - me.writer = None; - Poll::Ready(()) - } - Poll::Pending => Poll::Pending, +unsafe impl WaitableOp for FutureWriteOp +where + T: 'static, +{ + type Start = (FutureWriter, T); + type InProgress = (FutureWriter, Option); + type Result = (WriteComplete, FutureWriter); + type Cancel = Result<(), FutureWriteCancel>; + + fn start((writer, value): Self::Start) -> (u32, Self::InProgress) { + // TODO: it should be safe to store the lower-destination in + // `WaitableOperation` using `Pin` memory and such, but that would + // require some type-level trickery to get a correctly-sized value + // plumbed all the way to here. For now just dynamically allocate it and + // leave the optimization of leaving out this dynamic allocation to the + // future. + // + // In lieu of that a dedicated location on the heap is created for the + // lowering, and then `value`, as an owned value, is lowered into this + // pointer to initialize it. + let (ptr, cleanup) = Cleanup::new(writer.vtable.layout); + // SAFETY: `ptr` is allocated with `vtable.layout` and should be + // safe to use here. + let code = unsafe { + (writer.vtable.lower)(value, ptr); + (writer.vtable.start_write)(writer.handle, ptr) + }; + (code, (writer, cleanup)) + } + + fn start_cancel((writer, value): Self::Start) -> Self::Cancel { + Err(FutureWriteCancel::Cancelled(value, writer)) + } + + /// This write has completed. + /// + /// Here we need to clean up our allocations. The `ptr` exclusively owns all + /// of the value being sent and we notably need to cleanup the transitive + /// list allocations present in this pointer. Use `dealloc_lists` for that + /// (effectively a post-return lookalike). + /// + /// Afterwards the `cleanup` itself is naturally dropped and cleaned up. + fn in_progress_complete((writer, cleanup): Self::InProgress, amt: u32) -> Self::Result { + assert_eq!(amt, 1); + let ptr = cleanup + .as_ref() + .map(|c| c.ptr.as_ptr()) + .unwrap_or(ptr::null_mut()); + + // SAFETY: we're the ones managing `ptr` so we know it's safe to + // pass here. + unsafe { + (writer.vtable.dealloc_lists)(ptr); } + (WriteComplete::Written, writer) } -} -impl CancelableWrite { - /// Cancel this write if it hasn't already completed, returning the original `FutureWriter`. + /// The other end has closed its end. /// - /// This method will panic if the write has already completed. - pub fn cancel(mut self) -> FutureWriter { - self.cancel_mut() - } - - fn cancel_mut(&mut self) -> FutureWriter { - let writer = self.writer.take().unwrap(); - super::with_entry(writer.handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen - | Handle::LocalWaiting(_) - | Handle::Read - | Handle::LocalClosed - | Handle::WriteClosedErr(_) => unreachable!(), - Handle::LocalReady(..) => { - entry.insert(Handle::LocalOpen); - } - Handle::Write => unsafe { - // TODO: spec-wise this can return `BLOCKED` which seems - // bad? - (writer.vtable.cancel_write)(writer.handle); - }, - }, - }); - writer + /// The value was not received by the other end so `ptr` still has all of + /// its resources intact. Use `lift` to construct a new instance of `T` + /// which takes ownership of pointers and resources and such. The allocation + /// of `ptr` is then cleaned up naturally when `cleanup` goes out of scope. + fn in_progress_closed((writer, cleanup): Self::InProgress) -> Self::Result { + let ptr = cleanup + .as_ref() + .map(|c| c.ptr.as_ptr()) + .unwrap_or(ptr::null_mut()); + // SAFETY: we're the ones managing `ptr` so we know it's safe to + // pass here. + let value = unsafe { (writer.vtable.lift)(ptr) }; + (WriteComplete::Closed(value), writer) } -} -impl Drop for CancelableWrite { - fn drop(&mut self) { - if self.writer.is_some() { - self.cancel_mut(); - } + fn in_progress_waitable((writer, _): &Self::InProgress) -> u32 { + writer.handle } -} -impl FutureWriter { - #[doc(hidden)] - pub fn new(handle: u32, vtable: &'static FutureVtable) -> Self { - Self { handle, vtable } + fn in_progress_cancel((writer, _): &Self::InProgress) -> u32 { + // SAFETY: we're managing `writer` and all the various operational bits, + // so this relies on `WaitableOperation` being safe. + unsafe { (writer.vtable.cancel_write)(writer.handle) } } - /// Write the specified value to this `future`. - pub fn write(self, v: T) -> CancelableWrite { - let handle = self.handle; - let vtable = self.vtable; - CancelableWrite { - writer: Some(self), - future: super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen => { - let mut v = Some(v); - Box::pin(future::poll_fn(move |cx| { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen => { - entry.insert(Handle::LocalReady( - Box::new(v.take().unwrap()), - cx.waker().clone(), - )); - Poll::Pending - } - Handle::LocalReady(..) => Poll::Pending, - Handle::LocalClosed | Handle::WriteClosedErr(_) => { - Poll::Ready(()) - } - Handle::LocalWaiting(_) | Handle::Read | Handle::Write => { - unreachable!() - } - }, - }) - })) as Pin>> - } - Handle::LocalWaiting(_) => { - let Handle::LocalWaiting(tx) = entry.insert(Handle::LocalClosed) else { - unreachable!() - }; - _ = tx.send(Box::new(v)); - Box::pin(future::ready(())) - } - Handle::LocalClosed | Handle::WriteClosedErr(_) => Box::pin(future::ready(())), - Handle::Read | Handle::LocalReady(..) => unreachable!(), - Handle::Write => Box::pin((vtable.write)(handle, v).map(drop)), - }, - }), + fn in_progress_canceled(state: Self::InProgress) -> Self::Result { + match Self::in_progress_closed(state) { + (WriteComplete::Closed(value), writer) => (WriteComplete::Cancelled(value), writer), + _ => unreachable!(), } } - /// Close the writer with an error that will be returned as the last value - /// - /// Note that this error is not sent immediately, but only when the - /// writer closes, which is normally a result of a `drop()` - pub fn close_with_error(&mut self, err: ErrorContext) { - super::with_entry(self.handle, move |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - // Regardless of current state, put the writer into a closed with error state - _ => { - entry.insert(Handle::WriteClosedErr(Some(err))); - } - }, - }); + fn result_into_cancel((result, writer): Self::Result) -> Self::Cancel { + match result { + // The value was actually sent, meaning we can't yield back the + // future nor the value. + WriteComplete::Written => Ok(()), + + // The value was not sent because the other end either hung up or we + // successfully canceled. In both cases return back the value here + // with the writer. + WriteComplete::Closed(val) => Err(FutureWriteCancel::Closed(val)), + WriteComplete::Cancelled(val) => Err(FutureWriteCancel::Cancelled(val, writer)), + } } } -impl Drop for FutureWriter { - fn drop(&mut self) { - super::with_entry(self.handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - Handle::LocalOpen | Handle::LocalWaiting(_) | Handle::LocalReady(..) => { - entry.insert(Handle::LocalClosed); +impl Future for FutureWrite { + type Output = Result<(), FutureWriteError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.pin_project() + .poll_complete(cx) + .map(|(result, _writer)| match result { + WriteComplete::Written => Ok(()), + WriteComplete::Closed(value) | WriteComplete::Cancelled(value) => { + Err(FutureWriteError { value }) } - Handle::Read => unreachable!(), - Handle::Write | Handle::LocalClosed => unsafe { - entry.remove(); - (self.vtable.close_writable)(self.handle, 0); - }, - Handle::WriteClosedErr(_) => match entry.remove() { - Handle::WriteClosedErr(None) => unsafe { - (self.vtable.close_writable)(self.handle, 0); - }, - Handle::WriteClosedErr(Some(err_ctx)) => unsafe { - (self.vtable.close_writable)(self.handle, err_ctx.handle()); - }, - _ => unreachable!(), - }, - }, - }); + }) } } -/// Represents a read operation which may be canceled prior to completion. -pub struct CancelableRead { - reader: Option>, - future: Pin>>>>, +impl FutureWrite { + fn pin_project(self: Pin<&mut Self>) -> Pin<&mut WaitableOperation>> { + // SAFETY: we've chosen that when `Self` is pinned that it translates to + // always pinning the inner field, so that's codified here. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().op) } + } + + /// Cancel this write if it hasn't already completed. + /// + /// This method can be used to cancel a write-in-progress and re-acquire + /// the writer and the value being sent. If the write operation has already + /// succeeded racily then `None` is returned and the write completed. + /// + /// Possible return values are: + /// + /// * `Ok(())` - the pending write completed before cancellation went + /// through meaning that the original message was actually sent. + /// * `Err(FutureWriteCancel::Closed(v))` - the pending write did not complete + /// because the other end was closed before receiving the value. The value + /// is provided back here as part of the error. + /// * `Err(FutureWriteCancel::Cancelled(v, writer))` - the pending write was + /// cancelled. The value `v` is returned back and the `writer` is returned + /// as well to resume a write in the future if desired. + /// + /// Note that if this method is called after the write was already cancelled + /// then `Ok(())` will be returned. + /// + /// # Panics + /// + /// Panics if the operation has already been completed via `Future::poll`, + /// or if this method is called twice. + pub fn cancel(self: Pin<&mut Self>) -> Result<(), FutureWriteCancel> { + self.pin_project().cancel() + } } -impl Future for CancelableRead { - type Output = Option>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll>> { - let me = self.get_mut(); - match me.future.poll_unpin(cx) { - Poll::Ready(v) => { - me.reader = None; - Poll::Ready(v) - } - Poll::Pending => Poll::Pending, - } +/// Error type in the result of [`FutureWrite`], or the error type that is a result of +/// a failure to write a future. +pub struct FutureWriteError { + /// The value that could not be sent. + pub value: T, +} + +impl fmt::Debug for FutureWriteError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FutureWriteError").finish_non_exhaustive() + } +} + +impl fmt::Display for FutureWriteError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "read end closed".fmt(f) } } -impl CancelableRead { - /// Cancel this read if it hasn't already completed, returning the original `FutureReader`. +impl std::error::Error for FutureWriteError {} + +/// Error type in the result of [`FutureWrite::cancel`], or the error type that is a +/// result of cancelling a pending write. +pub enum FutureWriteCancel { + /// The other end was closed before cancellation happened. /// - /// This method will panic if the read has already completed. - pub fn cancel(mut self) -> FutureReader { - self.cancel_mut() - } - - fn cancel_mut(&mut self) -> FutureReader { - let reader = self.reader.take().unwrap(); - let handle = reader.handle.load(Relaxed); - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen - | Handle::LocalReady(..) - | Handle::Write - | Handle::LocalClosed - | Handle::WriteClosedErr(_) => unreachable!(), - Handle::LocalWaiting(_) => { - entry.insert(Handle::LocalOpen); - } - Handle::Read => unsafe { - // TODO: spec-wise this can return `BLOCKED` which seems - // bad? - (reader.vtable.cancel_read)(handle); - }, - }, - }); - reader + /// In this case the original value is returned back to the caller but the + /// writer itself is not longer accessible as it's no longer usable. + Closed(T), + + /// The pending write was successfully cancelled and the value being written + /// is returned along with the writer to resume again in the future if + /// necessary. + Cancelled(T, FutureWriter), +} + +impl fmt::Debug for FutureWriteCancel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FutureWriteCancel").finish_non_exhaustive() } } -impl Drop for CancelableRead { - fn drop(&mut self) { - if self.reader.is_some() { - self.cancel_mut(); +impl fmt::Display for FutureWriteCancel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FutureWriteCancel::Closed(_) => "read end closed".fmt(f), + FutureWriteCancel::Cancelled(..) => "write cancelled".fmt(f), } } } +impl std::error::Error for FutureWriteCancel {} + /// Represents the readable end of a Component Model `future`. pub struct FutureReader { handle: AtomicU32, @@ -306,126 +333,177 @@ impl FutureReader { } #[doc(hidden)] - pub fn from_handle_and_vtable(handle: u32, vtable: &'static FutureVtable) -> Self { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(entry) => { - entry.insert(Handle::Read); - } - Entry::Occupied(mut entry) => match entry.get() { - Handle::Write => { - entry.insert(Handle::LocalOpen); - } - Handle::Read - | Handle::LocalOpen - | Handle::LocalReady(..) - | Handle::LocalWaiting(_) - | Handle::LocalClosed - | Handle::WriteClosedErr(_) => { - unreachable!() - } - }, - }); - - Self { - handle: AtomicU32::new(handle), - vtable, - } + pub fn take_handle(&self) -> u32 { + let ret = self.opt_handle().unwrap(); + self.handle.store(u32::MAX, Relaxed); + ret } - #[doc(hidden)] - pub fn take_handle(&self) -> u32 { - let handle = self.handle.swap(u32::MAX, Relaxed); - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen => { - entry.insert(Handle::Write); - } - Handle::Read | Handle::LocalClosed => { - entry.remove(); - } - Handle::LocalReady(..) - | Handle::LocalWaiting(_) - | Handle::Write - | Handle::WriteClosedErr(_) => unreachable!(), - }, - }); + fn handle(&self) -> u32 { + self.opt_handle().unwrap() + } - handle + fn opt_handle(&self) -> Option { + match self.handle.load(Relaxed) { + u32::MAX => None, + other => Some(other), + } } } impl IntoFuture for FutureReader { - type Output = Option>; - type IntoFuture = CancelableRead; + type Output = Option; + type IntoFuture = FutureRead; /// Convert this object into a `Future` which will resolve when a value is /// written to the writable end of this `future` (yielding a `Some` result) /// or when the writable end is dropped (yielding a `None` result). fn into_future(self) -> Self::IntoFuture { - let handle = self.handle.load(Relaxed); - let vtable = self.vtable; - CancelableRead { - reader: Some(self), - future: super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - Handle::Write | Handle::LocalWaiting(_) => { - unreachable!() - } - Handle::Read => Box::pin(async move { (vtable.read)(handle).await }) - as Pin>>, - Handle::LocalOpen => { - let (tx, rx) = oneshot::channel(); - entry.insert(Handle::LocalWaiting(tx)); - Box::pin(async move { rx.await.ok().map(|v| *v.downcast().unwrap()) }) - } - Handle::LocalClosed => Box::pin(future::ready(None)), - Handle::WriteClosedErr(err_ctx) => match err_ctx.take() { - None => Box::pin(future::ready(None)), - Some(err_ctx) => Box::pin(future::ready(Some(Err(err_ctx)))), - }, - Handle::LocalReady(..) => { - let Handle::LocalReady(v, waker) = entry.insert(Handle::LocalClosed) else { - unreachable!() - }; - waker.wake(); - Box::pin(future::ready(Some(*v.downcast().unwrap()))) - } - }, - }), + FutureRead { + op: WaitableOperation::new(self), } } } impl Drop for FutureReader { fn drop(&mut self) { - match self.handle.load(Relaxed) { - u32::MAX => {} - handle => { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - Handle::LocalReady(..) => { - let Handle::LocalReady(_, waker) = entry.insert(Handle::LocalClosed) - else { - unreachable!() - }; - waker.wake(); - } - Handle::LocalOpen | Handle::LocalWaiting(_) => { - entry.insert(Handle::LocalClosed); - } - Handle::Read | Handle::LocalClosed => unsafe { - entry.remove(); - // TODO: expose `0` here as an error context in the - // API (or auto-fill-in? unsure). - (self.vtable.close_readable)(handle, 0); - }, - Handle::Write | Handle::WriteClosedErr(_) => unreachable!(), - }, - }); - } + let Some(handle) = self.opt_handle() else { + return; + }; + unsafe { + (self.vtable.close_readable)(handle); } } } + +/// Represents a read operation which may be canceled prior to completion. +pub struct FutureRead { + op: WaitableOperation>, +} + +struct FutureReadOp(marker::PhantomData); + +enum ReadComplete { + Value(T), + Closed, + Cancelled, +} + +unsafe impl WaitableOp for FutureReadOp +where + T: 'static, +{ + type Start = FutureReader; + type InProgress = (FutureReader, Option); + type Result = (ReadComplete, FutureReader); + type Cancel = Result, FutureReader>; + + fn start(reader: Self::Start) -> (u32, Self::InProgress) { + let (ptr, cleanup) = Cleanup::new(reader.vtable.layout); + // SAFETY: `ptr` is allocated with `vtable.layout` and should be + // safe to use here. Its lifetime for the async operation is hinged on + // `WaitableOperation` being safe. + let code = unsafe { (reader.vtable.start_read)(reader.handle(), ptr) }; + (code, (reader, cleanup)) + } + + fn start_cancel(state: Self::Start) -> Self::Cancel { + Err(state) + } + + /// The read has completed, so lift the value from the stored memory and + /// `cleanup` naturally falls out of scope after transferring ownership of + /// everything to the returned `value`. + fn in_progress_complete((reader, cleanup): Self::InProgress, amt: u32) -> Self::Result { + assert_eq!(amt, 1); + let ptr = cleanup + .as_ref() + .map(|c| c.ptr.as_ptr()) + .unwrap_or(ptr::null_mut()); + + // SAFETY: we're the ones managing `ptr` so we know it's safe to + // pass here. + let value = unsafe { (reader.vtable.lift)(ptr) }; + (ReadComplete::Value(value), reader) + } + + /// The read didn't complete, so `_cleanup` is still uninitialized, so let + /// it fall out of scope. + fn in_progress_closed((reader, _cleanup): Self::InProgress) -> Self::Result { + (ReadComplete::Closed, reader) + } + + fn in_progress_waitable((reader, _): &Self::InProgress) -> u32 { + reader.handle() + } + + fn in_progress_cancel((reader, _): &Self::InProgress) -> u32 { + // SAFETY: we're managing `reader` and all the various operational bits, + // so this relies on `WaitableOperation` being safe. + unsafe { (reader.vtable.cancel_read)(reader.handle()) } + } + + /// Like `in_progress_closed` the read operation has finished but without a + /// value, so let `_cleanup` fall out of scope to clean up its allocation. + fn in_progress_canceled((reader, _cleanup): Self::InProgress) -> Self::Result { + (ReadComplete::Cancelled, reader) + } + + fn result_into_cancel((value, reader): Self::Result) -> Self::Cancel { + match value { + // The value was actually read, so thread that through here. + ReadComplete::Value(value) => Ok(Some(value)), + + // The read was successfully cancelled, so thread through the + // `reader` to possibly restart later on. + ReadComplete::Cancelled => Err(reader), + + // The other end was closed, so this can't possibly ever complete + // again, so thread that through. + ReadComplete::Closed => Ok(None), + } + } +} + +impl Future for FutureRead { + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.pin_project() + .poll_complete(cx) + .map(|(result, _reader)| match result { + ReadComplete::Value(val) => Some(val), + ReadComplete::Cancelled | ReadComplete::Closed => None, + }) + } +} + +impl FutureRead { + fn pin_project(self: Pin<&mut Self>) -> Pin<&mut WaitableOperation>> { + // SAFETY: we've chosen that when `Self` is pinned that it translates to + // always pinning the inner field, so that's codified here. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().op) } + } + + /// Cancel this read if it hasn't already completed. + /// + /// Return values include: + /// + /// * `Ok(Some(value))` - future completed before this cancellation request + /// was received. + /// * `Ok(None)` - future closed before this cancellation request was + /// received. + /// * `Err(reader)` - read operation was cancelled and it can be retried in + /// the future if desired. + /// + /// Note that if this method is called after the write was already cancelled + /// then `Ok(None)` will be returned. + /// + /// # Panics + /// + /// Panics if the operation has already been completed via `Future::poll`, + /// or if this method is called twice. + pub fn cancel(self: Pin<&mut Self>) -> Result, FutureReader> { + self.pin_project().cancel() + } +} diff --git a/crates/guest-rust/rt/src/async_support/stream_support.rs b/crates/guest-rust/rt/src/async_support/stream_support.rs index 8fd420b87..b6626b28d 100644 --- a/crates/guest-rust/rt/src/async_support/stream_support.rs +++ b/crates/guest-rust/rt/src/async_support/stream_support.rs @@ -1,45 +1,33 @@ -extern crate std; - +use crate::async_support::waitable::{WaitableOp, WaitableOperation}; +use crate::async_support::AbiBuffer; use { - super::ErrorContext, - super::Handle, - futures::{ - channel::oneshot, - future::{self, FutureExt}, - sink::Sink, - stream::Stream, - }, + crate::Cleanup, std::{ - boxed::Box, - collections::hash_map::Entry, - convert::Infallible, + alloc::Layout, fmt, future::Future, - iter, - mem::{self, MaybeUninit}, + marker, pin::Pin, + ptr, sync::atomic::{AtomicU32, Ordering::Relaxed}, task::{Context, Poll}, vec::Vec, }, }; -fn ceiling(x: usize, y: usize) -> usize { - (x / y) + if x % y == 0 { 0 } else { 1 } -} - #[doc(hidden)] pub struct StreamVtable { - pub write: fn(future: u32, values: &[T]) -> Pin + '_>>, - pub read: fn( - future: u32, - values: &mut [MaybeUninit], - ) -> Pin>> + '_>>, - pub cancel_write: unsafe extern "C" fn(future: u32) -> u32, - pub cancel_read: unsafe extern "C" fn(future: u32) -> u32, - pub close_writable: unsafe extern "C" fn(future: u32, err_ctx: u32), - pub close_readable: unsafe extern "C" fn(future: u32, err_ctx: u32), - pub new: unsafe extern "C" fn() -> u32, + pub layout: Layout, + pub lower: Option, + pub dealloc_lists: Option, + pub lift: Option T>, + pub start_write: unsafe extern "C" fn(stream: u32, val: *const u8, amt: usize) -> u32, + pub start_read: unsafe extern "C" fn(stream: u32, val: *mut u8, amt: usize) -> u32, + pub cancel_write: unsafe extern "C" fn(stream: u32) -> u32, + pub cancel_read: unsafe extern "C" fn(stream: u32) -> u32, + pub close_writable: unsafe extern "C" fn(stream: u32), + pub close_readable: unsafe extern "C" fn(stream: u32), + pub new: unsafe extern "C" fn() -> u64, } /// Helper function to create a new read/write pair for a component model @@ -47,87 +35,75 @@ pub struct StreamVtable { pub unsafe fn stream_new( vtable: &'static StreamVtable, ) -> (StreamWriter, StreamReader) { - let handle = unsafe { (vtable.new)() }; - super::with_entry(handle, |entry| match entry { - Entry::Vacant(entry) => { - entry.insert(Handle::LocalOpen); - } - Entry::Occupied(_) => unreachable!(), - }); - ( - StreamWriter::new(handle, vtable), - StreamReader::new(handle, vtable), - ) -} -struct CancelWriteOnDrop { - handle: Option, - vtable: &'static StreamVtable, -} - -impl Drop for CancelWriteOnDrop { - fn drop(&mut self) { - if let Some(handle) = self.handle.take() { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen - | Handle::LocalWaiting(_) - | Handle::Read - | Handle::LocalClosed - | Handle::WriteClosedErr(_) => unreachable!(), - Handle::LocalReady(..) => { - entry.insert(Handle::LocalOpen); - } - Handle::Write => unsafe { - // TODO: spec-wise this can return `BLOCKED` which seems - // bad? - (self.vtable.cancel_write)(handle); - }, - }, - }); - } + unsafe { + let handles = (vtable.new)(); + ( + StreamWriter::new(handles as u32, vtable), + StreamReader::new((handles >> 32) as u32, vtable), + ) } } /// Represents the writable end of a Component Model `stream`. pub struct StreamWriter { handle: u32, - future: Option + 'static>>>, vtable: &'static StreamVtable, } impl StreamWriter { #[doc(hidden)] - pub fn new(handle: u32, vtable: &'static StreamVtable) -> Self { - Self { - handle, - future: None, - vtable, + pub unsafe fn new(handle: u32, vtable: &'static StreamVtable) -> Self { + Self { handle, vtable } + } + + /// Initiate a write of the `values` provided into this stream. + /// + /// This method will initiate a single write of the `values` provided. Upon + /// completion the values will be yielded back as an [`AbiBuffer`] which + /// manages intermediate state. That can be used to resume after a partial + /// write or re-acquire the underlying storage. + pub fn write(&mut self, values: Vec) -> StreamWrite<'_, T> { + self.write_buf(AbiBuffer::new(values, self.vtable)) + } + + /// Same as [`StreamWriter::write`], except this takes [`AbiBuffer`] + /// instead of `Vec`. + pub fn write_buf(&mut self, values: AbiBuffer) -> StreamWrite<'_, T> { + StreamWrite { + op: WaitableOperation::new((self, values)), } } - /// Cancel the current pending write operation. + /// Writes all of the `values` provided into this stream. /// - /// This will panic if no such operation is pending. - pub fn cancel(&mut self) { - assert!(self.future.is_some()); - self.future = None; + /// This is a higher-level method than [`StreamWriter::write`] and does not + /// expose cancellation for example. This will successively attempt to write + /// all of `values` provided into this stream. Upon completion the same + /// vector will be returned and any remaining elements in the vector were + /// not sent because the stream was closed. + pub async fn write_all(&mut self, values: Vec) -> Vec { + let (mut status, mut buf) = self.write(values).await; + while let StreamResult::Complete(_) = status { + if buf.remaining() == 0 { + break; + } + (status, buf) = self.write_buf(buf).await; + } + assert!(buf.remaining() == 0 || matches!(status, StreamResult::Closed)); + buf.into_vec() } - /// Close the writer with an error that will be returned as the last value + /// Writes the singular `value` provided /// - /// Note that this error is not sent immediately, but only when the - /// writer closes, which is normally a result of a `drop()` - pub fn close_with_error(self, err: ErrorContext) { - super::with_entry(self.handle, move |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - _ => { - // Note: the impending drop after this function runs should trigger - entry.insert(Handle::WriteClosedErr(Some(err))); - } - }, - }); + /// This is a higher-level method than [`StreamWriter::write`] and does not + /// expose cancellation for example. This will attempt to send `value` on + /// this stream. + /// + /// If the other end hangs up then the value is returned back as + /// `Some(value)`, otherwise `None` is returned indicating the value was + /// sent. + pub async fn write_one(&mut self, value: T) -> Option { + self.write_all(std::vec![value]).await.pop() } } @@ -139,178 +115,122 @@ impl fmt::Debug for StreamWriter { } } -impl Sink> for StreamWriter { - type Error = Infallible; +impl Drop for StreamWriter { + fn drop(&mut self) { + unsafe { + (self.vtable.close_writable)(self.handle); + } + } +} - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let me = self.get_mut(); +/// Represents a write operation which may be canceled prior to completion. +pub struct StreamWrite<'a, T: 'static> { + op: WaitableOperation>, +} - if let Some(future) = &mut me.future { - match future.as_mut().poll(cx) { - Poll::Ready(_) => { - me.future = None; - Poll::Ready(Ok(())) - } - Poll::Pending => Poll::Pending, - } - } else { - Poll::Ready(Ok(())) - } +struct StreamWriteOp<'a, T: 'static>(marker::PhantomData<(&'a mut StreamWriter, T)>); + +/// Result of a [`StreamWriter::write`] or [`StreamReader::read`] operation, +/// yielded by the [`StreamWrite`] or [`StreamRead`] futures. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum StreamResult { + /// The provided number of values were successfully transferred. + /// + /// For writes this is how many items were written, and for reads this is + /// how many items were read. + Complete(usize), + /// No values were written, the other end has closed its handle. + Closed, + /// No values were written, the operation was cancelled. + Cancelled, +} + +unsafe impl<'a, T> WaitableOp for StreamWriteOp<'a, T> +where + T: 'static, +{ + type Start = (&'a mut StreamWriter, AbiBuffer); + type InProgress = (&'a mut StreamWriter, AbiBuffer); + type Result = (StreamResult, AbiBuffer); + type Cancel = (StreamResult, AbiBuffer); + + fn start((writer, buf): Self::Start) -> (u32, Self::InProgress) { + let (ptr, len) = buf.abi_ptr_and_len(); + // SAFETY: sure hope this is safe, everything in this module and + // `AbiBuffer` is trying to make this safe. + let code = unsafe { (writer.vtable.start_write)(writer.handle, ptr, len) }; + (code, (writer, buf)) } - fn start_send(self: Pin<&mut Self>, item: Vec) -> Result<(), Self::Error> { - assert!(self.future.is_none()); - super::with_entry(self.handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen => { - let handle = self.handle; - let mut item = Some(item); - let mut cancel_on_drop = Some(CancelWriteOnDrop:: { - handle: Some(handle), - vtable: self.vtable, - }); - self.get_mut().future = Some(Box::pin(future::poll_fn(move |cx| { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen => { - if let Some(item) = item.take() { - entry.insert(Handle::LocalReady( - Box::new(item), - cx.waker().clone(), - )); - Poll::Pending - } else { - cancel_on_drop.take().unwrap().handle = None; - Poll::Ready(()) - } - } - Handle::LocalReady(..) => Poll::Pending, - Handle::LocalClosed | Handle::WriteClosedErr(_) => { - cancel_on_drop.take().unwrap().handle = None; - Poll::Ready(()) - } - Handle::LocalWaiting(_) | Handle::Read | Handle::Write => { - unreachable!() - } - }, - }) - }))); - } - Handle::LocalWaiting(_) => { - let Handle::LocalWaiting(tx) = entry.insert(Handle::LocalOpen) else { - unreachable!() - }; - _ = tx.send(Box::new(item)); - } - Handle::LocalClosed | Handle::WriteClosedErr(_) => (), - Handle::Read | Handle::LocalReady(..) => unreachable!(), - Handle::Write => { - let handle = self.handle; - let vtable = self.vtable; - let mut cancel_on_drop = CancelWriteOnDrop:: { - handle: Some(handle), - vtable, - }; - self.get_mut().future = Some(Box::pin(async move { - (vtable.write)(handle, &item).await; - cancel_on_drop.handle = None; - drop(cancel_on_drop); - })); - } - }, - }); - Ok(()) + fn start_cancel((_writer, buf): Self::Start) -> Self::Cancel { + (StreamResult::Cancelled, buf) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.poll_ready(cx) + fn in_progress_complete((_writer, mut buf): Self::InProgress, amt: u32) -> Self::Result { + let amt = amt.try_into().unwrap(); + buf.advance(amt); + (StreamResult::Complete(amt), buf) } - fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.poll_ready(cx) + fn in_progress_closed((_writer, buf): Self::InProgress) -> Self::Result { + (StreamResult::Closed, buf) } -} -impl Drop for StreamWriter { - fn drop(&mut self) { - self.future = None; - super::with_entry(self.handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - Handle::LocalOpen | Handle::LocalWaiting(_) | Handle::LocalReady(..) => { - entry.insert(Handle::LocalClosed); - } - Handle::Read => unreachable!(), - Handle::Write | Handle::LocalClosed => unsafe { - entry.remove(); - (self.vtable.close_writable)(self.handle, 0); - }, - Handle::WriteClosedErr(_) => match entry.remove() { - // Care is taken to avoid dropping the ErrorContext before close_writable is called. - // If the error context is dropped prematurely, the component may garbage collect - // the error context before it can be used/referenced by close_writable(). - Handle::WriteClosedErr(Some(e)) => unsafe { - (self.vtable.close_writable)(self.handle, e.handle) - }, - Handle::WriteClosedErr(None) => unsafe { - (self.vtable.close_writable)(self.handle, 0) - }, - _ => unreachable!(), - }, - }, - }); + fn in_progress_waitable((writer, _): &Self::InProgress) -> u32 { + writer.handle + } + + fn in_progress_cancel((writer, _): &Self::InProgress) -> u32 { + // SAFETY: we're managing `writer` and all the various operational bits, + // so this relies on `WaitableOperation` being safe. + unsafe { (writer.vtable.cancel_write)(writer.handle) } + } + + fn in_progress_canceled((_writer, buf): Self::InProgress) -> Self::Result { + (StreamResult::Cancelled, buf) + } + + fn result_into_cancel(result: Self::Result) -> Self::Cancel { + result } } -struct CancelReadOnDrop { - handle: Option, - vtable: &'static StreamVtable, +impl Future for StreamWrite<'_, T> { + type Output = (StreamResult, AbiBuffer); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.pin_project().poll_complete(cx) + } } -impl Drop for CancelReadOnDrop { - fn drop(&mut self) { - if let Some(handle) = self.handle.take() { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen - | Handle::LocalReady(..) - | Handle::Write - | Handle::LocalClosed - | Handle::WriteClosedErr(_) => unreachable!(), - Handle::LocalWaiting(_) => { - entry.insert(Handle::LocalOpen); - } - Handle::Read => unsafe { - // TODO: spec-wise this can return `BLOCKED` which seems - // bad? - (self.vtable.cancel_read)(handle); - }, - }, - }); - } +impl<'a, T: 'static> StreamWrite<'a, T> { + fn pin_project(self: Pin<&mut Self>) -> Pin<&mut WaitableOperation>> { + // SAFETY: we've chosen that when `Self` is pinned that it translates to + // always pinning the inner field, so that's codified here. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().op) } + } + + /// Cancel this write if it hasn't already completed. + /// + /// This method can be used to cancel a write-in-progress and re-acquire + /// values being sent. Note that the result here may still indicate that + /// some values were written if the race to cancel the write was lost. + /// + /// # Panics + /// + /// Panics if the operation has already been completed via `Future::poll`, + /// or if this method is called twice. + pub fn cancel(self: Pin<&mut Self>) -> (StreamResult, AbiBuffer) { + self.pin_project().cancel() } } /// Represents the readable end of a Component Model `stream`. pub struct StreamReader { handle: AtomicU32, - future: Option, ErrorContext>>> + 'static>>>, vtable: &'static StreamVtable, } -impl StreamReader { - /// Cancel the current pending read operation. - /// - /// This will panic if no such operation is pending. - pub fn cancel(&mut self) { - assert!(self.future.is_some()); - self.future = None; - } -} - impl fmt::Debug for StreamReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("StreamReader") @@ -324,177 +244,226 @@ impl StreamReader { pub fn new(handle: u32, vtable: &'static StreamVtable) -> Self { Self { handle: AtomicU32::new(handle), - future: None, vtable, } } #[doc(hidden)] - pub unsafe fn from_handle_and_vtable(handle: u32, vtable: &'static StreamVtable) -> Self { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(entry) => { - entry.insert(Handle::Read); - } - Entry::Occupied(mut entry) => match entry.get() { - Handle::Write => { - entry.insert(Handle::LocalOpen); - } - Handle::Read - | Handle::LocalOpen - | Handle::LocalReady(..) - | Handle::LocalWaiting(_) - | Handle::LocalClosed - | Handle::WriteClosedErr(_) => { - unreachable!() - } - }, - }); - - Self { - handle: AtomicU32::new(handle), - future: None, - vtable, - } + pub fn take_handle(&self) -> u32 { + let ret = self.opt_handle().unwrap(); + self.handle.store(u32::MAX, Relaxed); + ret } - #[doc(hidden)] - pub fn take_handle(&self) -> u32 { - let handle = self.handle.swap(u32::MAX, Relaxed); - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get() { - Handle::LocalOpen => { - entry.insert(Handle::Write); - } - Handle::Read | Handle::LocalClosed => { - entry.remove(); - } - Handle::LocalReady(..) - | Handle::LocalWaiting(_) - | Handle::Write - | Handle::WriteClosedErr(_) => unreachable!(), - }, - }); + fn handle(&self) -> u32 { + self.opt_handle().unwrap() + } - handle + fn opt_handle(&self) -> Option { + match self.handle.load(Relaxed) { + u32::MAX => None, + other => Some(other), + } } -} -impl Stream for StreamReader { - type Item = Result, ErrorContext>; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let me = self.get_mut(); - - if me.future.is_none() { - me.future = Some(super::with_entry( - me.handle.load(Relaxed), - |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - Handle::Write | Handle::LocalWaiting(_) => { - unreachable!() - } - Handle::Read => { - let handle = me.handle.load(Relaxed); - let vtable = me.vtable; - let mut cancel_on_drop = CancelReadOnDrop:: { - handle: Some(handle), - vtable, - }; - Box::pin(async move { - let mut buffer = iter::repeat_with(MaybeUninit::uninit) - .take(ceiling(64 * 1024, mem::size_of::().max(1))) - .collect::>(); - - let result = match (vtable.read)(handle, &mut buffer).await { - Some(Ok(count)) => { - buffer.truncate(count); - Some(Ok(unsafe { - mem::transmute::>, Vec>(buffer) - })) - } - Some(Err(err)) => Some(Err(err)), - None => None, - }; - cancel_on_drop.handle = None; - drop(cancel_on_drop); - result - }) as Pin>> - } - Handle::LocalOpen => { - let (tx, rx) = oneshot::channel(); - entry.insert(Handle::LocalWaiting(tx)); - let mut cancel_on_drop = CancelReadOnDrop:: { - handle: Some(me.handle.load(Relaxed)), - vtable: me.vtable, - }; - Box::pin(async move { - let result = - rx.map(|v| v.ok().map(|v| *v.downcast().unwrap())).await; - cancel_on_drop.handle = None; - drop(cancel_on_drop); - result - }) - } - Handle::LocalClosed => Box::pin(future::ready(None)), - Handle::WriteClosedErr(err_ctx) => match err_ctx.take() { - None => Box::pin(future::ready(None)), - Some(err_ctx) => Box::pin(future::ready(Some(Err(err_ctx)))), - }, - Handle::LocalReady(..) => { - let Handle::LocalReady(v, waker) = entry.insert(Handle::LocalOpen) - else { - unreachable!() - }; - waker.wake(); - Box::pin(future::ready(Some(*v.downcast().unwrap()))) - } - }, - }, - )); + /// Starts a new read operation on this stream into `buf`. + /// + /// This method will read values into the spare capacity of the `buf` + /// provided. If `buf` has no spare capacity then this will be equivalent + /// to a zero-length read. + /// + /// Upon completion the `buf` will be yielded back to the caller via the + /// completion of the [`StreamRead`] future. + pub fn read(&mut self, buf: Vec) -> StreamRead<'_, T> { + StreamRead { + op: WaitableOperation::new((self, buf)), } + } - match me.future.as_mut().unwrap().as_mut().poll(cx) { - Poll::Ready(v) => { - me.future = None; - Poll::Ready(v) + /// Reads a single item from this stream. + /// + /// This is a higher-level method than [`StreamReader::read`] in that it + /// reads only a single item and does not expose control over cancellation. + pub async fn next(&mut self) -> Option { + // TODO: should amortize this allocation and avoid doing it every time. + // Or somehow perhaps make this more optimal. + let (_result, mut buf) = self.read(Vec::with_capacity(1)).await; + buf.pop() + } + + /// Reads all items from this stream and returns the list. + /// + /// This method will read all remaining items from this stream into a list + /// and await the stream to be closed. + pub async fn collect(mut self) -> Vec { + let mut ret = Vec::new(); + loop { + if ret.len() == ret.capacity() { + ret.reserve(1); + } + let (status, buf) = self.read(ret).await; + ret = buf; + match status { + StreamResult::Complete(_) => {} + StreamResult::Closed => break, + StreamResult::Cancelled => unreachable!(), } - Poll::Pending => Poll::Pending, } + ret } } impl Drop for StreamReader { fn drop(&mut self) { - self.future = None; + let Some(handle) = self.opt_handle() else { + return; + }; + unsafe { + (self.vtable.close_readable)(handle); + } + } +} - match self.handle.load(Relaxed) { - u32::MAX => {} - handle => { - super::with_entry(handle, |entry| match entry { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut entry) => match entry.get_mut() { - Handle::LocalReady(..) => { - let Handle::LocalReady(_, waker) = entry.insert(Handle::LocalClosed) - else { - unreachable!() - }; - waker.wake(); - } - Handle::LocalOpen | Handle::LocalWaiting(_) => { - entry.insert(Handle::LocalClosed); - } - Handle::Read | Handle::LocalClosed => unsafe { - entry.remove(); - // TODO: expose `0` here as an error context in the - // API (or auto-fill-in? unsure). - (self.vtable.close_readable)(handle, 0); - }, - Handle::Write | Handle::WriteClosedErr(_) => unreachable!(), - }, - }); +/// Represents a read operation which may be canceled prior to completion. +pub struct StreamRead<'a, T: 'static> { + op: WaitableOperation>, +} + +struct StreamReadOp<'a, T: 'static>(marker::PhantomData<(&'a mut StreamReader, T)>); + +unsafe impl<'a, T> WaitableOp for StreamReadOp<'a, T> +where + T: 'static, +{ + type Start = (&'a mut StreamReader, Vec); + type InProgress = (&'a mut StreamReader, Vec, Option); + type Result = (StreamResult, Vec); + type Cancel = (StreamResult, Vec); + + fn start((reader, mut buf): Self::Start) -> (u32, Self::InProgress) { + let cap = buf.spare_capacity_mut(); + let ptr; + let cleanup; + // If `T` requires a lifting operation, then allocate a slab of memory + // which will store the canonical ABI read. Otherwise we can use the + // raw capacity in `buf` itself. + if reader.vtable.lift.is_some() { + let layout = Layout::from_size_align( + reader.vtable.layout.size() * cap.len(), + reader.vtable.layout.align(), + ) + .unwrap(); + (ptr, cleanup) = Cleanup::new(layout); + } else { + ptr = cap.as_mut_ptr().cast(); + cleanup = None; + } + // SAFETY: `ptr` is either in `buf` or in `cleanup`, both of which will + // persist with this async operation itself. + let code = unsafe { (reader.vtable.start_read)(reader.handle(), ptr, cap.len()) }; + (code, (reader, buf, cleanup)) + } + + fn start_cancel((_, buf): Self::Start) -> Self::Cancel { + std::dbg!("start_cancel"); + (StreamResult::Cancelled, buf) + } + + fn in_progress_complete( + (reader, mut buf, cleanup): Self::InProgress, + amt: u32, + ) -> Self::Result { + let amt = usize::try_from(amt).unwrap(); + let cur_len = buf.len(); + assert!(amt <= buf.capacity() - cur_len); + + match reader.vtable.lift { + // With a `lift` operation this now requires reading `amt` items + // from `cleanup` and pushing them into `buf`. + Some(lift) => { + let mut ptr = cleanup + .as_ref() + .map(|c| c.ptr.as_ptr()) + .unwrap_or(ptr::null_mut()); + for _ in 0..amt { + unsafe { + buf.push(lift(ptr)); + ptr = ptr.add(reader.vtable.layout.size()); + } + } } + + // If no `lift` was necessary, then the results of this operation + // were read directly into `buf`, so just update its length now that + // values have been initialized. + None => unsafe { buf.set_len(cur_len + amt) }, } + + // Intentionally dispose of `cleanup` here as, if it was used, all + // allocations have been read from it and appended to `buf`. + drop(cleanup); + (StreamResult::Complete(amt), buf) + } + + /// Like `in_progress_canceled` below, discard the temporary cleanup + /// allocation, if any. + fn in_progress_closed((_reader, buf, _cleanup): Self::InProgress) -> Self::Result { + (StreamResult::Closed, buf) + } + + fn in_progress_waitable((reader, ..): &Self::InProgress) -> u32 { + reader.handle() + } + + fn in_progress_cancel((reader, ..): &Self::InProgress) -> u32 { + // SAFETY: we're managing `reader` and all the various operational bits, + // so this relies on `WaitableOperation` being safe. + unsafe { (reader.vtable.cancel_read)(reader.handle()) } + } + + /// When an in-progress read is successfully cancel then the allocation + /// that was being read into, if any, is just discarded. + /// + /// TODO: should maybe thread this around like `AbiBuffer` to cache the + /// read allocation? + fn in_progress_canceled((_reader, buf, _cleanup): Self::InProgress) -> Self::Result { + (StreamResult::Cancelled, buf) + } + + fn result_into_cancel(result: Self::Result) -> Self::Cancel { + result + } +} + +impl Future for StreamRead<'_, T> { + type Output = (StreamResult, Vec); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.pin_project().poll_complete(cx) + } +} + +impl<'a, T> StreamRead<'a, T> { + fn pin_project(self: Pin<&mut Self>) -> Pin<&mut WaitableOperation>> { + // SAFETY: we've chosen that when `Self` is pinned that it translates to + // always pinning the inner field, so that's codified here. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().op) } + } + + /// Cancel this read if it hasn't already completed. + /// + /// This method will initiate a cancellation operation for this active + /// read. This may race with the actual read itself and so this may actually + /// complete with some results. + /// + /// The final result of cancellation is returned, along with the original + /// buffer. + /// + /// # Panics + /// + /// Panics if the operation has already been completed via `Future::poll`, + /// or if this method is called twice. + pub fn cancel(self: Pin<&mut Self>) -> (StreamResult, Vec) { + todo!() } } diff --git a/crates/guest-rust/rt/src/async_support/waitable.rs b/crates/guest-rust/rt/src/async_support/waitable.rs new file mode 100644 index 000000000..4dbf3958a --- /dev/null +++ b/crates/guest-rust/rt/src/async_support/waitable.rs @@ -0,0 +1,331 @@ +//! Generic support for "any waitable" and performing asynchronous operations on +//! that waitable. + +use super::results; +use std::marker; +use std::mem; +use std::pin::Pin; +use std::task::{Context, Poll}; + +pub struct WaitableOperation { + state: WaitableOperationState, + completion_status: CompletionStatus, +} + +struct CompletionStatus { + code: Option, + _pinned: marker::PhantomPinned, +} + +/// Helper trait to be used with `WaitableOperation` to assist with machinery +/// necessary to track in-flight reads/writes on futures. +pub unsafe trait WaitableOp { + /// Initial state of this operation, used to kick off the actual component + /// model operation and transition to `InProgress`. + type Start; + + /// Intermediate state of this operation when the component model is + /// involved but it hasn't resolved just yet. + type InProgress; + + /// Result type of this operation. + type Result; + + /// Result of when this operation is cancelled. + type Cancel; + + /// Starts the async operation. + /// + /// This method will actually call `{future,stream}.{read,write}` with + /// `state` provided. The return code of the intrinsic is returned here + /// along with the `InProgress` state. + fn start(state: Self::Start) -> (u32, Self::InProgress); + + /// Conversion from the "start" state to the "cancel" result, needed when an + /// operation is cancelled before it's started. + fn start_cancel(state: Self::Start) -> Self::Cancel; + + /// Completion callback for when an in-progress operation has completed + /// successfully after transferring `amt` items. + fn in_progress_complete(state: Self::InProgress, amt: u32) -> Self::Result; + + /// Completion callback for when an in-progress operation has completed + /// without actually transferring anything because the other end has closed. + fn in_progress_closed(state: Self::InProgress) -> Self::Result; + + /// Acquires the component-model `waitable` index that the `InProgress` + /// state is waiting on. + fn in_progress_waitable(state: &Self::InProgress) -> u32; + + /// Initiates a request for cancellation of this operation. Returns the + /// status code returned by the `{future,stream}.cancel-{read,write}` + /// intrinsic. + fn in_progress_cancel(state: &Self::InProgress) -> u32; + + /// Completion callback for when an operation was canceled. + /// + /// This is invoked after `in_progress_cancel` is used and the returned + /// status code indicates that the operation was indeed cancelled and didn't + /// racily return some other result. + fn in_progress_canceled(state: Self::InProgress) -> Self::Result; + + /// Converts a "completion result" into a "cancel result". This is necessary + /// when an in-progress operation is cancelled so the in-progress result is + /// first acquired and then transitioned to a cancel request. + fn result_into_cancel(result: Self::Result) -> Self::Cancel; +} + +enum WaitableOperationState { + Start(S::Start), + InProgress(S::InProgress), + Done, +} + +impl WaitableOperation +where + S: WaitableOp, +{ + /// Creates a new operation in the initial state. + pub fn new(state: S::Start) -> WaitableOperation { + WaitableOperation { + state: WaitableOperationState::Start(state), + completion_status: CompletionStatus { + code: None, + _pinned: marker::PhantomPinned, + }, + } + } + + fn pin_project( + self: Pin<&mut Self>, + ) -> (&mut WaitableOperationState, Pin<&mut CompletionStatus>) { + // SAFETY: this is the one method used to project from `Pin<&mut Self>` + // to the fields, and the contract we're deciding on is that + // `state` is never pinned but the `CompletionStatus` is. That's used + // to share a raw pointer with the completion callback with + // respect to `Option` internally. + unsafe { + let me = self.get_unchecked_mut(); + (&mut me.state, Pin::new_unchecked(&mut me.completion_status)) + } + } + + /// Registers a completion of `waitable` within the current task's future to: + /// + /// * Fill in `completion_status` with the result of a completion event. + /// * Call `cx.waker().wake()`. + pub fn register_waker(self: Pin<&mut Self>, waitable: u32, cx: &mut Context) { + let (_, completion_status) = self.pin_project(); + unsafe { + (*super::CURRENT).add_waitable(waitable); + (*super::CURRENT) + .wakers + .insert(waitable, (cx.waker().clone(), completion_status.code_mut())); + } + } + + /// Deregisters the corresponding `register_waker` within the current task + /// for the `waitable` passed here. + /// + /// This relinquishes control of the original `completion_status` pointer + /// passed to `register_waker` after this call has completed. + pub fn unregister_waker(self: Pin<&mut Self>, waitable: u32) { + unsafe { + (*super::CURRENT).remove_waitable(waitable); + let prev = (*super::CURRENT).wakers.remove(&waitable); + assert!(prev.is_some()); + } + } + + /// Polls this operation to see if it has completed yet. + /// + /// This is intended to be used within `Future::poll`. + pub fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + use WaitableOperationState::*; + + let (state, completion_status) = self.as_mut().pin_project(); + + // First up, determine the completion status, if any, that's available. + let optional_code = match state { + // If this operation hasn't actually started yet then now's the + // time to start it. + Start(_) => { + let Start(s) = mem::replace(state, Done) else { + unreachable!() + }; + let (code, s) = S::start(s); + *state = InProgress(s); + match code { + // The operation is blocked, meaning it didn't complete. + // + // We've already transitioned to the in-progress state so + // all that's left to do is indicate that we don't have a + // return code at this time. + results::BLOCKED => None, + + // This operation completed immediately. + // + // As above we're in the in-progress state, so defer what to do + // with this code to down below. + other => Some(other), + } + } + + // This operation was previously queued so we're just waiting on + // the completion to come in. Read the completion status and + // interpret it down below. + // + // Note that it's the responsibility of the completion callback at + // the ABI level that we install to fill in this pointer, e.g. it's + // part of the `register_waker` contract. + InProgress(_) => completion_status.code, + + // This write has already completed, it's a Rust-level API violation + // to call this function again. + Done => panic!("cannot re-poll after operation completes"), + }; + + self.poll_complete_with_code(Some(cx), optional_code) + } + + /// After acquiring the current return of this operation in `optional_code`, + /// figures out what to do with it. + /// + /// The `cx` argument is optional to do nothing in the case that + /// `optional_code` is not present. + fn poll_complete_with_code( + mut self: Pin<&mut Self>, + cx: Option<&mut Context>, + optional_code: Option, + ) -> Poll { + use WaitableOperationState::*; + + let (state, _completion_status) = self.as_mut().pin_project(); + let in_progress = match state { + InProgress(s) => s, + // programmer error if this is called in the wrong state. + _ => unreachable!(), + }; + + let code = match optional_code { + Some(code) => code, + + // The operation is still in progress. + // + // Register the `cx.waker()` to get notified when `writer.handle` + // receives its completion. + None => { + if let Some(cx) = cx { + let handle = S::in_progress_waitable(in_progress); + self.register_waker(handle, cx); + } + return Poll::Pending; + } + }; + + // After this point we're guaranteed the operation has completed, so + // it's time to interpret the result and return. + let InProgress(in_progress) = mem::replace(state, Done) else { + unreachable!() + }; + + match code { + // The other end has closed or the operation was cancelled and the + // operation did not complete. See what `S` has to say about that. + results::CLOSED => Poll::Ready(S::in_progress_closed(in_progress)), + results::CANCELED => Poll::Ready(S::in_progress_canceled(in_progress)), + + // This operation has completed, transferring `n` units of memory. + // + // Forward this information to `S` and see what it has to say about + // that. + n => Poll::Ready(S::in_progress_complete(in_progress, n)), + } + } + + /// Cancels the in-flight operation, if it's still in-flight, and sees what + /// happened. + /// + /// Defers to `S` how to communicate the current status through the + /// cancellation type. + /// + /// # Panics + /// + /// Panics if the operation has already been completed via `poll_complete` + /// above. + /// Panics if this method is called twice. + pub fn cancel(mut self: Pin<&mut Self>) -> S::Cancel { + use WaitableOperationState::*; + + let (state, _) = self.as_mut().pin_project(); + let in_progress = match state { + // This operation was never actually started, so there's no need to + // cancel anything, just pull out the value and return it. + Start(_) => { + let Start(s) = mem::replace(state, Done) else { + unreachable!() + }; + return S::start_cancel(s); + } + + // This operation is actively in progress, fall through to below. + InProgress(s) => s, + + // This operation was already completed after a `poll_complete` + // above advanced to the `Done` state, or this was cancelled twice. + // In such situations this is a programmer error to call this + // method, so panic. + Done => panic!("cannot cancel operation after completing it"), + }; + + // This operation is currently actively in progress after being queued + // up in the past. In this situation we need to call + // `{future,stream}.cancel-{read,write}`. First ensure that our + // exported task's state is no longer interested in the write handle + // here, so unregister that. Next if a completion hasn't already come + // in due to some race then perform the actual cancellation here. + let waitable = S::in_progress_waitable(in_progress); + self.as_mut().unregister_waker(waitable); + let (InProgress(in_progress), mut completion_status) = self.as_mut().pin_project() else { + unreachable!() + }; + if completion_status.code.is_none() { + *completion_status.as_mut().code_mut() = Some(S::in_progress_cancel(in_progress)); + } + + // Now that we're guaranteed to have a completion status, pass that + // through to "interpret the result". + let code = completion_status.code.unwrap(); + match self.poll_complete_with_code(None, Some(code)) { + // Leave it up to `S` to interpret the completion result as a + // cancellation result. + Poll::Ready(result) => S::result_into_cancel(result), + + // Should not be reachable as we always pass `Some(code)`. + Poll::Pending => unreachable!(), + } + } +} + +impl Drop for WaitableOperation { + fn drop(&mut self) { + // SAFETY: we're in the destructor here so the value `self` is about + // to go away and we can guarantee we're not moving out of it. + let mut pin = unsafe { Pin::new_unchecked(self) }; + + let (state, _) = pin.as_mut().pin_project(); + + // If this operation has already completed then skip cancellation, + // otherwise it's our job to cancel anything in-flight. + if let WaitableOperationState::Done = state { + return; + } + pin.cancel(); + } +} + +impl CompletionStatus { + fn code_mut(self: Pin<&mut Self>) -> &mut Option { + unsafe { &mut self.get_unchecked_mut().code } + } +} diff --git a/crates/guest-rust/rt/src/lib.rs b/crates/guest-rust/rt/src/lib.rs index 0f2858362..31b60afb0 100644 --- a/crates/guest-rust/rt/src/lib.rs +++ b/crates/guest-rust/rt/src/lib.rs @@ -1,7 +1,13 @@ #![no_std] +#[cfg(feature = "async")] +extern crate std; + extern crate alloc; +use alloc::alloc::Layout; +use core::ptr::{self, NonNull}; + // Re-export `bitflags` so that we can reference it from macros. #[cfg(feature = "bitflags")] #[doc(hidden)] @@ -116,3 +122,55 @@ pub fn run_ctors_once() { /// Support for using the Component Model Async ABI #[cfg(feature = "async")] pub mod async_support; + +/// Cleanup helper used to deallocate blocks of canonical ABI data from +/// lowerings. +pub struct Cleanup { + ptr: NonNull, + layout: Layout, +} + +// Usage of the returned pointer is always unsafe and must abide by these +// conventions, but this structure itself has no inherent reason to not be +// send/sync. +unsafe impl Send for Cleanup {} +unsafe impl Sync for Cleanup {} + +impl Cleanup { + /// Allocates a chunk of memory with `layout` and returns an object to clean + /// it up. + /// + /// Always returns a pointer which is null if `layout` has size zero. The + /// optional cleanup returned will be present if `layout` has a non-zero + /// size. When dropped `Cleanup` will deallocate the pointer returned. + pub fn new(layout: Layout) -> (*mut u8, Option) { + use alloc::alloc; + + if layout.size() == 0 { + return (ptr::null_mut(), None); + } + let ptr = unsafe { alloc::alloc(layout) }; + let ptr = match NonNull::new(ptr) { + Some(ptr) => ptr, + None => alloc::handle_alloc_error(layout), + }; + (ptr.as_ptr(), Some(Cleanup { ptr, layout })) + } + + /// Discards this cleanup to leak its memory or intentionally transfer + /// ownership to some other location. + pub fn forget(self) { + core::mem::forget(self); + } +} + +impl Drop for Cleanup { + fn drop(&mut self) { + unsafe { + for i in 0..self.layout.size() { + *self.ptr.add(i).as_ptr() = 0xff; + } + alloc::alloc::dealloc(self.ptr.as_ptr(), self.layout); + } + } +} diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index e0e71071d..7190fb1f6 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -888,4 +888,6 @@ pub mod rt { pub use wit_bindgen_rt::async_support; pub use crate::pre_wit_bindgen_0_20_0::*; + + pub use wit_bindgen_rt::Cleanup; } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index ba49e85b6..4854963fd 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -12,16 +12,14 @@ pub(super) struct FunctionBindgen<'a, 'b> { wasm_import_module: &'b str, pub src: Source, blocks: Vec, - block_storage: Vec<(Source, Vec<(String, String)>)>, + block_storage: Vec, tmp: usize, pub needs_cleanup_list: bool, - cleanup: Vec<(String, String)>, pub import_return_pointer_area_size: ArchitectureSize, pub import_return_pointer_area_align: Alignment, pub handle_decls: Vec, always_owned: bool, pub async_result_name: Option, - emitted_cleanup: bool, } pub const POINTER_SIZE_EXPRESSION: &str = "::core::mem::size_of::<*const u8>()"; @@ -44,51 +42,18 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { block_storage: Vec::new(), tmp: 0, needs_cleanup_list: false, - cleanup: Vec::new(), import_return_pointer_area_size: Default::default(), import_return_pointer_area_align: Default::default(), handle_decls: Vec::new(), always_owned, async_result_name: None, - emitted_cleanup: false, } } - pub(crate) fn flush_cleanup(&mut self) { - if !self.cleanup.is_empty() { + fn cleanup(&mut self, cleanup_value: &str) { + if self.block_storage.len() > 0 { self.needs_cleanup_list = true; - self.push_str("cleanup_list.extend_from_slice(&["); - for (ptr, layout) in mem::take(&mut self.cleanup) { - self.push_str("("); - self.push_str(&ptr); - self.push_str(", "); - self.push_str(&layout); - self.push_str("),"); - } - self.push_str("]);\n"); - } - } - - pub(crate) fn emit_cleanup(&mut self) { - if self.emitted_cleanup { - return; - } - self.emitted_cleanup = true; - for (ptr, layout) in mem::take(&mut self.cleanup) { - let alloc = self.r#gen.path_to_std_alloc_module(); - self.push_str(&format!( - "if {layout}.size() != 0 {{\n{alloc}::dealloc({ptr}.cast(), {layout});\n}}\n" - )); - } - if self.needs_cleanup_list { - let alloc = self.r#gen.path_to_std_alloc_module(); - self.push_str(&format!( - "for (ptr, layout) in cleanup_list {{\n - if layout.size() != 0 {{\n - {alloc}::dealloc(ptr.cast(), layout);\n - }}\n - }}\n", - )); + uwriteln!(self.src, "cleanup_list.extend({cleanup_value});"); } } @@ -254,15 +219,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { fn push_block(&mut self) { let prev_src = mem::take(&mut self.src); - let prev_cleanup = mem::take(&mut self.cleanup); - self.block_storage.push((prev_src, prev_cleanup)); + self.block_storage.push(prev_src); } fn finish_block(&mut self, operands: &mut Vec) { - self.flush_cleanup(); - let (prev_src, prev_cleanup) = self.block_storage.pop().unwrap(); + let prev_src = self.block_storage.pop().unwrap(); let src = mem::replace(&mut self.src, prev_src); - self.cleanup = prev_cleanup; let expr = match operands.len() { 0 => "()".to_string(), 1 => operands[0].clone(), @@ -316,19 +278,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { &self.r#gen.sizes } - fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool { - if !resolve.all_bits_valid(ty) { - return false; - } - match ty { - // Note that tuples in Rust are not ABI-compatible with component - // model tuples, so those are exempted here from canonical lists. - Type::Id(id) => { - let info = self.r#gen.r#gen.types.get(*id); - !info.has_resource && !info.has_tuple - } - _ => true, - } + fn is_list_canonical(&self, _resolve: &Resolve, ty: &Type) -> bool { + self.r#gen.is_list_canonical(ty) } fn emit( @@ -513,7 +464,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { .unwrap(); let path = self.r#gen.path_to_root(); results.push(format!( - "{async_support}::FutureReader::from_handle_and_vtable\ + "{async_support}::FutureReader::new\ ({op} as u32, &{path}wit_future::vtable{ordinal}::VTABLE)" )) } @@ -541,7 +492,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { .unwrap(); let path = self.r#gen.path_to_root(); results.push(format!( - "{async_support}::StreamReader::from_handle_and_vtable\ + "{async_support}::StreamReader::new\ ({op} as u32, &{path}wit_stream::vtable{ordinal}::VTABLE)" )) } @@ -807,12 +758,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::ListLower { element, realloc } => { let alloc = self.r#gen.path_to_std_alloc_module(); + let rt = self.gen.gen.runtime_path().to_string(); let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); let vec = format!("vec{tmp}"); let result = format!("result{tmp}"); let layout = format!("layout{tmp}"); let len = format!("len{tmp}"); + let cleanup = format!("_cleanup{tmp}"); self.push_str(&format!( "let {vec} = {operand0};\n", operand0 = operands[0] @@ -821,17 +774,23 @@ impl Bindgen for FunctionBindgen<'_, '_> { let size = self.r#gen.sizes.size(element); let align = self.r#gen.sizes.align(element); self.push_str(&format!( - "let {layout} = {alloc}::Layout::from_size_align_unchecked({vec}.len() * {}, {});\n", + "let {layout} = {alloc}::Layout::from_size_align({vec}.len() * {}, {}).unwrap();\n", size.format(POINTER_SIZE_EXPRESSION), align.format(POINTER_SIZE_EXPRESSION), )); - self.push_str(&format!("let {result} = if {layout}.size() != 0 {{\n")); - self.push_str(&format!( - "let ptr = {alloc}::alloc({layout}).cast::();\n", - )); self.push_str(&format!( - "if ptr.is_null()\n{{\n{alloc}::handle_alloc_error({layout});\n}}\nptr\n}}", + "let ({result}, {cleanup}) = {rt}::Cleanup::new({layout});" )); - self.push_str("else {\n::core::ptr::null_mut()\n};\n"); + if realloc.is_none() { + // If an allocator isn't requested then we must clean up the + // allocation ourselves since our callee isn't taking + // ownership. + self.cleanup(&cleanup); + } else { + uwriteln!( + self.src, + "if let Some(cleanup) = {cleanup} {{ cleanup.forget(); }}" + ); + } self.push_str(&format!("for (i, e) in {vec}.into_iter().enumerate() {{\n",)); self.push_str(&format!( "let base = {result}.add(i * {});\n", @@ -841,13 +800,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str("\n}\n"); results.push(format!("{result}")); results.push(len); - - if realloc.is_none() { - // If an allocator isn't requested then we must clean up the - // allocation ourselves since our callee isn't taking - // ownership. - self.cleanup.push((result, layout)); - } } Instruction::ListLift { element, .. } => { @@ -1020,7 +972,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { "\ {result} }}; - let result = {async_support}::first_poll({result}, |{params}| {{ + let result = {async_support}::first_poll({result}, move |{params}| {{ " ); } @@ -1029,7 +981,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { let func = self.declare_import(name, params, &[]); uwriteln!(self.src, "{func}({});", operands.join(", ")); - self.emit_cleanup(); self.src.push_str("});\n"); } @@ -1042,21 +993,18 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - Instruction::Return { amt, .. } => { - self.emit_cleanup(); - match amt { - 0 => {} - 1 => { - self.push_str(&operands[0]); - self.push_str("\n"); - } - _ => { - self.push_str("("); - self.push_str(&operands.join(", ")); - self.push_str(")\n"); - } + Instruction::Return { amt, .. } => match amt { + 0 => {} + 1 => { + self.push_str(&operands[0]); + self.push_str("\n"); } - } + _ => { + self.push_str("("); + self.push_str(&operands.join(", ")); + self.push_str(")\n"); + } + }, Instruction::I32Load { offset } => { let tmp = self.tmp(); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 618985437..7bed23d27 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -128,6 +128,11 @@ impl TypeOwnershipStyle { } } +enum PayloadFor { + Future, + Stream, +} + impl<'i> InterfaceGenerator<'i> { pub(super) fn generate_exports<'a>( &mut self, @@ -507,332 +512,196 @@ macro_rules! {macro_name} {{ .unwrap_or_else(|| "$root".into()) ); let func_name = &func.name; - let async_support = self.r#gen.async_support_path(); match &self.resolve.types[ty].kind { TypeDefKind::Future(payload_type) => { - let name = if let Some(payload_type) = payload_type { - self.type_name_owned(payload_type) - } else { - "()".into() - }; - - if !self.r#gen.future_payloads.contains_key(&name) { - let ordinal = self.r#gen.future_payloads.len(); - let (size, align) = if let Some(payload_type) = payload_type { - ( - self.sizes.size(payload_type), - self.sizes.align(payload_type), - ) - } else { - ( - ArchitectureSize { - bytes: 0, - pointers: 0, - }, - Alignment::default(), - ) - }; - let size = size.size_wasm32(); - let align = align.align_wasm32(); - let (lower, cleanup, lift) = if let Some(payload_type) = payload_type { - let (lower, cleanup) = - self.lower_to_memory("address", "&value", &payload_type, &module); - let lift = - self.lift_from_memory("address", "value", &payload_type, &module); - (lower, cleanup, lift) - } else { - (String::new(), None, "let value = ();\n".into()) - }; - - let (cleanup_start, cleanup_end) = - cleanup.unwrap_or_else(|| (String::new(), String::new())); - - let box_ = self.path_to_box(); - let code = format!( - r#" -#[doc(hidden)] -pub mod vtable{ordinal} {{ - fn write(future: u32, value: {name}) -> ::core::pin::Pin<{box_}>> {{ - {box_}::pin(async move {{ - {cleanup_start} - #[repr(align({align}))] - struct Buffer([::core::mem::MaybeUninit::; {size}]); - let mut buffer = Buffer([::core::mem::MaybeUninit::uninit(); {size}]); - let address = buffer.0.as_mut_ptr() as *mut u8; - {lower} - - let result = match unsafe {{ {async_support}::await_future_result(start_write, future, address).await }} {{ - {async_support}::AsyncWaitResult::Values(_) => true, - {async_support}::AsyncWaitResult::End => false, - {async_support}::AsyncWaitResult::Error(_) => unreachable!("received error while performing write"), - }}; - {cleanup_end} - result - }}) - }} - - fn read(future: u32) -> ::core::pin::Pin<{box_}>>>> {{ - {box_}::pin(async move {{ - #[repr(align({align}))] - struct Buffer([::core::mem::MaybeUninit::; {size}]); - let mut buffer = Buffer([::core::mem::MaybeUninit::uninit(); {size}]); - let address = buffer.0.as_mut_ptr() as *mut u8; - - match unsafe {{ {async_support}::await_future_result(start_read, future, address).await }} {{ - {async_support}::AsyncWaitResult::Values(v) => {{ - {lift} - Some(Ok(value)) - }}, - {async_support}::AsyncWaitResult::Error(e) => {{ - Some(Err({async_support}::ErrorContext::from_handle(e))) - }}, - {async_support}::AsyncWaitResult::End => None, - }} - }}) - }} - - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn cancel_write(_: u32) -> u32 {{ unreachable!() }} - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn cancel_read(_: u32) -> u32 {{ unreachable!() }} - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn close_writable(_: u32, _: u32) {{ unreachable!() }} - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn close_readable(_: u32, _: u32) {{ unreachable!() }} - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn new() -> u32 {{ unreachable!() }} - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn start_read(_: u32, _: *mut u8) -> u32 {{ unreachable!() }} - #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn start_write(_: u32, _: *mut u8) -> u32 {{ unreachable!() }} - - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "{module}")] - unsafe extern "C" {{ - #[link_name = "[future-new-{index}]{func_name}"] - fn new() -> u32; - #[link_name = "[future-cancel-write-{index}]{func_name}"] - fn cancel_write(_: u32) -> u32; - #[link_name = "[future-cancel-read-{index}]{func_name}"] - fn cancel_read(_: u32) -> u32; - #[link_name = "[future-close-writable-{index}]{func_name}"] - fn close_writable(_: u32, _: u32); - #[link_name = "[future-close-readable-{index}]{func_name}"] - fn close_readable(_: u32, _: u32); - #[link_name = "[async-lower][future-read-{index}]{func_name}"] - fn start_read(_: u32, _: *mut u8) -> u32; - #[link_name = "[async-lower][future-write-{index}]{func_name}"] - fn start_write(_: u32, _: *mut u8) -> u32; - }} + self.generate_payload( + PayloadFor::Future, + &module, + index, + func_name, + payload_type.as_ref(), + ); + } + TypeDefKind::Stream(payload_type) => { + self.generate_payload( + PayloadFor::Stream, + &module, + index, + func_name, + payload_type.as_ref(), + ); + } + _ => unreachable!(), + } + } + self.identifier = old_identifier; + } - pub static VTABLE: {async_support}::FutureVtable<{name}> = {async_support}::FutureVtable::<{name}> {{ - write, read, cancel_write, cancel_read, close_writable, close_readable, new - }}; + fn generate_payload( + &mut self, + payload_for: PayloadFor, + module: &str, + index: usize, + func_name: &str, + payload_type: Option<&Type>, + ) { + let name = if let Some(payload_type) = payload_type { + self.type_name_owned(payload_type) + } else { + "()".into() + }; + let map = match payload_for { + PayloadFor::Future => &mut self.r#gen.future_payloads, + PayloadFor::Stream => &mut self.r#gen.stream_payloads, + }; - impl super::FuturePayload for {name} {{ - const VTABLE: &'static {async_support}::FutureVtable = &VTABLE; - }} -}} - "#, - ); + if map.contains_key(&name) { + return; + } + let ordinal = map.len(); + let async_support = self.r#gen.async_support_path(); + let (size, align) = if let Some(payload_type) = payload_type { + ( + self.sizes.size(payload_type), + self.sizes.align(payload_type), + ) + } else { + ( + ArchitectureSize { + bytes: 0, + pointers: 0, + }, + Alignment::default(), + ) + }; + let size = size.size_wasm32(); + let align = align.align_wasm32(); + let lift; + let lower; + let dealloc_lists; + if let Some(payload_type) = payload_type { + lift = self.lift_from_memory_new("ptr", &payload_type, &module); + dealloc_lists = String::new(); + lower = self.lower_to_memory_new("ptr", "value", &payload_type, &module); + } else { + lift = format!("let _ = ptr;"); + lower = format!("let _ = (ptr, value);"); + dealloc_lists = format!("let _ = ptr;"); + } + let import_prefix = match payload_for { + PayloadFor::Future => "future", + PayloadFor::Stream => "stream", + }; + let camel = match payload_for { + PayloadFor::Future => "Future", + PayloadFor::Stream => "Stream", + }; + let start_extra = match payload_for { + PayloadFor::Future => "", + PayloadFor::Stream => ", _: usize", + }; + let mut lift_fn = format!("unsafe fn lift(ptr: *mut u8) -> {name} {{ {lift} }}"); + let mut lower_fn = format!("unsafe fn lower(value: {name}, ptr: *mut u8) {{ {lower} }}"); + let mut dealloc_lists_fn = + format!("unsafe fn dealloc_lists(ptr: *mut u8) {{ {dealloc_lists} }}"); + let mut lift_arg = "lift"; + let mut lower_arg = "lower"; + let mut dealloc_lists_arg = "dealloc_lists"; + + if let PayloadFor::Stream = payload_for { + lift_arg = "lift: Some(lift)"; + lower_arg = "lower: Some(lower)"; + dealloc_lists_arg = "dealloc_lists: Some(dealloc_lists)"; + + let is_list_canonical = match payload_type { + Some(ty) => self.is_list_canonical(ty), + None => true, + }; - self.r#gen.future_payloads.insert(name, code); - } - } - TypeDefKind::Stream(payload_type) => { - let name = if let Some(payload_type) = payload_type { - self.type_name_owned(payload_type) - } else { - "()".into() - }; + if is_list_canonical { + lift_arg = "lift: None"; + lower_arg = "lower: None"; + dealloc_lists_arg = "dealloc_lists: None"; + lift_fn = String::new(); + lower_fn = String::new(); + dealloc_lists_fn = String::new(); + } + } - if !self.r#gen.stream_payloads.contains_key(&name) { - let ordinal = self.r#gen.stream_payloads.len(); - let (size, align) = if let Some(payload_type) = payload_type { - ( - self.sizes.size(payload_type), - self.sizes.align(payload_type), - ) - } else { - ( - ArchitectureSize { - bytes: 0, - pointers: 0, - }, - Alignment::default(), - ) - }; - let size = size.size_wasm32(); - let align = align.align_wasm32(); - let alloc = self.path_to_std_alloc_module(); - let (lower_address, lower, cleanup, lift_address, lift) = match payload_type - { - Some(payload_type) if !stream_direct(payload_type) => { - let address = format!( - "let address = unsafe {{ {alloc}::alloc\ - ({alloc}::Layout::from_size_align_unchecked\ - ({size} * values.len(), {align})) }};" - ); - let (lower, cleanup) = self.lower_to_memory( - "address", - "value", - &payload_type, - &module, - ); - let lower = format!( - r#" -for (index, value) in values.iter().enumerate() {{ - let address = unsafe {{ address.add(index * {size}) }}; - {lower} -}} - "# - ); - let lift = self.lift_from_memory( - "address", - "value", - &payload_type, - &module, - ); - let lift = format!( - r#" -for (index, dst) in values.iter_mut().take(count).enumerate() {{ - let address = unsafe {{ address.add(index * {size}) }}; - {lift} - dst.write(value); -}} - "# - ); - (address.clone(), lower, cleanup, address, lift) - } - _ => { - let lower_address = - "let address = values.as_ptr() as *mut u8;".into(); - let lift_address = - "let address = values.as_mut_ptr() as *mut u8;".into(); - ( - lower_address, - String::new(), - None, - lift_address, - "let value = ();\n".into(), - ) - } - }; - - let (cleanup_start, cleanup_end) = - cleanup.unwrap_or_else(|| (String::new(), String::new())); - - let box_ = self.path_to_box(); - let code = format!( - r#" + let code = format!( + r#" #[doc(hidden)] +#[allow(unused_unsafe)] pub mod vtable{ordinal} {{ - fn write(stream: u32, values: &[{name}]) -> ::core::pin::Pin<{box_} + '_>> {{ - {box_}::pin(async move {{ - {cleanup_start} - {lower_address} - {lower} - - let mut total = 0; - while total < values.len() {{ - match unsafe {{ - {async_support}::await_stream_result( - start_write, - stream, - address.add(total * {size}), - u32::try_from(values.len() - (total * {size})).unwrap() - ).await - }} {{ - {async_support}::AsyncWaitResult::Values(count) => total += count, - {async_support}::AsyncWaitResult::Error(_) => unreachable!("encountered error during write"), - {async_support}::AsyncWaitResult::End => break, - }} - }} - {cleanup_end} - total - }}) - }} - - fn read( - stream: u32, - values: &mut [::core::mem::MaybeUninit::<{name}>] - ) -> ::core::pin::Pin<{box_}>> + '_>> {{ - {box_}::pin(async move {{ - {lift_address} - - match unsafe {{ - {async_support}::await_stream_result( - start_read, - stream, - address, - u32::try_from(values.len()).unwrap() - ).await - }} {{ - {async_support}::AsyncWaitResult::Values(count) => {{ - {lift} - Some(Ok(count)) - }}, - {async_support}::AsyncWaitResult::Error(e) => Some(Err({async_support}::ErrorContext::from_handle(e))), - {async_support}::AsyncWaitResult::End => None, - }} - }}) - }} #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn cancel_write(_: u32) -> u32 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn cancel_read(_: u32) -> u32 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn close_writable(_: u32, _: u32) {{ unreachable!() }} + unsafe extern "C" fn close_writable(_: u32) {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn close_readable(_: u32, _: u32) {{ unreachable!() }} + unsafe extern "C" fn close_readable(_: u32) {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn new() -> u32 {{ unreachable!() }} + unsafe extern "C" fn new() -> u64 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn start_read(_: u32, _: *mut u8, _: u32) -> u32 {{ unreachable!() }} + unsafe extern "C" fn start_read(_: u32, _: *mut u8{start_extra}) -> u32 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] - unsafe extern "C" fn start_write(_: u32, _: *mut u8, _: u32) -> u32 {{ unreachable!() }} + unsafe extern "C" fn start_write(_: u32, _: *const u8{start_extra}) -> u32 {{ unreachable!() }} #[cfg(target_arch = "wasm32")] #[link(wasm_import_module = "{module}")] unsafe extern "C" {{ - #[link_name = "[stream-new-{index}]{func_name}"] - fn new() -> u32; - #[link_name = "[stream-cancel-write-{index}]{func_name}"] + #[link_name = "[{import_prefix}-new-{index}]{func_name}"] + fn new() -> u64; + #[link_name = "[{import_prefix}-cancel-write-{index}]{func_name}"] fn cancel_write(_: u32) -> u32; - #[link_name = "[stream-cancel-read-{index}]{func_name}"] + #[link_name = "[{import_prefix}-cancel-read-{index}]{func_name}"] fn cancel_read(_: u32) -> u32; - #[link_name = "[stream-close-writable-{index}]{func_name}"] - fn close_writable(_: u32, _: u32); - #[link_name = "[stream-close-readable-{index}]{func_name}"] - fn close_readable(_: u32, _: u32); - #[link_name = "[async-lower][stream-read-{index}]{func_name}"] - fn start_read(_: u32, _: *mut u8, _: u32) -> u32; - #[link_name = "[async-lower][stream-write-{index}]{func_name}"] - fn start_write(_: u32, _: *mut u8, _: u32) -> u32; + #[link_name = "[{import_prefix}-close-writable-{index}]{func_name}"] + fn close_writable(_: u32); + #[link_name = "[{import_prefix}-close-readable-{index}]{func_name}"] + fn close_readable(_: u32); + #[link_name = "[async-lower][{import_prefix}-read-{index}]{func_name}"] + fn start_read(_: u32, _: *mut u8{start_extra}) -> u32; + #[link_name = "[async-lower][{import_prefix}-write-{index}]{func_name}"] + fn start_write(_: u32, _: *const u8{start_extra}) -> u32; }} - pub static VTABLE: {async_support}::StreamVtable<{name}> = {async_support}::StreamVtable::<{name}> {{ - write, read, cancel_write, cancel_read, close_writable, close_readable, new + {lift_fn} + {lower_fn} + {dealloc_lists_fn} + + pub static VTABLE: {async_support}::{camel}Vtable<{name}> = {async_support}::{camel}Vtable::<{name}> {{ + cancel_write, + cancel_read, + close_writable, + close_readable, + {dealloc_lists_arg}, + layout: unsafe {{ + ::std::alloc::Layout::from_size_align_unchecked({size}, {align}) + }}, + {lift_arg}, + {lower_arg}, + new, + start_read, + start_write, }}; - impl super::StreamPayload for {name} {{ - const VTABLE: &'static {async_support}::StreamVtable = &VTABLE; + impl super::{camel}Payload for {name} {{ + const VTABLE: &'static {async_support}::{camel}Vtable = &VTABLE; }} }} "#, - ); - - self.r#gen.stream_payloads.insert(name, code); - } - } - _ => unreachable!(), - } - } + ); - self.identifier = old_identifier; + let map = match payload_for { + PayloadFor::Future => &mut self.r#gen.future_payloads, + PayloadFor::Stream => &mut self.r#gen.stream_payloads, + }; + map.insert(name, code); } fn generate_guest_import(&mut self, func: &Function, interface: Option<&WorldKey>) { @@ -880,39 +749,22 @@ pub mod vtable{ordinal} {{ } } - fn lower_to_memory( + fn lower_to_memory_new( &mut self, address: &str, value: &str, ty: &Type, module: &str, - ) -> (String, Option<(String, String)>) { + ) -> String { let mut f = FunctionBindgen::new(self, Vec::new(), true, module, true); abi::lower_to_memory(f.r#gen.resolve, &mut f, address.into(), value.into(), ty); - f.flush_cleanup(); - let lower = format!("unsafe {{ {} }}", String::from(f.src)); - let cleanup = if f.needs_cleanup_list { - f.src = Default::default(); - f.emit_cleanup(); - let body = String::from(f.src); - let vec = self.path_to_vec(); - Some(( - format!("let mut cleanup_list = {vec}::new();\n"), - format!("unsafe {{ {body} }}"), - )) - } else { - None - }; - (lower, cleanup) + format!("unsafe {{ {} }}", String::from(f.src)) } - fn lift_from_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { + fn lift_from_memory_new(&mut self, address: &str, ty: &Type, module: &str) -> String { let mut f = FunctionBindgen::new(self, Vec::new(), true, module, true); let result = abi::lift_from_memory(f.r#gen.resolve, &mut f, address.into(), ty); - format!( - "let {value} = unsafe {{ {}\n{result} }};", - String::from(f.src) - ) + format!("unsafe {{ {}\n{result} }}", String::from(f.src)) } fn generate_guest_import_body( @@ -2478,6 +2330,21 @@ pub mod vtable{ordinal} {{ }; format!("{prefix}_rt::{name_in_runtime_module}") } + + pub fn is_list_canonical(&self, ty: &Type) -> bool { + if !self.resolve.all_bits_valid(ty) { + return false; + } + match ty { + // Note that tuples in Rust are not ABI-compatible with component + // model tuples, so those are exempted here from canonical lists. + Type::Id(id) => { + let info = self.r#gen.types.get(*id); + !info.has_resource && !info.has_tuple + } + _ => true, + } + } } impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { @@ -2931,21 +2798,3 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< self.interface.push_str(">"); } } - -fn stream_direct(ty: &Type) -> bool { - // TODO: might be able to return `true` for other types if the generated Rust versions of those types are - // guaranteed to be safely transmutable to and from their lowered form. - matches!( - ty, - Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::U64 - | Type::S64 - | Type::F32 - | Type::F64 - ) -} diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 15f5f701b..899f80400 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -474,26 +474,24 @@ impl RustWasm { } fn finish_runtime_module(&mut self) { - if self.rt_module.is_empty() { - return; - } - - // As above, disable rustfmt, as we use prettyplease. - if self.opts.format { - uwriteln!(self.src, "#[rustfmt::skip]"); - } + if !self.rt_module.is_empty() { + // As above, disable rustfmt, as we use prettyplease. + if self.opts.format { + uwriteln!(self.src, "#[rustfmt::skip]"); + } - self.src.push_str("mod _rt {\n"); - self.src.push_str("#![allow(dead_code, clippy::all)]\n"); - let mut emitted = IndexSet::new(); - while !self.rt_module.is_empty() { - for item in mem::take(&mut self.rt_module) { - if emitted.insert(item) { - self.emit_runtime_item(item); + self.src.push_str("mod _rt {\n"); + self.src.push_str("#![allow(dead_code, clippy::all)]\n"); + let mut emitted = IndexSet::new(); + while !self.rt_module.is_empty() { + for item in mem::take(&mut self.rt_module) { + if emitted.insert(item) { + self.emit_runtime_item(item); + } } } + self.src.push_str("}\n"); } - self.src.push_str("}\n"); if !self.future_payloads.is_empty() { let async_support = self.async_support_path(); diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index afe8034d7..352a0d8cb 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -24,9 +24,12 @@ regex = "1.11.1" serde = { workspace = true } toml = "0.8.20" wasi-preview1-component-adapter-provider = "30.0.2" -wac-parser = "0.6.1" -wac-types = "0.6.1" -wac-graph = "0.6.1" +# wac-parser = "0.6.1" +# wac-types = "0.6.1" +# wac-graph = "0.6.1" +wac-parser = { path = '../../../wac/crates/wac-parser' } +wac-types = { path = '../../../wac/crates/wac-types' } +wac-graph = { path = '../../../wac/crates/wac-graph' } indexmap = { workspace = true } wasm-encoder = { workspace = true } wasmparser = { workspace = true, features = ["features"] } diff --git a/tests/runtime-new/async/future-cancel-write-then-read/runner.rs b/tests/runtime-new/async/future-cancel-write-then-read/runner.rs new file mode 100644 index 000000000..79c28bf19 --- /dev/null +++ b/tests/runtime-new/async/future-cancel-write-then-read/runner.rs @@ -0,0 +1,11 @@ +include!(env!("BINDINGS")); + +use crate::a::b::the_test::f; + +fn main() { + let (tx, rx) = wit_future::new(); + + drop(tx.write(())); + + f(rx); +} diff --git a/tests/runtime-new/async/future-cancel-write-then-read/test.rs b/tests/runtime-new/async/future-cancel-write-then-read/test.rs new file mode 100644 index 000000000..f5947cc93 --- /dev/null +++ b/tests/runtime-new/async/future-cancel-write-then-read/test.rs @@ -0,0 +1,17 @@ +//@ args = '--async all' + +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::FutureReader; + +impl Guest for Component { + async fn f(future: FutureReader<()>) { + assert!(future.await.is_none()); + } +} diff --git a/tests/runtime-new/async/future-cancel-write-then-read/test.wit b/tests/runtime-new/async/future-cancel-write-then-read/test.wit new file mode 100644 index 000000000..228d6a9e3 --- /dev/null +++ b/tests/runtime-new/async/future-cancel-write-then-read/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func(param: future); +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/tests/runtime-new/async/future-close-after-coming-back/runner.rs b/tests/runtime-new/async/future-close-after-coming-back/runner.rs new file mode 100644 index 000000000..c0d79b4cc --- /dev/null +++ b/tests/runtime-new/async/future-close-after-coming-back/runner.rs @@ -0,0 +1,11 @@ +include!(env!("BINDINGS")); + +use crate::a::b::the_test::f; + +fn main() { + let (tx, rx) = wit_future::new(); + + let rx = f(rx); + drop(tx); + drop(rx); +} diff --git a/tests/runtime-new/async/future-close-after-coming-back/test.rs b/tests/runtime-new/async/future-close-after-coming-back/test.rs new file mode 100644 index 000000000..2b6caa57c --- /dev/null +++ b/tests/runtime-new/async/future-close-after-coming-back/test.rs @@ -0,0 +1,15 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::FutureReader; + +impl Guest for Component { + fn f(future: FutureReader<()>) -> FutureReader<()> { + future + } +} diff --git a/tests/runtime-new/async/future-close-after-coming-back/test.wit b/tests/runtime-new/async/future-close-after-coming-back/test.wit new file mode 100644 index 000000000..aebf1f589 --- /dev/null +++ b/tests/runtime-new/async/future-close-after-coming-back/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func(param: future) -> future; +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/tests/runtime-new/async/future-close-then-receive-read/runner.rs b/tests/runtime-new/async/future-close-then-receive-read/runner.rs new file mode 100644 index 000000000..fc13ead8c --- /dev/null +++ b/tests/runtime-new/async/future-close-then-receive-read/runner.rs @@ -0,0 +1,14 @@ +include!(env!("BINDINGS")); + +use crate::a::b::the_test::{get, set}; + +fn main() { + let (tx, rx) = wit_future::new(); + + set(rx); + let rx = get(); + drop(tx); + drop(rx); + + wit_future::new::<()>(); +} diff --git a/tests/runtime-new/async/future-close-then-receive-read/test.rs b/tests/runtime-new/async/future-close-then-receive-read/test.rs new file mode 100644 index 000000000..bfb00b5a3 --- /dev/null +++ b/tests/runtime-new/async/future-close-then-receive-read/test.rs @@ -0,0 +1,22 @@ +include!(env!("BINDINGS")); + +use crate::exports::a::b::the_test::Guest; +use std::cell::Cell; +use wit_bindgen::rt::async_support::FutureReader; + +struct Component; + +export!(Component); + +std::thread_local!( + static SLOT: Cell>> = const { Cell::new(None) }; +); + +impl Guest for Component { + fn set(future: FutureReader<()>) { + SLOT.with(|s| s.set(Some(future))); + } + fn get() -> FutureReader<()> { + SLOT.with(|s| s.replace(None).unwrap()) + } +} diff --git a/tests/runtime-new/async/future-close-then-receive-read/test.wit b/tests/runtime-new/async/future-close-then-receive-read/test.wit new file mode 100644 index 000000000..309ca4670 --- /dev/null +++ b/tests/runtime-new/async/future-close-then-receive-read/test.wit @@ -0,0 +1,13 @@ +package a:b; + +interface the-test { + set: func(param: future); + get: func() -> future; +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/tests/runtime-new/async/future-closes-with-error/runner.rs b/tests/runtime-new/async/future-closes-with-error/runner.rs new file mode 100644 index 000000000..e068b4cb7 --- /dev/null +++ b/tests/runtime-new/async/future-closes-with-error/runner.rs @@ -0,0 +1,11 @@ +include!(env!("BINDINGS")); + +use crate::a::b::the_test::f; + +fn main() { + let (tx, rx) = wit_future::new(); + + drop(tx); + + f(rx); +} diff --git a/tests/runtime-new/async/future-closes-with-error/test.rs b/tests/runtime-new/async/future-closes-with-error/test.rs new file mode 100644 index 000000000..f5947cc93 --- /dev/null +++ b/tests/runtime-new/async/future-closes-with-error/test.rs @@ -0,0 +1,17 @@ +//@ args = '--async all' + +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::FutureReader; + +impl Guest for Component { + async fn f(future: FutureReader<()>) { + assert!(future.await.is_none()); + } +} diff --git a/tests/runtime-new/async/future-closes-with-error/test.wit b/tests/runtime-new/async/future-closes-with-error/test.wit new file mode 100644 index 000000000..228d6a9e3 --- /dev/null +++ b/tests/runtime-new/async/future-closes-with-error/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func(param: future); +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/tests/runtime-new/async/future-write-then-read-comes-back/runner.rs b/tests/runtime-new/async/future-write-then-read-comes-back/runner.rs new file mode 100644 index 000000000..a99531dc0 --- /dev/null +++ b/tests/runtime-new/async/future-write-then-read-comes-back/runner.rs @@ -0,0 +1,16 @@ +include!(env!("BINDINGS")); + +use wit_bindgen::rt::async_support; + +use crate::a::b::the_test::f; + +fn main() { + async_support::block_on(async { + let (tx, rx) = wit_future::new(); + + let a = async { tx.write(()).await }; + let b = async { f(rx).await.unwrap() }; + let (a_result, ()) = futures::join!(a, b); + a_result.unwrap() + }); +} diff --git a/tests/runtime-new/async/future-write-then-read-comes-back/test.rs b/tests/runtime-new/async/future-write-then-read-comes-back/test.rs new file mode 100644 index 000000000..2b6caa57c --- /dev/null +++ b/tests/runtime-new/async/future-write-then-read-comes-back/test.rs @@ -0,0 +1,15 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::FutureReader; + +impl Guest for Component { + fn f(future: FutureReader<()>) -> FutureReader<()> { + future + } +} diff --git a/tests/runtime-new/async/future-write-then-read-comes-back/test.wit b/tests/runtime-new/async/future-write-then-read-comes-back/test.wit new file mode 100644 index 000000000..aebf1f589 --- /dev/null +++ b/tests/runtime-new/async/future-write-then-read-comes-back/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func(param: future) -> future; +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/tests/runtime-new/async/future-write-then-read-remote/runner.rs b/tests/runtime-new/async/future-write-then-read-remote/runner.rs new file mode 100644 index 000000000..706993607 --- /dev/null +++ b/tests/runtime-new/async/future-write-then-read-remote/runner.rs @@ -0,0 +1,16 @@ +include!(env!("BINDINGS")); + +use wit_bindgen::rt::async_support; + +use crate::a::b::the_test::f; + +fn main() { + async_support::block_on(async { + let (tx, rx) = wit_future::new(); + + let a = async { tx.write(()).await }; + let b = async { f(rx) }; + let (a_result, ()) = futures::join!(a, b); + a_result.unwrap(); + }); +} diff --git a/tests/runtime-new/async/future-write-then-read-remote/runner2.rs b/tests/runtime-new/async/future-write-then-read-remote/runner2.rs new file mode 100644 index 000000000..46eb3a2b5 --- /dev/null +++ b/tests/runtime-new/async/future-write-then-read-remote/runner2.rs @@ -0,0 +1,16 @@ +include!(env!("BINDINGS")); + +use wit_bindgen::rt::async_support; + +use crate::a::b::the_test::f; + +fn main() { + async_support::block_on(async { + let (tx, rx) = wit_future::new(); + + let a = tx.write(()); + let b = async { f(rx) }; + let (a_result, ()) = futures::join!(a, b); + a_result.unwrap(); + }); +} diff --git a/tests/runtime-new/async/future-write-then-read-remote/test.rs b/tests/runtime-new/async/future-write-then-read-remote/test.rs new file mode 100644 index 000000000..9f8f6920e --- /dev/null +++ b/tests/runtime-new/async/future-write-then-read-remote/test.rs @@ -0,0 +1,19 @@ +//@ args = '--async all' + +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::FutureReader; + +impl Guest for Component { + async fn f(future: FutureReader<()>) { + eprintln!("e1"); + future.await.unwrap(); + eprintln!("e2"); + } +} diff --git a/tests/runtime-new/async/future-write-then-read-remote/test.wit b/tests/runtime-new/async/future-write-then-read-remote/test.wit new file mode 100644 index 000000000..228d6a9e3 --- /dev/null +++ b/tests/runtime-new/async/future-write-then-read-remote/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func(param: future); +} + +world test { + export the-test; +} +world runner { + import the-test; +} From 4752ef965c41054b7199946646040f8062996c4b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 28 Mar 2025 14:59:45 -0700 Subject: [PATCH 02/13] Don't run new tests just yet --- .../future-cancel-write-then-read/runner.rs | 0 .../future-cancel-write-then-read/test.rs | 0 .../future-cancel-write-then-read/test.wit | 0 .../future-close-after-coming-back/runner.rs | 0 .../future-close-after-coming-back/test.rs | 0 .../future-close-after-coming-back/test.wit | 0 .../future-close-then-receive-read/runner.rs | 0 .../future-close-then-receive-read/test.rs | 0 .../future-close-then-receive-read/test.wit | 0 .../future-closes-with-error/runner.rs | 0 .../future-closes-with-error/test.rs | 0 .../future-closes-with-error/test.wit | 0 .../future-write-then-read-comes-back/runner.rs | 0 .../future-write-then-read-comes-back/test.rs | 0 .../future-write-then-read-comes-back/test.wit | 0 .../future-write-then-read-remote/runner.rs | 0 .../future-write-then-read-remote/runner2.rs | 0 .../future-write-then-read-remote/test.rs | 0 .../future-write-then-read-remote/test.wit | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-cancel-write-then-read/runner.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-cancel-write-then-read/test.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-cancel-write-then-read/test.wit (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-close-after-coming-back/runner.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-close-after-coming-back/test.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-close-after-coming-back/test.wit (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-close-then-receive-read/runner.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-close-then-receive-read/test.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-close-then-receive-read/test.wit (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-closes-with-error/runner.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-closes-with-error/test.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-closes-with-error/test.wit (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-write-then-read-comes-back/runner.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-write-then-read-comes-back/test.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-write-then-read-comes-back/test.wit (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-write-then-read-remote/runner.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-write-then-read-remote/runner2.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-write-then-read-remote/test.rs (100%) rename tests/{runtime-new/async => runtime-new-async-disabled}/future-write-then-read-remote/test.wit (100%) diff --git a/tests/runtime-new/async/future-cancel-write-then-read/runner.rs b/tests/runtime-new-async-disabled/future-cancel-write-then-read/runner.rs similarity index 100% rename from tests/runtime-new/async/future-cancel-write-then-read/runner.rs rename to tests/runtime-new-async-disabled/future-cancel-write-then-read/runner.rs diff --git a/tests/runtime-new/async/future-cancel-write-then-read/test.rs b/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.rs similarity index 100% rename from tests/runtime-new/async/future-cancel-write-then-read/test.rs rename to tests/runtime-new-async-disabled/future-cancel-write-then-read/test.rs diff --git a/tests/runtime-new/async/future-cancel-write-then-read/test.wit b/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.wit similarity index 100% rename from tests/runtime-new/async/future-cancel-write-then-read/test.wit rename to tests/runtime-new-async-disabled/future-cancel-write-then-read/test.wit diff --git a/tests/runtime-new/async/future-close-after-coming-back/runner.rs b/tests/runtime-new-async-disabled/future-close-after-coming-back/runner.rs similarity index 100% rename from tests/runtime-new/async/future-close-after-coming-back/runner.rs rename to tests/runtime-new-async-disabled/future-close-after-coming-back/runner.rs diff --git a/tests/runtime-new/async/future-close-after-coming-back/test.rs b/tests/runtime-new-async-disabled/future-close-after-coming-back/test.rs similarity index 100% rename from tests/runtime-new/async/future-close-after-coming-back/test.rs rename to tests/runtime-new-async-disabled/future-close-after-coming-back/test.rs diff --git a/tests/runtime-new/async/future-close-after-coming-back/test.wit b/tests/runtime-new-async-disabled/future-close-after-coming-back/test.wit similarity index 100% rename from tests/runtime-new/async/future-close-after-coming-back/test.wit rename to tests/runtime-new-async-disabled/future-close-after-coming-back/test.wit diff --git a/tests/runtime-new/async/future-close-then-receive-read/runner.rs b/tests/runtime-new-async-disabled/future-close-then-receive-read/runner.rs similarity index 100% rename from tests/runtime-new/async/future-close-then-receive-read/runner.rs rename to tests/runtime-new-async-disabled/future-close-then-receive-read/runner.rs diff --git a/tests/runtime-new/async/future-close-then-receive-read/test.rs b/tests/runtime-new-async-disabled/future-close-then-receive-read/test.rs similarity index 100% rename from tests/runtime-new/async/future-close-then-receive-read/test.rs rename to tests/runtime-new-async-disabled/future-close-then-receive-read/test.rs diff --git a/tests/runtime-new/async/future-close-then-receive-read/test.wit b/tests/runtime-new-async-disabled/future-close-then-receive-read/test.wit similarity index 100% rename from tests/runtime-new/async/future-close-then-receive-read/test.wit rename to tests/runtime-new-async-disabled/future-close-then-receive-read/test.wit diff --git a/tests/runtime-new/async/future-closes-with-error/runner.rs b/tests/runtime-new-async-disabled/future-closes-with-error/runner.rs similarity index 100% rename from tests/runtime-new/async/future-closes-with-error/runner.rs rename to tests/runtime-new-async-disabled/future-closes-with-error/runner.rs diff --git a/tests/runtime-new/async/future-closes-with-error/test.rs b/tests/runtime-new-async-disabled/future-closes-with-error/test.rs similarity index 100% rename from tests/runtime-new/async/future-closes-with-error/test.rs rename to tests/runtime-new-async-disabled/future-closes-with-error/test.rs diff --git a/tests/runtime-new/async/future-closes-with-error/test.wit b/tests/runtime-new-async-disabled/future-closes-with-error/test.wit similarity index 100% rename from tests/runtime-new/async/future-closes-with-error/test.wit rename to tests/runtime-new-async-disabled/future-closes-with-error/test.wit diff --git a/tests/runtime-new/async/future-write-then-read-comes-back/runner.rs b/tests/runtime-new-async-disabled/future-write-then-read-comes-back/runner.rs similarity index 100% rename from tests/runtime-new/async/future-write-then-read-comes-back/runner.rs rename to tests/runtime-new-async-disabled/future-write-then-read-comes-back/runner.rs diff --git a/tests/runtime-new/async/future-write-then-read-comes-back/test.rs b/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.rs similarity index 100% rename from tests/runtime-new/async/future-write-then-read-comes-back/test.rs rename to tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.rs diff --git a/tests/runtime-new/async/future-write-then-read-comes-back/test.wit b/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.wit similarity index 100% rename from tests/runtime-new/async/future-write-then-read-comes-back/test.wit rename to tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.wit diff --git a/tests/runtime-new/async/future-write-then-read-remote/runner.rs b/tests/runtime-new-async-disabled/future-write-then-read-remote/runner.rs similarity index 100% rename from tests/runtime-new/async/future-write-then-read-remote/runner.rs rename to tests/runtime-new-async-disabled/future-write-then-read-remote/runner.rs diff --git a/tests/runtime-new/async/future-write-then-read-remote/runner2.rs b/tests/runtime-new-async-disabled/future-write-then-read-remote/runner2.rs similarity index 100% rename from tests/runtime-new/async/future-write-then-read-remote/runner2.rs rename to tests/runtime-new-async-disabled/future-write-then-read-remote/runner2.rs diff --git a/tests/runtime-new/async/future-write-then-read-remote/test.rs b/tests/runtime-new-async-disabled/future-write-then-read-remote/test.rs similarity index 100% rename from tests/runtime-new/async/future-write-then-read-remote/test.rs rename to tests/runtime-new-async-disabled/future-write-then-read-remote/test.rs diff --git a/tests/runtime-new/async/future-write-then-read-remote/test.wit b/tests/runtime-new-async-disabled/future-write-then-read-remote/test.wit similarity index 100% rename from tests/runtime-new/async/future-write-then-read-remote/test.wit rename to tests/runtime-new-async-disabled/future-write-then-read-remote/test.wit From c60bbc4a24cc3dff26e2c4f813fd6face28774fe Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 28 Mar 2025 15:00:29 -0700 Subject: [PATCH 03/13] Go back to original `wac` deps --- Cargo.lock | 101 ++++++++++++++++++++++------------------- crates/test/Cargo.toml | 9 ++-- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c81d456c..b83875827 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,18 +135,6 @@ dependencies = [ "syn", ] -[[package]] -name = "auditable-serde" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7bf8143dfc3c0258df908843e169b5cc5fcf76c7718bd66135ef4a9cd558c5" -dependencies = [ - "semver", - "serde", - "serde_json", - "topological-sort", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -718,16 +706,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "flate2" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1996,12 +1974,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "topological-sort" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" - [[package]] name = "tracing" version = "0.1.41" @@ -2112,7 +2084,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wac-graph" -version = "0.7.0-dev" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d94268a683b67ae20210565b5f91e106fe05034c36b931e739fe90377ed80b98" dependencies = [ "anyhow", "id-arena", @@ -2122,14 +2096,16 @@ dependencies = [ "semver", "thiserror", "wac-types", - "wasm-encoder 0.227.1", - "wasm-metadata", - "wasmparser 0.227.1", + "wasm-encoder 0.202.0", + "wasm-metadata 0.202.0", + "wasmparser 0.202.0", ] [[package]] name = "wac-parser" -version = "0.7.0-dev" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec0c4f63641fa095b4a551263fe35a15c72c9680b650b8f08f70db0fdbd19" dependencies = [ "anyhow", "id-arena", @@ -2141,21 +2117,23 @@ dependencies = [ "serde", "thiserror", "wac-graph", - "wasm-encoder 0.227.1", - "wasm-metadata", - "wasmparser 0.227.1", + "wasm-encoder 0.202.0", + "wasm-metadata 0.202.0", + "wasmparser 0.202.0", ] [[package]] name = "wac-types" -version = "0.7.0-dev" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5028a15e266f4c8fed48beb95aebb76af5232dcd554fd849a305a4e5cce1563" dependencies = [ "anyhow", "id-arena", "indexmap", "semver", - "wasm-encoder 0.227.1", - "wasmparser 0.227.1", + "wasm-encoder 0.202.0", + "wasmparser 0.202.0", ] [[package]] @@ -2228,6 +2206,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd106365a7f5f7aa3c1916a98cbb3ad477f5ff96ddb130285a91c6e7429e67a" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-encoder" version = "0.217.1" @@ -2247,21 +2234,41 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.227.1" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094aea3cb90e09f16ee25a4c0e324b3e8c934e7fd838bfa039aef5352f44a917" dependencies = [ "anyhow", - "auditable-serde", - "flate2", "indexmap", "serde", "serde_derive", "serde_json", "spdx", - "url", + "wasm-encoder 0.202.0", + "wasmparser 0.202.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.227.1" +dependencies = [ + "anyhow", + "indexmap", "wasm-encoder 0.227.1", "wasmparser 0.227.1", ] +[[package]] +name = "wasmparser" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6998515d3cf3f8b980ef7c11b29a9b1017d4cf86b99ae93b546992df9931413" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + [[package]] name = "wasmparser" version = "0.217.1" @@ -2829,7 +2836,7 @@ dependencies = [ "clap", "heck 0.5.0", "wasm-encoder 0.227.1", - "wasm-metadata", + "wasm-metadata 0.227.1", "wit-bindgen-core", "wit-component", ] @@ -2875,7 +2882,7 @@ dependencies = [ "clap", "heck 0.5.0", "indexmap", - "wasm-metadata", + "wasm-metadata 0.227.1", "wit-bindgen-core", "wit-component", "wit-parser 0.227.1", @@ -2925,7 +2932,7 @@ dependencies = [ "serde_json", "syn", "test-helpers", - "wasm-metadata", + "wasm-metadata 0.227.1", "wit-bindgen", "wit-bindgen-core", "wit-bindgen-rt", @@ -2982,7 +2989,7 @@ dependencies = [ "serde_derive", "serde_json", "wasm-encoder 0.227.1", - "wasm-metadata", + "wasm-metadata 0.227.1", "wasmparser 0.227.1", "wat", "wit-parser 0.227.1", diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 352a0d8cb..afe8034d7 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -24,12 +24,9 @@ regex = "1.11.1" serde = { workspace = true } toml = "0.8.20" wasi-preview1-component-adapter-provider = "30.0.2" -# wac-parser = "0.6.1" -# wac-types = "0.6.1" -# wac-graph = "0.6.1" -wac-parser = { path = '../../../wac/crates/wac-parser' } -wac-types = { path = '../../../wac/crates/wac-types' } -wac-graph = { path = '../../../wac/crates/wac-graph' } +wac-parser = "0.6.1" +wac-types = "0.6.1" +wac-graph = "0.6.1" indexmap = { workspace = true } wasm-encoder = { workspace = true } wasmparser = { workspace = true, features = ["features"] } From 5bcc78ed10ee9eba4f3e5a92a644012a1a894d02 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 28 Mar 2025 15:01:11 -0700 Subject: [PATCH 04/13] Disable patch overrides --- Cargo.lock | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 -------- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b83875827..cddc1e341 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,18 @@ dependencies = [ "syn", ] +[[package]] +name = "auditable-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7bf8143dfc3c0258df908843e169b5cc5fcf76c7718bd66135ef4a9cd558c5" +dependencies = [ + "semver", + "serde", + "serde_json", + "topological-sort", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -706,6 +718,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1974,6 +1996,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + [[package]] name = "tracing" version = "0.1.41" @@ -2227,6 +2255,8 @@ dependencies = [ [[package]] name = "wasm-encoder" version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822" dependencies = [ "leb128fmt", "wasmparser 0.227.1", @@ -2251,9 +2281,18 @@ dependencies = [ [[package]] name = "wasm-metadata" version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1ef0faabbbba6674e97a56bee857ccddf942785a336c8b47b42373c922a91d" dependencies = [ "anyhow", + "auditable-serde", + "flate2", "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "url", "wasm-encoder 0.227.1", "wasmparser 0.227.1", ] @@ -2286,6 +2325,8 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ "bitflags", "hashbrown 0.15.2", @@ -2605,6 +2646,8 @@ dependencies = [ [[package]] name = "wast" version = "227.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c14e5042b16c9d267da3b9b0f4529870455178415286312c25c34dfc1b2816" dependencies = [ "bumpalo", "leb128fmt", @@ -2616,6 +2659,8 @@ dependencies = [ [[package]] name = "wat" version = "1.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d394d5bef7006ff63338d481ca10f1af76601e65ebdf5ed33d29302994e9cc" dependencies = [ "wast 227.0.1", ] @@ -2980,6 +3025,8 @@ dependencies = [ [[package]] name = "wit-component" version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" dependencies = [ "anyhow", "bitflags", @@ -3016,6 +3063,8 @@ dependencies = [ [[package]] name = "wit-parser" version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" dependencies = [ "anyhow", "id-arena", diff --git a/Cargo.toml b/Cargo.toml index 64c83877e..cf4d0bfd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,14 +50,6 @@ wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.41.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.41.0', default-features = false } wit-bindgen-test = { path = 'crates/test', version = '0.41.0' } -[patch.crates-io] -wat = { path = '../wasm-tools/crates/wat' } -wasmparser = { path = '../wasm-tools/crates/wasmparser' } -wasm-encoder = { path = '../wasm-tools/crates/wasm-encoder' } -wasm-metadata = { path = '../wasm-tools/crates/wasm-metadata' } -wit-parser = { path = '../wasm-tools/crates/wit-parser' } -wit-component = { path = '../wasm-tools/crates/wit-component' } - [[bin]] name = "wit-bindgen" From 07f7ecb1ed8e78d8d6452712f8dd18a67586ebaf Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 28 Mar 2025 18:33:43 -0700 Subject: [PATCH 05/13] Beef up some documentation in various places --- crates/guest-rust/Cargo.toml | 3 + crates/guest-rust/rt/src/async_support.rs | 28 ++- .../rt/src/async_support/abi_buffer.rs | 3 + .../rt/src/async_support/future_support.rs | 170 +++++++++++++++++- .../rt/src/async_support/stream_support.rs | 93 +++++++++- .../rt/src/async_support/waitable.rs | 25 ++- crates/guest-rust/src/lib.rs | 6 + crates/rust/src/lib.rs | 11 +- 8 files changed, 307 insertions(+), 32 deletions(-) diff --git a/crates/guest-rust/Cargo.toml b/crates/guest-rust/Cargo.toml index 9150b7576..871fad843 100644 --- a/crates/guest-rust/Cargo.toml +++ b/crates/guest-rust/Cargo.toml @@ -11,6 +11,9 @@ Rust bindings generator and runtime support for WIT and the component model. Used when compiling Rust programs to the component model. """ +[package.metadata.docs.rs] +all-features = true + [dependencies] wit-bindgen-rust-macro = { path = "./macro", optional = true, version = "0.41.0" } wit-bindgen-rt = { path = "./rt", version = "0.41.0", features = ["bitflags"] } diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index ca8068bb4..37e29eaad 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -49,7 +49,17 @@ struct FutureState { tasks: Option>, /// The waitable set containing waitables created by this task, if any. waitable_set: Option, - /// TODO + /// A map of waitables to the corresponding waker and completion code. + /// + /// This is primarily filled in and managed by `WaitableOperation`. The + /// waker here comes straight from `std::task::Context` and the pointer is + /// otherwise stored within the `WaitableOperation` The raw pointer here + /// has a disconnected lifetime with each future but the management of the + /// internal states with respect to drop should always ensure that this is + /// only ever pointing to active waitable operations. + /// + /// When a waitable notification is received the corresponding entry in this + /// map is removed, the status code is filled in, and the waker is notified. wakers: HashMap)>, } @@ -266,22 +276,6 @@ pub enum AsyncWaitResult { End, } -impl AsyncWaitResult { - /// Interpret the results from an async operation that is known to *not* be blocked - fn from_nonblocked_async_result(v: u32) -> Self { - match v { - results::CLOSED | results::CANCELED => Self::End, - v => { - if v & results::CLOSED != 0 { - Self::Error(v & !results::CLOSED) - } else { - Self::Values(v as usize) - } - } - } - } -} - /// Call the `subtask.drop` canonical built-in function. fn subtask_drop(subtask: u32) { #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/guest-rust/rt/src/async_support/abi_buffer.rs b/crates/guest-rust/rt/src/async_support/abi_buffer.rs index 848a610c2..08d18520f 100644 --- a/crates/guest-rust/rt/src/async_support/abi_buffer.rs +++ b/crates/guest-rust/rt/src/async_support/abi_buffer.rs @@ -13,6 +13,9 @@ use std::vec::Vec; /// additionally tracks any partial writes. Writes can then be resumed with /// this buffer again or the partial write can be converted back to `Vec` to /// get access to the remaining values. +/// +/// This value is created through the [`StreamWrite`](super::StreamWrite) +/// future's return value. pub struct AbiBuffer { rust_storage: Vec>, vtable: &'static StreamVtable, diff --git a/crates/guest-rust/rt/src/async_support/future_support.rs b/crates/guest-rust/rt/src/async_support/future_support.rs index c11ff33b9..8cdc58680 100644 --- a/crates/guest-rust/rt/src/async_support/future_support.rs +++ b/crates/guest-rust/rt/src/async_support/future_support.rs @@ -1,10 +1,91 @@ //! Runtime support for `future` in the component model. //! -//! TODO: +//! There are a number of tricky concerns to all balance when implementing +//! bindings to `future`, specifically with how it interacts with Rust. This +//! will attempt to go over some of the high-level details of the implementation +//! here. //! -//! * leaking requires owned values -//! * owned values means we can't return back sent items -//! * intimately used in implementation details. +//! ## Leak safety +//! +//! It's safe to leak any value at any time currently in Rust. In other words +//! Rust doesn't have linear types (yet). Typically this isn't really a problem +//! but the component model intrinsics we're working with here operate by being +//! given a pointer and then at some point in the future the pointer may be +//! read. This means that it's our responsibility to keep this pointer alive and +//! valid for the entire duration of an asynchronous operation. +//! +//! Chiefly this means that borrowed values are a no-no in this module. For +//! example if you were to send a `&[u8]` as an implementation of +//! `future>` that would not be sound. For example: +//! +//! * The future send operation is started, recording an address of `&[u8]`. +//! * The future is then leaked. +//! * According to rustc, later in code the original `&[u8]` is then no longer +//! borrowed. +//! * The original source of `&[u8]` could then be deallocated. +//! * Then the component model actually reads the pointer that it was given. +//! +//! This constraint effectively means that all types flowing in-and-out of +//! futures, streams, and async APIs are all "owned values", notably no +//! lifetimes. This requires, for example, that `future>` operates on +//! `Vec`. +//! +//! This is in stark contrast to bindings generated for `list` otherwise, +//! however, where for example a synchronous import with a `list` argument +//! would be bound with a `&[u8]` argument. Until Rust has some form of linear +//! types, however, it's not possible to loosen this restriction soundly because +//! it's generally not safe to leak an active I/O operation. This restriction is +//! similar to why it's so difficult to bind `io_uring` in safe Rust, which +//! operates similarly to the component model where pointers are submitted and +//! read in the future after the original call for submission returns. +//! +//! ## Lowering Owned Values +//! +//! According to the above everything with futures/streams operates on owned +//! values already, but this also affects precisely how lifting and lowering is +//! performed. In general any active asynchronous operation could be cancelled +//! at any time, meaning we have to deal with situations such as: +//! +//! * A `write` hasn't even started yet. +//! * A `write` was started and then cancelled. +//! * A `write` was started and then the other end closed the channel. +//! * A `write` was started and then the other end received the value. +//! +//! In all of these situations regardless of the structure of `T` we can't leak +//! memory. The `future.write` intrinsic, however, takes no ownership of the +//! memory involved which means that we're still responsible for cleaning up +//! lists. It does take ownership, however, of `own` handles and other +//! resources. +//! +//! The way that this is solved for futures/streams is to lean further into +//! processing owned values. Namely lowering a `T` takes `T`-by-value, not `&T`. +//! This means that lowering operates similarly to return values of exported +//! functions, not parameters to imported functions. By lowering an owned value +//! of `T` this preserves a nice property where the lowered value has exclusive +//! ownership of all of its pointers/resources/etc. Lowering `&T` may require a +//! "cleanup list" for example which we avoid here entirely. +//! +//! This then makes the second and third cases above, getting a value back after +//! lowering, much easier. Namely re-acquisition of a value is simple `lift` +//! operation as if we received a value on the channel. +//! +//! ## Inefficiencies +//! +//! The above requirements generally mean that this is not a hyper-efficient +//! implementation. All writes and reads, for example, start out with allocation +//! memory on the heap to be owned by the asynchronous operation. Writing a +//! `list` to a future passes ownership of `Vec` but in theory doesn't +//! not actually require relinquishing ownership of the vector. Furthermore +//! there's no way to re-acquire a `T` after it has been sent, but all of `T` is +//! still valid except for `own` resources. +//! +//! That's all to say that this implementation can probably still be improved +//! upon, but doing so is thought to be pretty nontrivial at this time. It +//! should be noted though that there are other high-level inefficiencies with +//! WIT unrelated to this module. For example `list` is not always +//! represented the same in Rust as it is in the canonical ABI. That means that +//! sending `list` into a future might require copying the entire list and +//! changing its layout. Currently this is par-for-the-course with bindings. use { super::waitable::{WaitableOp, WaitableOperation}, @@ -21,23 +102,64 @@ use { }, }; +/// Function table used for [`FutureWriter`] and [`FutureReader`] +/// +/// Instances of this table are generated by `wit_bindgen::generate!`. This is +/// not a trait to enable different `FutureVtable<()>` instances to exist, for +/// example, through different calls to `wit_bindgen::generate!`. +/// +/// It's not intended that any user implements this vtable, instead it's +/// intended to only be auto-generated. #[doc(hidden)] pub struct FutureVtable { + /// The Canonical ABI layout of `T` in-memory. pub layout: Layout, + + /// A callback to consume a value of `T` and lower it to the canonical ABI + /// pointed to by `dst`. + /// + /// The `dst` pointer should have `self.layout`. This is used to convert + /// in-memory representations in Rust to their canonical representations in + /// the component model. pub lower: unsafe fn(value: T, dst: *mut u8), + + /// A callback to deallocate any lists within the canonical ABI value `dst` + /// provided. + /// + /// This is used when a value is successfully sent to another component. In + /// such a situation it may be possible that the canonical lowering of `T` + /// has lists that are still owned by this component and must be + /// deallocated. This is akin to a `post-return` callback for returns of + /// exported functions. pub dealloc_lists: unsafe fn(dst: *mut u8), + + /// A callback to lift a value of `T` from the canonical ABI representation + /// provided. pub lift: unsafe fn(dst: *mut u8) -> T, + + /// The raw `future.write` intrinsic. pub start_write: unsafe extern "C" fn(future: u32, val: *const u8) -> u32, + /// The raw `future.read` intrinsic. pub start_read: unsafe extern "C" fn(future: u32, val: *mut u8) -> u32, + /// The raw `future.cancel-write` intrinsic. pub cancel_write: unsafe extern "C" fn(future: u32) -> u32, + /// The raw `future.cancel-read` intrinsic. pub cancel_read: unsafe extern "C" fn(future: u32) -> u32, + /// The raw `future.close-writable` intrinsic. pub close_writable: unsafe extern "C" fn(future: u32), + /// The raw `future.close-readable` intrinsic. pub close_readable: unsafe extern "C" fn(future: u32), + /// The raw `future.new` intrinsic. pub new: unsafe extern "C" fn() -> u64, } /// Helper function to create a new read/write pair for a component model /// future. +/// +/// # Unsafety +/// +/// This function is unsafe as it requires the functions within `vtable` to +/// correctly uphold the contracts of the component model. pub unsafe fn future_new( vtable: &'static FutureVtable, ) -> (FutureWriter, FutureReader) { @@ -51,18 +173,53 @@ pub unsafe fn future_new( } /// Represents the writable end of a Component Model `future`. +/// +/// A [`FutureWriter`] can be used to send a single value of `T` to the other +/// end of a `future`. In a sense this is similar to a oneshot channel in Rust. pub struct FutureWriter { handle: u32, vtable: &'static FutureVtable, } impl FutureWriter { + /// Helper function to wrap a handle/vtable into a `FutureWriter`. + /// + /// # Unsafety + /// + /// This function is unsafe as it requires the functions within `vtable` to + /// correctly uphold the contracts of the component model. #[doc(hidden)] pub unsafe fn new(handle: u32, vtable: &'static FutureVtable) -> Self { Self { handle, vtable } } /// Write the specified `value` to this `future`. + /// + /// This method is equivalent to an `async fn` which sends the `value` into + /// this future. The asynchronous operation acts as a rendezvous where the + /// operation does not complete until the other side has successfully + /// received the value. + /// + /// # Return Value + /// + /// The returned [`FutureWrite`] is a future that can be `.await`'d. The + /// return value of this future is: + /// + /// * `Ok(())` - the `value` was sent and received. The `self` value was + /// consumed along the way and will no longer be accessible. + /// * `Err(FutureWriteError { value })` - an attempt was made to send + /// `value` but the other half of this [`FutureWriter`] was closed before + /// the value was received. This consumes `self` because the channel is + /// now closed, but `value` is returned in case the caller wants to reuse + /// it. + /// + /// # Cancellation + /// + /// The returned future can be cancelled normally via `drop` which means + /// that the `value` provided here, along with this `FutureWriter` itself, + /// will be lost. There is also [`FutureWrite::cancel`] which can be used to + /// possibly re-acquire `value` and `self` if the operation was cancelled. + /// In such a situation the operation can be retried at a future date. pub fn write(self, value: T) -> FutureWrite { FutureWrite { op: WaitableOperation::new((self, value)), @@ -87,6 +244,8 @@ impl Drop for FutureWriter { } /// Represents a write operation which may be canceled prior to completion. +/// +/// This is returned by [`FutureWriter::write`]. pub struct FutureWrite { op: WaitableOperation>, } @@ -377,6 +536,9 @@ impl Drop for FutureReader { } /// Represents a read operation which may be canceled prior to completion. +/// +/// This represents a read operation on a [`FutureReader`] and is created via +/// `IntoFuture`. pub struct FutureRead { op: WaitableOperation>, } diff --git a/crates/guest-rust/rt/src/async_support/stream_support.rs b/crates/guest-rust/rt/src/async_support/stream_support.rs index b6626b28d..84c8ab6f2 100644 --- a/crates/guest-rust/rt/src/async_support/stream_support.rs +++ b/crates/guest-rust/rt/src/async_support/stream_support.rs @@ -1,3 +1,6 @@ +//! For a high-level overview of how this module is implemented see the +//! module documentation in `future_support.rs`. + use crate::async_support::waitable::{WaitableOp, WaitableOperation}; use crate::async_support::AbiBuffer; use { @@ -15,18 +18,49 @@ use { }, }; +/// Operations that a stream requires throughout the implementation. +/// +/// This is generated by `wit_bindgen::generate!` primarily. #[doc(hidden)] pub struct StreamVtable { + /// The in-memory canonical ABI layout of a single value of `T`. pub layout: Layout, + + /// An optional callback where if provided will lower an owned `T` value + /// into the `dst` pointer. + /// + /// If this is called the ownership of all of `T`'s lists and resources are + /// passed to `dst`, possibly by reallocating if `T`'s layout differs from + /// the canonical ABI layout. + /// + /// If this is `None` then it means that `T` has the same layout in-memory + /// in Rust as it does in the canonical ABI. In such a situation the + /// lower/lift operation can be dropped. pub lower: Option, + + /// Callback used to deallocate any owned lists in `dst` after a value has + /// been successfully sent along a stream. + /// + /// `None` means that `T` has no lists internally. pub dealloc_lists: Option, + + /// Dual of `lower`, and like `lower` if this is missing then it means that + /// `T` has the same in-memory representation in Rust and the canonical ABI. pub lift: Option T>, + + /// The raw `stream.write` intrinsic. pub start_write: unsafe extern "C" fn(stream: u32, val: *const u8, amt: usize) -> u32, + /// The raw `stream.read` intrinsic. pub start_read: unsafe extern "C" fn(stream: u32, val: *mut u8, amt: usize) -> u32, + /// The raw `stream.cancel-write` intrinsic. pub cancel_write: unsafe extern "C" fn(stream: u32) -> u32, + /// The raw `stream.cancel-read` intrinsic. pub cancel_read: unsafe extern "C" fn(stream: u32) -> u32, + /// The raw `stream.close-writable` intrinsic. pub close_writable: unsafe extern "C" fn(stream: u32), + /// The raw `stream.close-readable` intrinsic. pub close_readable: unsafe extern "C" fn(stream: u32), + /// The raw `stream.new` intrinsic. pub new: unsafe extern "C" fn() -> u64, } @@ -58,10 +92,41 @@ impl StreamWriter { /// Initiate a write of the `values` provided into this stream. /// - /// This method will initiate a single write of the `values` provided. Upon - /// completion the values will be yielded back as an [`AbiBuffer`] which - /// manages intermediate state. That can be used to resume after a partial - /// write or re-acquire the underlying storage. + /// This method is akin to an `async fn` except that the returned + /// [`StreamWrite`] future can also be cancelled via [`StreamWrite::cancel`] + /// to re-acquire intermediate values. + /// + /// This method will perform at most a single write of the `values` + /// provided. The returned future will resolve once the write has completed. + /// + /// # Return Values + /// + /// The returned [`StreamWrite`] future returns a tuple of `(result, buf)`. + /// The `result` can be `StreamResult::Complete(n)` meaning that `n` values + /// were sent from `values` into this writer. A result of + /// `StreamResult::Closed` means that no values were sent and the other side + /// has hung-up and sending values will no longer be possible. + /// + /// The `buf` returned is an [`AbiBuffer`] which retains ownership of the + /// original `values` provided here. That can be used to re-acquire `values` + /// through the [`AbiBuffer::into_vec`] method. The `buf` maintains an + /// internal cursor of how many values have been written and if the write + /// should be resumed to write the entire buffer then the + /// [`StreamWriter::write_buf`] method can be used to resume writing at the + /// next value in the buffer. + /// + /// # Cancellation + /// + /// The returned [`StreamWrite`] future can be cancelled like any other Rust + /// future via `drop`, but this means that `values` will be lost within the + /// future. The [`StreamWrite::cancel`] method can be used to re-acquire the + /// in-progress write that is being done with `values`. This is effectively + /// a way of forcing the future to immediately resolve. + /// + /// Note that if this future is cancelled via `drop` it does not mean that + /// no values were sent. It may be possible that values were still sent + /// despite being cancelled. Cancelling a write and determining what + /// happened must be done with [`StreamWrite::cancel`]. pub fn write(&mut self, values: Vec) -> StreamWrite<'_, T> { self.write_buf(AbiBuffer::new(values, self.vtable)) } @@ -82,13 +147,20 @@ impl StreamWriter { /// vector will be returned and any remaining elements in the vector were /// not sent because the stream was closed. pub async fn write_all(&mut self, values: Vec) -> Vec { + // Perform an initial write which converts `values` into `AbiBuffer`. let (mut status, mut buf) = self.write(values).await; + + // While the previous write completed and there's still remaining items + // in the buffer, perform another write. while let StreamResult::Complete(_) = status { if buf.remaining() == 0 { break; } (status, buf) = self.write_buf(buf).await; } + + // Return back any values that weren't written by shifting them to the + // front of the returned vector. assert!(buf.remaining() == 0 || matches!(status, StreamResult::Closed)); buf.into_vec() } @@ -103,6 +175,9 @@ impl StreamWriter { /// `Some(value)`, otherwise `None` is returned indicating the value was /// sent. pub async fn write_one(&mut self, value: T) -> Option { + // TODO: can probably be a bit more efficient about this and avoid + // moving `value` onto the heap in some situations, but that's left as + // an optimization for later. self.write_all(std::vec![value]).await.pop() } } @@ -274,6 +349,13 @@ impl StreamReader { /// /// Upon completion the `buf` will be yielded back to the caller via the /// completion of the [`StreamRead`] future. + /// + /// # Cancellation + /// + /// Cancelling the returned future can be done with `drop` like all Rust + /// futures, but it does not mean that no values were read. To accurately + /// determine if values were read the [`StreamRead::cancel`] method must be + /// used. pub fn read(&mut self, buf: Vec) -> StreamRead<'_, T> { StreamRead { op: WaitableOperation::new((self, buf)), @@ -298,6 +380,9 @@ impl StreamReader { pub async fn collect(mut self) -> Vec { let mut ret = Vec::new(); loop { + // If there's no more spare capacity then reserve room for one item + // which should trigger `Vec`'s built-in resizing logic, which will + // free up likely more capacity than just one slot. if ret.len() == ret.capacity() { ret.reserve(1); } diff --git a/crates/guest-rust/rt/src/async_support/waitable.rs b/crates/guest-rust/rt/src/async_support/waitable.rs index 4dbf3958a..b40a2f0a5 100644 --- a/crates/guest-rust/rt/src/async_support/waitable.rs +++ b/crates/guest-rust/rt/src/async_support/waitable.rs @@ -7,6 +7,12 @@ use std::mem; use std::pin::Pin; use std::task::{Context, Poll}; +/// Generic future-based operation on any "waitable" in the component model. +/// +/// This is used right now to power futures and streams for both read/write +/// halves. This structure is driven by `S`, an implementation of +/// [`WaitableOp`], which codifies the various state transitions and what to do +/// on each state transition. pub struct WaitableOperation { state: WaitableOperationState, completion_status: CompletionStatus, @@ -19,6 +25,14 @@ struct CompletionStatus { /// Helper trait to be used with `WaitableOperation` to assist with machinery /// necessary to track in-flight reads/writes on futures. +/// +/// # Unsafety +/// +/// This trait is `unsafe` as it has various guarantees that must be upheld by +/// implementors such as: +/// +/// * `S::in_progress_waitable` must always return the same value for the state +/// given. pub unsafe trait WaitableOp { /// Initial state of this operation, used to kick off the actual component /// model operation and transition to `InProgress`. @@ -132,8 +146,15 @@ where pub fn unregister_waker(self: Pin<&mut Self>, waitable: u32) { unsafe { (*super::CURRENT).remove_waitable(waitable); - let prev = (*super::CURRENT).wakers.remove(&waitable); - assert!(prev.is_some()); + let _prev = (*super::CURRENT).wakers.remove(&waitable); + // Note that `_prev` here is not guaranteed to be either `Some` or + // `None`. A racy completion notification may have come in and + // removed our waitable from the map even though we're in the + // `InProgress` state, meaning it may not be present. + // + // The main thing is that after this method is called the + // internal `completion_status` is guaranteed to no longer be in + // `FuturesState`. } } diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index 7190fb1f6..aad2a54a0 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -891,3 +891,9 @@ pub mod rt { pub use wit_bindgen_rt::Cleanup; } + +#[cfg(feature = "async")] +pub use wit_bindgen_rt::async_support::{ + AbiBuffer, FutureRead, FutureReader, FutureWrite, FutureWriter, StreamRead, StreamReader, + StreamResult, StreamWrite, StreamWriter, +}; diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 899f80400..b31db183e 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -324,11 +324,12 @@ pub struct Opts { /// Determines which functions to lift or lower `async`, if any. /// /// Accepted values are: - /// - none - /// - all - /// - some=[,...], where each is of the form: - /// - import: or - /// - export: + /// + /// - none + /// - all + /// - `some=[,...]`, where each `` is of the form: + /// - `import:` or + /// - `export:` #[cfg_attr(feature = "clap", arg(long = "async", value_parser = parse_async, default_value = "none"))] #[cfg_attr(feature = "serde", serde(rename = "async"))] pub async_: AsyncConfig, From c6ec1e84fe6ef8ec7f9d147aac5e96be3eb3af1b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 28 Mar 2025 21:45:47 -0700 Subject: [PATCH 06/13] Remove some dead code --- crates/guest-rust/rt/src/async_support.rs | 25 ----------------------- 1 file changed, 25 deletions(-) diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index 37e29eaad..dfd529ebe 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -89,20 +89,6 @@ impl Drop for FutureState { } } -/// Represents the state of a stream or future. -#[doc(hidden)] -pub enum Handle { - LocalOpen, - LocalReady(Box, Waker), - LocalWaiting(oneshot::Sender>), - LocalClosed, - Read, - Write, - // Local end is closed with an error - // NOTE: this is only valid for write ends - WriteClosedErr(Option), -} - /// The current task being polled (or null if none). static mut CURRENT: *mut FutureState = ptr::null_mut(); @@ -265,17 +251,6 @@ mod results { pub const CANCELED: u32 = 0; } -/// Result of awaiting a asynchronous read or write -#[doc(hidden)] -pub enum AsyncWaitResult { - /// Used when a value was successfully sent or received - Values(usize), - /// Represents a successful but error-indicating read - Error(u32), - /// Represents a failed read (closed, canceled, etc) - End, -} - /// Call the `subtask.drop` canonical built-in function. fn subtask_drop(subtask: u32) { #[cfg(not(target_arch = "wasm32"))] From eb1f4b7ccb7f22d014fe8e085a5790d07823c671 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 28 Mar 2025 21:47:27 -0700 Subject: [PATCH 07/13] Defer async tests to a future PR --- .../future-cancel-write-then-read/runner.rs | 11 ---------- .../future-cancel-write-then-read/test.rs | 17 -------------- .../future-cancel-write-then-read/test.wit | 12 ---------- .../future-close-after-coming-back/runner.rs | 11 ---------- .../future-close-after-coming-back/test.rs | 15 ------------- .../future-close-after-coming-back/test.wit | 12 ---------- .../future-close-then-receive-read/runner.rs | 14 ------------ .../future-close-then-receive-read/test.rs | 22 ------------------- .../future-close-then-receive-read/test.wit | 13 ----------- .../future-closes-with-error/runner.rs | 11 ---------- .../future-closes-with-error/test.rs | 17 -------------- .../future-closes-with-error/test.wit | 12 ---------- .../runner.rs | 16 -------------- .../future-write-then-read-comes-back/test.rs | 15 ------------- .../test.wit | 12 ---------- .../future-write-then-read-remote/runner.rs | 16 -------------- .../future-write-then-read-remote/runner2.rs | 16 -------------- .../future-write-then-read-remote/test.rs | 19 ---------------- .../future-write-then-read-remote/test.wit | 12 ---------- 19 files changed, 273 deletions(-) delete mode 100644 tests/runtime-new-async-disabled/future-cancel-write-then-read/runner.rs delete mode 100644 tests/runtime-new-async-disabled/future-cancel-write-then-read/test.rs delete mode 100644 tests/runtime-new-async-disabled/future-cancel-write-then-read/test.wit delete mode 100644 tests/runtime-new-async-disabled/future-close-after-coming-back/runner.rs delete mode 100644 tests/runtime-new-async-disabled/future-close-after-coming-back/test.rs delete mode 100644 tests/runtime-new-async-disabled/future-close-after-coming-back/test.wit delete mode 100644 tests/runtime-new-async-disabled/future-close-then-receive-read/runner.rs delete mode 100644 tests/runtime-new-async-disabled/future-close-then-receive-read/test.rs delete mode 100644 tests/runtime-new-async-disabled/future-close-then-receive-read/test.wit delete mode 100644 tests/runtime-new-async-disabled/future-closes-with-error/runner.rs delete mode 100644 tests/runtime-new-async-disabled/future-closes-with-error/test.rs delete mode 100644 tests/runtime-new-async-disabled/future-closes-with-error/test.wit delete mode 100644 tests/runtime-new-async-disabled/future-write-then-read-comes-back/runner.rs delete mode 100644 tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.rs delete mode 100644 tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.wit delete mode 100644 tests/runtime-new-async-disabled/future-write-then-read-remote/runner.rs delete mode 100644 tests/runtime-new-async-disabled/future-write-then-read-remote/runner2.rs delete mode 100644 tests/runtime-new-async-disabled/future-write-then-read-remote/test.rs delete mode 100644 tests/runtime-new-async-disabled/future-write-then-read-remote/test.wit diff --git a/tests/runtime-new-async-disabled/future-cancel-write-then-read/runner.rs b/tests/runtime-new-async-disabled/future-cancel-write-then-read/runner.rs deleted file mode 100644 index 79c28bf19..000000000 --- a/tests/runtime-new-async-disabled/future-cancel-write-then-read/runner.rs +++ /dev/null @@ -1,11 +0,0 @@ -include!(env!("BINDINGS")); - -use crate::a::b::the_test::f; - -fn main() { - let (tx, rx) = wit_future::new(); - - drop(tx.write(())); - - f(rx); -} diff --git a/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.rs b/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.rs deleted file mode 100644 index f5947cc93..000000000 --- a/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.rs +++ /dev/null @@ -1,17 +0,0 @@ -//@ args = '--async all' - -include!(env!("BINDINGS")); - -struct Component; - -export!(Component); - -use crate::exports::a::b::the_test::Guest; - -use wit_bindgen::rt::async_support::FutureReader; - -impl Guest for Component { - async fn f(future: FutureReader<()>) { - assert!(future.await.is_none()); - } -} diff --git a/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.wit b/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.wit deleted file mode 100644 index 228d6a9e3..000000000 --- a/tests/runtime-new-async-disabled/future-cancel-write-then-read/test.wit +++ /dev/null @@ -1,12 +0,0 @@ -package a:b; - -interface the-test { - f: func(param: future); -} - -world test { - export the-test; -} -world runner { - import the-test; -} diff --git a/tests/runtime-new-async-disabled/future-close-after-coming-back/runner.rs b/tests/runtime-new-async-disabled/future-close-after-coming-back/runner.rs deleted file mode 100644 index c0d79b4cc..000000000 --- a/tests/runtime-new-async-disabled/future-close-after-coming-back/runner.rs +++ /dev/null @@ -1,11 +0,0 @@ -include!(env!("BINDINGS")); - -use crate::a::b::the_test::f; - -fn main() { - let (tx, rx) = wit_future::new(); - - let rx = f(rx); - drop(tx); - drop(rx); -} diff --git a/tests/runtime-new-async-disabled/future-close-after-coming-back/test.rs b/tests/runtime-new-async-disabled/future-close-after-coming-back/test.rs deleted file mode 100644 index 2b6caa57c..000000000 --- a/tests/runtime-new-async-disabled/future-close-after-coming-back/test.rs +++ /dev/null @@ -1,15 +0,0 @@ -include!(env!("BINDINGS")); - -struct Component; - -export!(Component); - -use crate::exports::a::b::the_test::Guest; - -use wit_bindgen::rt::async_support::FutureReader; - -impl Guest for Component { - fn f(future: FutureReader<()>) -> FutureReader<()> { - future - } -} diff --git a/tests/runtime-new-async-disabled/future-close-after-coming-back/test.wit b/tests/runtime-new-async-disabled/future-close-after-coming-back/test.wit deleted file mode 100644 index aebf1f589..000000000 --- a/tests/runtime-new-async-disabled/future-close-after-coming-back/test.wit +++ /dev/null @@ -1,12 +0,0 @@ -package a:b; - -interface the-test { - f: func(param: future) -> future; -} - -world test { - export the-test; -} -world runner { - import the-test; -} diff --git a/tests/runtime-new-async-disabled/future-close-then-receive-read/runner.rs b/tests/runtime-new-async-disabled/future-close-then-receive-read/runner.rs deleted file mode 100644 index fc13ead8c..000000000 --- a/tests/runtime-new-async-disabled/future-close-then-receive-read/runner.rs +++ /dev/null @@ -1,14 +0,0 @@ -include!(env!("BINDINGS")); - -use crate::a::b::the_test::{get, set}; - -fn main() { - let (tx, rx) = wit_future::new(); - - set(rx); - let rx = get(); - drop(tx); - drop(rx); - - wit_future::new::<()>(); -} diff --git a/tests/runtime-new-async-disabled/future-close-then-receive-read/test.rs b/tests/runtime-new-async-disabled/future-close-then-receive-read/test.rs deleted file mode 100644 index bfb00b5a3..000000000 --- a/tests/runtime-new-async-disabled/future-close-then-receive-read/test.rs +++ /dev/null @@ -1,22 +0,0 @@ -include!(env!("BINDINGS")); - -use crate::exports::a::b::the_test::Guest; -use std::cell::Cell; -use wit_bindgen::rt::async_support::FutureReader; - -struct Component; - -export!(Component); - -std::thread_local!( - static SLOT: Cell>> = const { Cell::new(None) }; -); - -impl Guest for Component { - fn set(future: FutureReader<()>) { - SLOT.with(|s| s.set(Some(future))); - } - fn get() -> FutureReader<()> { - SLOT.with(|s| s.replace(None).unwrap()) - } -} diff --git a/tests/runtime-new-async-disabled/future-close-then-receive-read/test.wit b/tests/runtime-new-async-disabled/future-close-then-receive-read/test.wit deleted file mode 100644 index 309ca4670..000000000 --- a/tests/runtime-new-async-disabled/future-close-then-receive-read/test.wit +++ /dev/null @@ -1,13 +0,0 @@ -package a:b; - -interface the-test { - set: func(param: future); - get: func() -> future; -} - -world test { - export the-test; -} -world runner { - import the-test; -} diff --git a/tests/runtime-new-async-disabled/future-closes-with-error/runner.rs b/tests/runtime-new-async-disabled/future-closes-with-error/runner.rs deleted file mode 100644 index e068b4cb7..000000000 --- a/tests/runtime-new-async-disabled/future-closes-with-error/runner.rs +++ /dev/null @@ -1,11 +0,0 @@ -include!(env!("BINDINGS")); - -use crate::a::b::the_test::f; - -fn main() { - let (tx, rx) = wit_future::new(); - - drop(tx); - - f(rx); -} diff --git a/tests/runtime-new-async-disabled/future-closes-with-error/test.rs b/tests/runtime-new-async-disabled/future-closes-with-error/test.rs deleted file mode 100644 index f5947cc93..000000000 --- a/tests/runtime-new-async-disabled/future-closes-with-error/test.rs +++ /dev/null @@ -1,17 +0,0 @@ -//@ args = '--async all' - -include!(env!("BINDINGS")); - -struct Component; - -export!(Component); - -use crate::exports::a::b::the_test::Guest; - -use wit_bindgen::rt::async_support::FutureReader; - -impl Guest for Component { - async fn f(future: FutureReader<()>) { - assert!(future.await.is_none()); - } -} diff --git a/tests/runtime-new-async-disabled/future-closes-with-error/test.wit b/tests/runtime-new-async-disabled/future-closes-with-error/test.wit deleted file mode 100644 index 228d6a9e3..000000000 --- a/tests/runtime-new-async-disabled/future-closes-with-error/test.wit +++ /dev/null @@ -1,12 +0,0 @@ -package a:b; - -interface the-test { - f: func(param: future); -} - -world test { - export the-test; -} -world runner { - import the-test; -} diff --git a/tests/runtime-new-async-disabled/future-write-then-read-comes-back/runner.rs b/tests/runtime-new-async-disabled/future-write-then-read-comes-back/runner.rs deleted file mode 100644 index a99531dc0..000000000 --- a/tests/runtime-new-async-disabled/future-write-then-read-comes-back/runner.rs +++ /dev/null @@ -1,16 +0,0 @@ -include!(env!("BINDINGS")); - -use wit_bindgen::rt::async_support; - -use crate::a::b::the_test::f; - -fn main() { - async_support::block_on(async { - let (tx, rx) = wit_future::new(); - - let a = async { tx.write(()).await }; - let b = async { f(rx).await.unwrap() }; - let (a_result, ()) = futures::join!(a, b); - a_result.unwrap() - }); -} diff --git a/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.rs b/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.rs deleted file mode 100644 index 2b6caa57c..000000000 --- a/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.rs +++ /dev/null @@ -1,15 +0,0 @@ -include!(env!("BINDINGS")); - -struct Component; - -export!(Component); - -use crate::exports::a::b::the_test::Guest; - -use wit_bindgen::rt::async_support::FutureReader; - -impl Guest for Component { - fn f(future: FutureReader<()>) -> FutureReader<()> { - future - } -} diff --git a/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.wit b/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.wit deleted file mode 100644 index aebf1f589..000000000 --- a/tests/runtime-new-async-disabled/future-write-then-read-comes-back/test.wit +++ /dev/null @@ -1,12 +0,0 @@ -package a:b; - -interface the-test { - f: func(param: future) -> future; -} - -world test { - export the-test; -} -world runner { - import the-test; -} diff --git a/tests/runtime-new-async-disabled/future-write-then-read-remote/runner.rs b/tests/runtime-new-async-disabled/future-write-then-read-remote/runner.rs deleted file mode 100644 index 706993607..000000000 --- a/tests/runtime-new-async-disabled/future-write-then-read-remote/runner.rs +++ /dev/null @@ -1,16 +0,0 @@ -include!(env!("BINDINGS")); - -use wit_bindgen::rt::async_support; - -use crate::a::b::the_test::f; - -fn main() { - async_support::block_on(async { - let (tx, rx) = wit_future::new(); - - let a = async { tx.write(()).await }; - let b = async { f(rx) }; - let (a_result, ()) = futures::join!(a, b); - a_result.unwrap(); - }); -} diff --git a/tests/runtime-new-async-disabled/future-write-then-read-remote/runner2.rs b/tests/runtime-new-async-disabled/future-write-then-read-remote/runner2.rs deleted file mode 100644 index 46eb3a2b5..000000000 --- a/tests/runtime-new-async-disabled/future-write-then-read-remote/runner2.rs +++ /dev/null @@ -1,16 +0,0 @@ -include!(env!("BINDINGS")); - -use wit_bindgen::rt::async_support; - -use crate::a::b::the_test::f; - -fn main() { - async_support::block_on(async { - let (tx, rx) = wit_future::new(); - - let a = tx.write(()); - let b = async { f(rx) }; - let (a_result, ()) = futures::join!(a, b); - a_result.unwrap(); - }); -} diff --git a/tests/runtime-new-async-disabled/future-write-then-read-remote/test.rs b/tests/runtime-new-async-disabled/future-write-then-read-remote/test.rs deleted file mode 100644 index 9f8f6920e..000000000 --- a/tests/runtime-new-async-disabled/future-write-then-read-remote/test.rs +++ /dev/null @@ -1,19 +0,0 @@ -//@ args = '--async all' - -include!(env!("BINDINGS")); - -struct Component; - -export!(Component); - -use crate::exports::a::b::the_test::Guest; - -use wit_bindgen::rt::async_support::FutureReader; - -impl Guest for Component { - async fn f(future: FutureReader<()>) { - eprintln!("e1"); - future.await.unwrap(); - eprintln!("e2"); - } -} diff --git a/tests/runtime-new-async-disabled/future-write-then-read-remote/test.wit b/tests/runtime-new-async-disabled/future-write-then-read-remote/test.wit deleted file mode 100644 index 228d6a9e3..000000000 --- a/tests/runtime-new-async-disabled/future-write-then-read-remote/test.wit +++ /dev/null @@ -1,12 +0,0 @@ -package a:b; - -interface the-test { - f: func(param: future); -} - -world test { - export the-test; -} -world runner { - import the-test; -} From 4e65cf4d83ee397b426e2ce1565d410e66cea580 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 29 Mar 2025 10:09:19 -0700 Subject: [PATCH 08/13] Add some tests to `AbiBuffer` --- .github/workflows/main.yml | 5 + crates/guest-rust/rt/src/async_support.rs | 1 - .../rt/src/async_support/abi_buffer.rs | 218 ++++++++++++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 64ebfdec0..e552a24e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -133,6 +133,11 @@ jobs: - run: cargo test -p wit-bindgen-core - run: cargo test -p wit-bindgen - run: cargo test --workspace --exclude 'wit-bindgen*' + - run: cargo test -p wit-bindgen-rt --all-features + - run: rustup update nightly --no-self-update + - run: rustup component install miri + - run: rustup component install rust-src + - run: cargo +nightly miri test -p wit-bindgen-rt --all-features check: name: Check diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index dfd529ebe..06cfcb300 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -6,7 +6,6 @@ extern crate std; use core::sync::atomic::{AtomicBool, Ordering}; -use std::any::Any; use std::boxed::Box; use std::collections::HashMap; use std::fmt::{self, Debug, Display}; diff --git a/crates/guest-rust/rt/src/async_support/abi_buffer.rs b/crates/guest-rust/rt/src/async_support/abi_buffer.rs index 08d18520f..b19ec42ae 100644 --- a/crates/guest-rust/rt/src/async_support/abi_buffer.rs +++ b/crates/guest-rust/rt/src/async_support/abi_buffer.rs @@ -197,3 +197,221 @@ impl Drop for AbiBuffer { let _ = self.take_vec(); } } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + use std::vec; + + extern "C" fn cancel(_: u32) -> u32 { + todo!() + } + extern "C" fn close(_: u32) { + todo!() + } + extern "C" fn new() -> u64 { + todo!() + } + extern "C" fn start_read(_: u32, _: *mut u8, _: usize) -> u32 { + todo!() + } + extern "C" fn start_write(_: u32, _: *const u8, _: usize) -> u32 { + todo!() + } + + static BLANK: StreamVtable = StreamVtable { + cancel_read: cancel, + cancel_write: cancel, + close_readable: close, + close_writable: close, + dealloc_lists: None, + lift: None, + lower: None, + layout: unsafe { Layout::from_size_align_unchecked(1, 1) }, + new, + start_read, + start_write, + }; + + #[test] + fn blank_advance_to_end() { + let mut buffer = AbiBuffer::new(vec![1, 2, 3, 4], &BLANK); + assert_eq!(buffer.remaining(), 4); + buffer.advance(1); + assert_eq!(buffer.remaining(), 3); + buffer.advance(2); + assert_eq!(buffer.remaining(), 1); + buffer.advance(1); + assert_eq!(buffer.remaining(), 0); + assert_eq!(buffer.into_vec(), []); + } + + #[test] + fn blank_advance_partial() { + let buffer = AbiBuffer::new(vec![1, 2, 3, 4], &BLANK); + assert_eq!(buffer.into_vec(), [1, 2, 3, 4]); + let mut buffer = AbiBuffer::new(vec![1, 2, 3, 4], &BLANK); + buffer.advance(1); + assert_eq!(buffer.into_vec(), [2, 3, 4]); + let mut buffer = AbiBuffer::new(vec![1, 2, 3, 4], &BLANK); + buffer.advance(1); + buffer.advance(2); + assert_eq!(buffer.into_vec(), [4]); + } + + #[test] + fn blank_ptr_eq() { + let mut buf = vec![1, 2, 3, 4]; + let ptr = buf.as_mut_ptr(); + let mut buffer = AbiBuffer::new(buf, &BLANK); + let (a, b) = buffer.abi_ptr_and_len(); + assert_eq!(a, ptr); + assert_eq!(b, 4); + unsafe { + assert_eq!(std::slice::from_raw_parts(a, b), [1, 2, 3, 4]); + } + + buffer.advance(1); + let (a, b) = buffer.abi_ptr_and_len(); + assert_eq!(a, ptr.wrapping_add(1)); + assert_eq!(b, 3); + unsafe { + assert_eq!(std::slice::from_raw_parts(a, b), [2, 3, 4]); + } + + buffer.advance(2); + let (a, b) = buffer.abi_ptr_and_len(); + assert_eq!(a, ptr.wrapping_add(3)); + assert_eq!(b, 1); + unsafe { + assert_eq!(std::slice::from_raw_parts(a, b), [4]); + } + + let ret = buffer.into_vec(); + assert_eq!(ret, [4]); + assert_eq!(ret.as_ptr(), ptr); + } + + #[derive(PartialEq, Eq, Debug)] + struct B(u8); + + static OP: StreamVtable = StreamVtable { + cancel_read: cancel, + cancel_write: cancel, + close_readable: close, + close_writable: close, + dealloc_lists: Some(|_ptr| {}), + lift: Some(|ptr| unsafe { B(*ptr - 1) }), + lower: Some(|b, ptr| unsafe { + *ptr = b.0 + 1; + }), + layout: unsafe { Layout::from_size_align_unchecked(1, 1) }, + new, + start_read, + start_write, + }; + + #[test] + fn op_advance_to_end() { + let mut buffer = AbiBuffer::new(vec![B(1), B(2), B(3), B(4)], &OP); + assert_eq!(buffer.remaining(), 4); + buffer.advance(1); + assert_eq!(buffer.remaining(), 3); + buffer.advance(2); + assert_eq!(buffer.remaining(), 1); + buffer.advance(1); + assert_eq!(buffer.remaining(), 0); + assert_eq!(buffer.into_vec(), []); + } + + #[test] + fn op_advance_partial() { + let buffer = AbiBuffer::new(vec![B(1), B(2), B(3), B(4)], &OP); + assert_eq!(buffer.into_vec(), [B(1), B(2), B(3), B(4)]); + let mut buffer = AbiBuffer::new(vec![B(1), B(2), B(3), B(4)], &OP); + buffer.advance(1); + assert_eq!(buffer.into_vec(), [B(2), B(3), B(4)]); + let mut buffer = AbiBuffer::new(vec![B(1), B(2), B(3), B(4)], &OP); + buffer.advance(1); + buffer.advance(2); + assert_eq!(buffer.into_vec(), [B(4)]); + } + + #[test] + fn op_ptrs() { + let mut buf = vec![B(1), B(2), B(3), B(4)]; + let ptr = buf.as_mut_ptr().cast::(); + let mut buffer = AbiBuffer::new(buf, &OP); + let (a, b) = buffer.abi_ptr_and_len(); + let base = a; + assert_ne!(a, ptr); + assert_eq!(b, 4); + unsafe { + assert_eq!(std::slice::from_raw_parts(a, b), [2, 3, 4, 5]); + } + + buffer.advance(1); + let (a, b) = buffer.abi_ptr_and_len(); + assert_ne!(a, ptr.wrapping_add(1)); + assert_eq!(a, base.wrapping_add(1)); + assert_eq!(b, 3); + unsafe { + assert_eq!(std::slice::from_raw_parts(a, b), [3, 4, 5]); + } + + buffer.advance(2); + let (a, b) = buffer.abi_ptr_and_len(); + assert_ne!(a, ptr.wrapping_add(3)); + assert_eq!(a, base.wrapping_add(3)); + assert_eq!(b, 1); + unsafe { + assert_eq!(std::slice::from_raw_parts(a, b), [5]); + } + + let ret = buffer.into_vec(); + assert_eq!(ret, [B(4)]); + assert_eq!(ret.as_ptr(), ptr.cast()); + } + + #[test] + fn dealloc_lists() { + static DEALLOCS: AtomicUsize = AtomicUsize::new(0); + static OP: StreamVtable = StreamVtable { + cancel_read: cancel, + cancel_write: cancel, + close_readable: close, + close_writable: close, + dealloc_lists: Some(|ptr| { + let prev = DEALLOCS.fetch_add(1, Relaxed); + assert_eq!(unsafe { usize::from(*ptr) }, prev + 1); + }), + lift: Some(|ptr| unsafe { B(*ptr) }), + lower: Some(|b, ptr| unsafe { + *ptr = b.0; + }), + layout: unsafe { Layout::from_size_align_unchecked(1, 1) }, + new, + start_read, + start_write, + }; + + assert_eq!(DEALLOCS.load(Relaxed), 0); + let buf = vec![B(1), B(2), B(3), B(4)]; + let mut buffer = AbiBuffer::new(buf, &OP); + assert_eq!(DEALLOCS.load(Relaxed), 0); + buffer.abi_ptr_and_len(); + assert_eq!(DEALLOCS.load(Relaxed), 0); + + buffer.advance(1); + assert_eq!(DEALLOCS.load(Relaxed), 1); + buffer.abi_ptr_and_len(); + assert_eq!(DEALLOCS.load(Relaxed), 1); + buffer.advance(2); + assert_eq!(DEALLOCS.load(Relaxed), 3); + buffer.abi_ptr_and_len(); + assert_eq!(DEALLOCS.load(Relaxed), 3); + buffer.into_vec(); + assert_eq!(DEALLOCS.load(Relaxed), 3); + } +} From 65cebe91d2a257474ce77ef8c21a425bb2d88834 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 29 Mar 2025 10:12:20 -0700 Subject: [PATCH 09/13] Try to fix CI --- .github/workflows/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e552a24e2..8cfff00c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -135,8 +135,7 @@ jobs: - run: cargo test --workspace --exclude 'wit-bindgen*' - run: cargo test -p wit-bindgen-rt --all-features - run: rustup update nightly --no-self-update - - run: rustup component install miri - - run: rustup component install rust-src + - run: rustup component add rust-src --toolchain nightly - run: cargo +nightly miri test -p wit-bindgen-rt --all-features check: From 44fec39066c41afad7b500d204a1bfd70c29a681 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 29 Mar 2025 10:12:47 -0700 Subject: [PATCH 10/13] Add miri too --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8cfff00c7..67a86dd80 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -135,6 +135,7 @@ jobs: - run: cargo test --workspace --exclude 'wit-bindgen*' - run: cargo test -p wit-bindgen-rt --all-features - run: rustup update nightly --no-self-update + - run: rustup component add miri --toolchain nightly - run: rustup component add rust-src --toolchain nightly - run: cargo +nightly miri test -p wit-bindgen-rt --all-features From 917a2e21821e135c35b971b7e159069c7e116d1e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 31 Mar 2025 10:12:32 -0700 Subject: [PATCH 11/13] Add some built-in debugging support Require source edits for now though to enable it. --- crates/guest-rust/rt/src/async_support.rs | 22 ++++++++++++++- .../rt/src/async_support/future_support.rs | 19 ++++++++++--- .../rt/src/async_support/stream_support.rs | 27 +++++++++++++++---- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/crates/guest-rust/rt/src/async_support.rs b/crates/guest-rust/rt/src/async_support.rs index 06cfcb300..5a9f25fd0 100644 --- a/crates/guest-rust/rt/src/async_support.rs +++ b/crates/guest-rust/rt/src/async_support.rs @@ -23,6 +23,18 @@ use futures::future::FutureExt; use futures::stream::{FuturesUnordered, StreamExt}; use once_cell::sync::Lazy; +macro_rules! rtdebug { + ($($f:tt)*) => { + // Change this flag to enable debugging, right now we're not using a + // crate like `log` or such to reduce runtime deps. Intended to be used + // during development for now. + if false { + std::println!($($f)*); + } + } + +} + mod abi_buffer; mod future_support; mod stream_support; @@ -295,11 +307,16 @@ unsafe fn callback_with_state( ) -> i32 { match event0 { EVENT_NONE => { + rtdebug!("EVENT_NONE"); let done = poll(state).is_ready(); callback_code(state, done) } - EVENT_CALL_STARTED => callback_code(state, false), + EVENT_CALL_STARTED => { + rtdebug!("EVENT_CALL_STARTED"); + callback_code(state, false) + } EVENT_CALL_RETURNED => { + rtdebug!("EVENT_CALL_RETURNED({event1:#x}, {event2:#x})"); (*state).remove_waitable(event1 as _); if let Some(call) = CALLS.remove(&event1) { @@ -318,6 +335,9 @@ unsafe fn callback_with_state( } EVENT_STREAM_READ | EVENT_STREAM_WRITE | EVENT_FUTURE_READ | EVENT_FUTURE_WRITE => { + rtdebug!( + "EVENT_{{STREAM,FUTURE}}_{{READ,WRITE}}({event0:#x}, {event1:#x}, {event2:#x})" + ); (*state).remove_waitable(event1 as u32); let (waker, code) = (*state).wakers.remove(&(event1 as u32)).unwrap(); *code = Some(event2 as u32); diff --git a/crates/guest-rust/rt/src/async_support/future_support.rs b/crates/guest-rust/rt/src/async_support/future_support.rs index 8cdc58680..ac3432f04 100644 --- a/crates/guest-rust/rt/src/async_support/future_support.rs +++ b/crates/guest-rust/rt/src/async_support/future_support.rs @@ -165,9 +165,12 @@ pub unsafe fn future_new( ) -> (FutureWriter, FutureReader) { unsafe { let handles = (vtable.new)(); + let writer = handles as u32; + let reader = (handles >> 32) as u32; + rtdebug!("future.new() = [{writer}, {reader}]"); ( - FutureWriter::new(handles as u32, vtable), - FutureReader::new((handles >> 32) as u32, vtable), + FutureWriter::new(writer, vtable), + FutureReader::new(reader, vtable), ) } } @@ -238,6 +241,7 @@ impl fmt::Debug for FutureWriter { impl Drop for FutureWriter { fn drop(&mut self) { unsafe { + rtdebug!("future.close-writable({})", self.handle); (self.vtable.close_writable)(self.handle); } } @@ -285,6 +289,7 @@ where (writer.vtable.lower)(value, ptr); (writer.vtable.start_write)(writer.handle, ptr) }; + rtdebug!("future.write({}, {ptr:?}) = {code:#x}", writer.handle); (code, (writer, cleanup)) } @@ -339,7 +344,9 @@ where fn in_progress_cancel((writer, _): &Self::InProgress) -> u32 { // SAFETY: we're managing `writer` and all the various operational bits, // so this relies on `WaitableOperation` being safe. - unsafe { (writer.vtable.cancel_write)(writer.handle) } + let code = unsafe { (writer.vtable.cancel_write)(writer.handle) }; + rtdebug!("future.cancel-write({}) = {code:#x}", writer.handle); + code } fn in_progress_canceled(state: Self::InProgress) -> Self::Result { @@ -530,6 +537,7 @@ impl Drop for FutureReader { return; }; unsafe { + rtdebug!("future.close-readable({handle})"); (self.vtable.close_readable)(handle); } } @@ -566,6 +574,7 @@ where // safe to use here. Its lifetime for the async operation is hinged on // `WaitableOperation` being safe. let code = unsafe { (reader.vtable.start_read)(reader.handle(), ptr) }; + rtdebug!("future.read({}, {ptr:?}) = {code:#x}", reader.handle()); (code, (reader, cleanup)) } @@ -602,7 +611,9 @@ where fn in_progress_cancel((reader, _): &Self::InProgress) -> u32 { // SAFETY: we're managing `reader` and all the various operational bits, // so this relies on `WaitableOperation` being safe. - unsafe { (reader.vtable.cancel_read)(reader.handle()) } + let code = unsafe { (reader.vtable.cancel_read)(reader.handle()) }; + rtdebug!("future.cancel-read({}) = {code:#x}", reader.handle()); + code } /// Like `in_progress_closed` the read operation has finished but without a diff --git a/crates/guest-rust/rt/src/async_support/stream_support.rs b/crates/guest-rust/rt/src/async_support/stream_support.rs index 84c8ab6f2..4201a2bdc 100644 --- a/crates/guest-rust/rt/src/async_support/stream_support.rs +++ b/crates/guest-rust/rt/src/async_support/stream_support.rs @@ -71,9 +71,12 @@ pub unsafe fn stream_new( ) -> (StreamWriter, StreamReader) { unsafe { let handles = (vtable.new)(); + let writer = handles as u32; + let reader = (handles >> 32) as u32; + rtdebug!("stream.new() = [{writer}, {reader}]"); ( - StreamWriter::new(handles as u32, vtable), - StreamReader::new((handles >> 32) as u32, vtable), + StreamWriter::new(writer, vtable), + StreamReader::new(reader, vtable), ) } } @@ -192,6 +195,7 @@ impl fmt::Debug for StreamWriter { impl Drop for StreamWriter { fn drop(&mut self) { + rtdebug!("stream.close-writable({})", self.handle); unsafe { (self.vtable.close_writable)(self.handle); } @@ -234,6 +238,10 @@ where // SAFETY: sure hope this is safe, everything in this module and // `AbiBuffer` is trying to make this safe. let code = unsafe { (writer.vtable.start_write)(writer.handle, ptr, len) }; + rtdebug!( + "stream.write({}, {ptr:?}, {len}) = {code:#x}", + writer.handle + ); (code, (writer, buf)) } @@ -258,7 +266,9 @@ where fn in_progress_cancel((writer, _): &Self::InProgress) -> u32 { // SAFETY: we're managing `writer` and all the various operational bits, // so this relies on `WaitableOperation` being safe. - unsafe { (writer.vtable.cancel_write)(writer.handle) } + let code = unsafe { (writer.vtable.cancel_write)(writer.handle) }; + rtdebug!("stream.cancel-write({}) = {code:#x}", writer.handle); + code } fn in_progress_canceled((_writer, buf): Self::InProgress) -> Self::Result { @@ -404,6 +414,7 @@ impl Drop for StreamReader { return; }; unsafe { + rtdebug!("stream.close-readable({})", handle); (self.vtable.close_readable)(handle); } } @@ -446,11 +457,15 @@ where // SAFETY: `ptr` is either in `buf` or in `cleanup`, both of which will // persist with this async operation itself. let code = unsafe { (reader.vtable.start_read)(reader.handle(), ptr, cap.len()) }; + rtdebug!( + "stream.read({}, {ptr:?}, {}) = {code:#x}", + reader.handle(), + cap.len() + ); (code, (reader, buf, cleanup)) } fn start_cancel((_, buf): Self::Start) -> Self::Cancel { - std::dbg!("start_cancel"); (StreamResult::Cancelled, buf) } @@ -503,7 +518,9 @@ where fn in_progress_cancel((reader, ..): &Self::InProgress) -> u32 { // SAFETY: we're managing `reader` and all the various operational bits, // so this relies on `WaitableOperation` being safe. - unsafe { (reader.vtable.cancel_read)(reader.handle()) } + let code = unsafe { (reader.vtable.cancel_read)(reader.handle()) }; + rtdebug!("stream.cancel-read({}) = {code:#x}", reader.handle()); + code } /// When an in-progress read is successfully cancel then the allocation From 27d673ac8c2375b30f5aa5b92d03724641e25ea1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 31 Mar 2025 10:17:02 -0700 Subject: [PATCH 12/13] Document `CompletionStatus` --- .../rt/src/async_support/waitable.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/guest-rust/rt/src/async_support/waitable.rs b/crates/guest-rust/rt/src/async_support/waitable.rs index b40a2f0a5..7fcdce3b8 100644 --- a/crates/guest-rust/rt/src/async_support/waitable.rs +++ b/crates/guest-rust/rt/src/async_support/waitable.rs @@ -15,11 +15,36 @@ use std::task::{Context, Poll}; /// on each state transition. pub struct WaitableOperation { state: WaitableOperationState, + /// Storage for the final result of this asynchronous operation, if it's + /// completed asynchronously. completion_status: CompletionStatus, } +/// Structure used to store the `u32` return code from the canonical ABI about +/// an asynchronous operation. +/// +/// When an asynchronous operation is started and it does not immediately +/// complete then this structure is used to asynchronously fill in the return +/// code. A `Pin<&mut CompletionStatus>` is used to register a pointer with +/// `FutureState` to get filled in. +/// +/// Note that this means that this type is participating in unsafe lifetime +/// management and has properties it needs to uphold as a result. Specifically +/// the `PhantomPinned` field here means that `Pin` actually has meaning for +/// this structure, notably that once `Pin<&mut CompletionStatus>` is created +/// then it's guaranteed the destructor will be run before the backing memory +/// is deallocated. That's used in `WaitableOperation` above to share an +/// internal pointer of this data structure with `FuturesState` safely. The +/// destructor of `WaitableOperation` will deregister from `FutureState` meaning +/// that if `FuturesState` has a pointer here then it should be valid . struct CompletionStatus { + /// Where the async operation's code is filled in, and `None` until that + /// happens. code: Option, + + /// This is necessary to ensure that `Pin<&mut CompletionStatus>` carries + /// the "pin guarantee", basically to mean that it's not safe to construct + /// `Pin<&mut CompletionStatus>` and it must somehow require `unsafe` code. _pinned: marker::PhantomPinned, } From 271a047c1a0b05fd2b0e7c3c0d9e83d71cf70d8f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 31 Mar 2025 14:05:50 -0700 Subject: [PATCH 13/13] Review comments --- .../rt/src/async_support/abi_buffer.rs | 2 +- .../rt/src/async_support/future_support.rs | 70 ++++++------------- .../rt/src/async_support/stream_support.rs | 16 ++--- .../rt/src/async_support/waitable.rs | 10 +-- 4 files changed, 36 insertions(+), 62 deletions(-) diff --git a/crates/guest-rust/rt/src/async_support/abi_buffer.rs b/crates/guest-rust/rt/src/async_support/abi_buffer.rs index b19ec42ae..04077fef4 100644 --- a/crates/guest-rust/rt/src/async_support/abi_buffer.rs +++ b/crates/guest-rust/rt/src/async_support/abi_buffer.rs @@ -75,7 +75,7 @@ impl AbiBuffer { /// Returns the canonical ABI pointer/length to pass off to a write /// operation. pub(crate) fn abi_ptr_and_len(&self) -> (*const u8, usize) { - // If there's no `lower` operation then it menas that `T`'s layout is + // If there's no `lower` operation then it means that `T`'s layout is // the same in the canonical ABI so it can be used as-is. In this // situation the list would have been un-tampered with above. if self.vtable.lower.is_none() { diff --git a/crates/guest-rust/rt/src/async_support/future_support.rs b/crates/guest-rust/rt/src/async_support/future_support.rs index ac3432f04..969517b73 100644 --- a/crates/guest-rust/rt/src/async_support/future_support.rs +++ b/crates/guest-rust/rt/src/async_support/future_support.rs @@ -247,7 +247,7 @@ impl Drop for FutureWriter { } } -/// Represents a write operation which may be canceled prior to completion. +/// Represents a write operation which may be cancelled prior to completion. /// /// This is returned by [`FutureWriter::write`]. pub struct FutureWrite { @@ -269,7 +269,7 @@ where type Start = (FutureWriter, T); type InProgress = (FutureWriter, Option); type Result = (WriteComplete, FutureWriter); - type Cancel = Result<(), FutureWriteCancel>; + type Cancel = FutureWriteCancel; fn start((writer, value): Self::Start) -> (u32, Self::InProgress) { // TODO: it should be safe to store the lower-destination in @@ -293,8 +293,8 @@ where (code, (writer, cleanup)) } - fn start_cancel((writer, value): Self::Start) -> Self::Cancel { - Err(FutureWriteCancel::Cancelled(value, writer)) + fn start_cancelled((writer, value): Self::Start) -> Self::Cancel { + FutureWriteCancel::Cancelled(value, writer) } /// This write has completed. @@ -349,7 +349,7 @@ where code } - fn in_progress_canceled(state: Self::InProgress) -> Self::Result { + fn in_progress_cancelled(state: Self::InProgress) -> Self::Result { match Self::in_progress_closed(state) { (WriteComplete::Closed(value), writer) => (WriteComplete::Cancelled(value), writer), _ => unreachable!(), @@ -360,13 +360,13 @@ where match result { // The value was actually sent, meaning we can't yield back the // future nor the value. - WriteComplete::Written => Ok(()), + WriteComplete::Written => FutureWriteCancel::AlreadySent, // The value was not sent because the other end either hung up or we - // successfully canceled. In both cases return back the value here + // successfully cancelled. In both cases return back the value here // with the writer. - WriteComplete::Closed(val) => Err(FutureWriteCancel::Closed(val)), - WriteComplete::Cancelled(val) => Err(FutureWriteCancel::Cancelled(val, writer)), + WriteComplete::Closed(val) => FutureWriteCancel::Closed(val), + WriteComplete::Cancelled(val) => FutureWriteCancel::Cancelled(val, writer), } } } @@ -396,28 +396,15 @@ impl FutureWrite { /// Cancel this write if it hasn't already completed. /// /// This method can be used to cancel a write-in-progress and re-acquire - /// the writer and the value being sent. If the write operation has already - /// succeeded racily then `None` is returned and the write completed. - /// - /// Possible return values are: - /// - /// * `Ok(())` - the pending write completed before cancellation went - /// through meaning that the original message was actually sent. - /// * `Err(FutureWriteCancel::Closed(v))` - the pending write did not complete - /// because the other end was closed before receiving the value. The value - /// is provided back here as part of the error. - /// * `Err(FutureWriteCancel::Cancelled(v, writer))` - the pending write was - /// cancelled. The value `v` is returned back and the `writer` is returned - /// as well to resume a write in the future if desired. - /// - /// Note that if this method is called after the write was already cancelled - /// then `Ok(())` will be returned. + /// the writer and the value being sent. Note that the write operation may + /// succeed racily or the other end may also close racily, and these + /// outcomes are reflected in the returned value here. /// /// # Panics /// /// Panics if the operation has already been completed via `Future::poll`, /// or if this method is called twice. - pub fn cancel(self: Pin<&mut Self>) -> Result<(), FutureWriteCancel> { + pub fn cancel(self: Pin<&mut Self>) -> FutureWriteCancel { self.pin_project().cancel() } } @@ -443,9 +430,13 @@ impl fmt::Display for FutureWriteError { impl std::error::Error for FutureWriteError {} -/// Error type in the result of [`FutureWrite::cancel`], or the error type that is a -/// result of cancelling a pending write. +/// Result of [`FutureWrite::cancel`]. pub enum FutureWriteCancel { + /// The cancel request raced with the receipt of the sent value, and the + /// value was actually sent. Neither the value nor the writer are made + /// available here as both are gone. + AlreadySent, + /// The other end was closed before cancellation happened. /// /// In this case the original value is returned back to the caller but the @@ -458,23 +449,6 @@ pub enum FutureWriteCancel { Cancelled(T, FutureWriter), } -impl fmt::Debug for FutureWriteCancel { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FutureWriteCancel").finish_non_exhaustive() - } -} - -impl fmt::Display for FutureWriteCancel { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FutureWriteCancel::Closed(_) => "read end closed".fmt(f), - FutureWriteCancel::Cancelled(..) => "write cancelled".fmt(f), - } - } -} - -impl std::error::Error for FutureWriteCancel {} - /// Represents the readable end of a Component Model `future`. pub struct FutureReader { handle: AtomicU32, @@ -543,7 +517,7 @@ impl Drop for FutureReader { } } -/// Represents a read operation which may be canceled prior to completion. +/// Represents a read operation which may be cancelled prior to completion. /// /// This represents a read operation on a [`FutureReader`] and is created via /// `IntoFuture`. @@ -578,7 +552,7 @@ where (code, (reader, cleanup)) } - fn start_cancel(state: Self::Start) -> Self::Cancel { + fn start_cancelled(state: Self::Start) -> Self::Cancel { Err(state) } @@ -618,7 +592,7 @@ where /// Like `in_progress_closed` the read operation has finished but without a /// value, so let `_cleanup` fall out of scope to clean up its allocation. - fn in_progress_canceled((reader, _cleanup): Self::InProgress) -> Self::Result { + fn in_progress_cancelled((reader, _cleanup): Self::InProgress) -> Self::Result { (ReadComplete::Cancelled, reader) } diff --git a/crates/guest-rust/rt/src/async_support/stream_support.rs b/crates/guest-rust/rt/src/async_support/stream_support.rs index 4201a2bdc..2b0fd26dd 100644 --- a/crates/guest-rust/rt/src/async_support/stream_support.rs +++ b/crates/guest-rust/rt/src/async_support/stream_support.rs @@ -97,7 +97,7 @@ impl StreamWriter { /// /// This method is akin to an `async fn` except that the returned /// [`StreamWrite`] future can also be cancelled via [`StreamWrite::cancel`] - /// to re-acquire intermediate values. + /// to re-acquire undelivered values. /// /// This method will perform at most a single write of the `values` /// provided. The returned future will resolve once the write has completed. @@ -202,7 +202,7 @@ impl Drop for StreamWriter { } } -/// Represents a write operation which may be canceled prior to completion. +/// Represents a write operation which may be cancelled prior to completion. pub struct StreamWrite<'a, T: 'static> { op: WaitableOperation>, } @@ -245,7 +245,7 @@ where (code, (writer, buf)) } - fn start_cancel((_writer, buf): Self::Start) -> Self::Cancel { + fn start_cancelled((_writer, buf): Self::Start) -> Self::Cancel { (StreamResult::Cancelled, buf) } @@ -271,7 +271,7 @@ where code } - fn in_progress_canceled((_writer, buf): Self::InProgress) -> Self::Result { + fn in_progress_cancelled((_writer, buf): Self::InProgress) -> Self::Result { (StreamResult::Cancelled, buf) } @@ -420,7 +420,7 @@ impl Drop for StreamReader { } } -/// Represents a read operation which may be canceled prior to completion. +/// Represents a read operation which may be cancelled prior to completion. pub struct StreamRead<'a, T: 'static> { op: WaitableOperation>, } @@ -465,7 +465,7 @@ where (code, (reader, buf, cleanup)) } - fn start_cancel((_, buf): Self::Start) -> Self::Cancel { + fn start_cancelled((_, buf): Self::Start) -> Self::Cancel { (StreamResult::Cancelled, buf) } @@ -505,7 +505,7 @@ where (StreamResult::Complete(amt), buf) } - /// Like `in_progress_canceled` below, discard the temporary cleanup + /// Like `in_progress_cancelled` below, discard the temporary cleanup /// allocation, if any. fn in_progress_closed((_reader, buf, _cleanup): Self::InProgress) -> Self::Result { (StreamResult::Closed, buf) @@ -528,7 +528,7 @@ where /// /// TODO: should maybe thread this around like `AbiBuffer` to cache the /// read allocation? - fn in_progress_canceled((_reader, buf, _cleanup): Self::InProgress) -> Self::Result { + fn in_progress_cancelled((_reader, buf, _cleanup): Self::InProgress) -> Self::Result { (StreamResult::Cancelled, buf) } diff --git a/crates/guest-rust/rt/src/async_support/waitable.rs b/crates/guest-rust/rt/src/async_support/waitable.rs index 7fcdce3b8..956fc46aa 100644 --- a/crates/guest-rust/rt/src/async_support/waitable.rs +++ b/crates/guest-rust/rt/src/async_support/waitable.rs @@ -82,7 +82,7 @@ pub unsafe trait WaitableOp { /// Conversion from the "start" state to the "cancel" result, needed when an /// operation is cancelled before it's started. - fn start_cancel(state: Self::Start) -> Self::Cancel; + fn start_cancelled(state: Self::Start) -> Self::Cancel; /// Completion callback for when an in-progress operation has completed /// successfully after transferring `amt` items. @@ -101,12 +101,12 @@ pub unsafe trait WaitableOp { /// intrinsic. fn in_progress_cancel(state: &Self::InProgress) -> u32; - /// Completion callback for when an operation was canceled. + /// Completion callback for when an operation was cancelled. /// /// This is invoked after `in_progress_cancel` is used and the returned /// status code indicates that the operation was indeed cancelled and didn't /// racily return some other result. - fn in_progress_canceled(state: Self::InProgress) -> Self::Result; + fn in_progress_cancelled(state: Self::InProgress) -> Self::Result; /// Converts a "completion result" into a "cancel result". This is necessary /// when an in-progress operation is cancelled so the in-progress result is @@ -279,7 +279,7 @@ where // The other end has closed or the operation was cancelled and the // operation did not complete. See what `S` has to say about that. results::CLOSED => Poll::Ready(S::in_progress_closed(in_progress)), - results::CANCELED => Poll::Ready(S::in_progress_canceled(in_progress)), + results::CANCELED => Poll::Ready(S::in_progress_cancelled(in_progress)), // This operation has completed, transferring `n` units of memory. // @@ -311,7 +311,7 @@ where let Start(s) = mem::replace(state, Done) else { unreachable!() }; - return S::start_cancel(s); + return S::start_cancelled(s); } // This operation is actively in progress, fall through to below.