From 57ad3250d07f6ad078baca89eb7a1af0ee4be847 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 18:53:13 -0800 Subject: [PATCH 1/4] quest: audit the tree against the code and moq Fix dead moq links, move upstream waits into Waiting sections, narrow the ffi stand-in first, merge the two moq pin bumps, and say a glob of a crate root exports its items. Co-Authored-By: Claude Opus 5.5 --- bench/build/README.md | 1 + bench/ffi/README.md | 2 +- ffi/README.md | 4 +- quest/README.md | 16 +++---- quest/m1/README.md | 2 +- quest/m1/ffi-export/README.md | 4 +- quest/m1/ffi-export/refusals/README.md | 22 ++++----- quest/m1/ffi-export/refusals/moq-pin.md | 46 +++++++++++++++++++ quest/m1/ffi-export/refusals/moq-reexports.md | 32 ------------- .../m1/ffi-export/refusals/moq-unreachable.md | 24 ---------- .../m1/ffi-export/refusals/narrow-standin.md | 27 +++++++++++ quest/m1/ffi-export/refusals/reexports.md | 11 +++-- quest/m1/ffi-export/session-quic.md | 15 +++--- quest/m2/ubrn-release.md | 13 +++--- quest/m2/uniffi-swift-arm.md | 18 +++++--- quest/m3/cranelift-unwind.md | 13 ++++-- quest/m3/relink.md | 20 ++++---- quest/m3/ts-array-ref-conversions.md | 4 ++ quest/m3/ts-fma-allowance.md | 12 +++-- quest/m3/ts-tsrs.md | 12 +++-- quest/m3/ts-wasm-bindgen/README.md | 2 +- quest/m4/README.md | 2 +- quest/m4/fork.md | 9 +++- quest/m4/go/enums.md | 19 ++++---- 24 files changed, 196 insertions(+), 134 deletions(-) create mode 100644 quest/m1/ffi-export/refusals/moq-pin.md delete mode 100644 quest/m1/ffi-export/refusals/moq-reexports.md delete mode 100644 quest/m1/ffi-export/refusals/moq-unreachable.md create mode 100644 quest/m1/ffi-export/refusals/narrow-standin.md diff --git a/bench/build/README.md b/bench/build/README.md index 37dbf8e17..a1b493222 100644 --- a/bench/build/README.md +++ b/bench/build/README.md @@ -127,6 +127,7 @@ Measured on 2026-10-25 and 2026-10-26 on a busy 32-CPU host, in billions of inst ## Cranelift for moq's CI Recommended on 2026-10-25: moq's CI keeps building its tests with LLVM, since 42 of its 5418 tests fail under Cranelift. +At the pin [#646](https://github.com/moq-dev/rust2/pull/646) moved to on 2026-11-07, 58 of 6379 fail: 27 that catch a panic and 31 on the intrinsic ([cranelift-unwind](/quest/m3/cranelift-unwind.md)). The numbers below are the 2026-10-25 run's. - 23 catch a panic, which the Cranelift backend that ships with nightly cannot unwind: it builds every panic as an abort (unwinding is a flag on cg_clif's own build, off by default: [rustc_codegen_cranelift#1567](https://github.com/rust-lang/rustc_codegen_cranelift/issues/1567)). - 19 abort on an x86 SIMD intrinsic it does not implement, `llvm.x86.sse2.pmulhu.w` ([rustc_codegen_cranelift#171](https://github.com/rust-lang/rustc_codegen_cranelift/issues/171)). moq's CI runs on aarch64, whose intrinsics this x86 benchmark does not try. diff --git a/bench/ffi/README.md b/bench/ffi/README.md index e96dce2d6..3811994fb 100644 --- a/bench/ffi/README.md +++ b/bench/ffi/README.md @@ -110,7 +110,7 @@ Native libraries are `release` (fat LTO, `strip = "debuginfo"`, as moq-ffi ships | slice, Swift: UniFFI / BoltFFI | 3 / 2 | 101 / 51 KiB | 14 / 8 KiB | | moq-ffi, UniFFI: Python / Kotlin / Swift | 1 / 1 / 3 | 653 / 914 / 684 KiB | 66 / 73 / 72 KiB | -Browser builds use opt-level z after wasm-bindgen and no wasm-opt, as moq-wasm ships; the stripped columns drop the `name` section. +Browser builds use opt-level z after wasm-bindgen and no wasm-opt, as moq-wasm shipped before moq removed it; the stripped columns drop the `name` section. | browser module | .wasm as built | .wasm stripped | stripped, gzip | JS glue | JS glue, gzip | |---|---|---|---|---|---| diff --git a/ffi/README.md b/ffi/README.md index fc23574f6..8125eb80d 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -1,8 +1,8 @@ # rust2-ffi A rustc driver that exports a crate's `pub` API as a C ABI and a C header, with no -attributes in the Rust source. It replaces [the UniFFI fork](../uniffi/README.md) for moq, -starting from C, which every other language's generator will build on. +attributes in the Rust source. It replaces [the UniFFI fork](../uniffi/README.md) for moq. +Every language's generator but Python's builds on the C ABI. ```bash rust2-ffi --config ffi.toml (--out dir | --census dir) [--manifest-path Cargo.toml] [-- cargo args] diff --git a/quest/README.md b/quest/README.md index 98b764ae1..0ffb28aa1 100644 --- a/quest/README.md +++ b/quest/README.md @@ -8,13 +8,13 @@ grouped into milestones ordered by priority. ## Plan m0 holds urgent fixes. m1 is the work with a consumer waiting on it now: -the exporter that replaces the UniFFI fork (moq skips switching to the fork), and -the fork's perf work. m2 is research: the TypeScript translator, which moq's -browser package adopts once it matches the handwritten one, and the fork's gated or -speculative work. m3 -holds the build-speed tracks, which the -[build-speed benchmark](/bench/build/README.md) rescopes before they start, -and speculative work. m4 holds long-term research. +the exporter that replaces the UniFFI fork (moq skips switching to the fork). m2 is +research: the TypeScript translator, which moq's browser package adopts once it matches +the handwritten one, and the fork's gated or speculative work. m3 holds work that waits +on evidence or has no consumer yet: the build-speed tracks, which the +[build-speed benchmark](/bench/build/README.md) rescopes before they start, and the +parts of std and the translators' features no consumer calls yet. m4 holds long-term +research. Decided with the maintainer on 2026-10-23, when rust2 was planned from moq ([moq-dev/moq#6578](https://github.com/moq-dev/moq/pull/6578)): @@ -32,5 +32,5 @@ Decided with the maintainer on 2026-10-23, when rust2 was planned from moq - [m0: immediate priorities](/quest/m0/README.md) - urgent fixes - [m1: consumers waiting](/quest/m1/README.md) - the exporter that replaces the UniFFI fork for moq - [m2: research](/quest/m2/README.md) - the TypeScript translator's spikes and features toward moq-net's model, and the fork's gated work -- [m3: deferred](/quest/m3/README.md) - the pure-Rust profile and upstream's build-speed levers, measured by the benchmark +- [m3: deferred](/quest/m3/README.md) - upstream's build-speed levers, measured by the benchmark, and the std gaps and translator features no consumer calls yet - [m4: long-term research](/quest/m4/README.md) - ideas no consumer needs yet, such as a rustc and cargo fork diff --git a/quest/m1/README.md b/quest/m1/README.md index 23487cc6d..c92c3d4d0 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -14,7 +14,7 @@ fork stays in maintenance and keeps building moq's bindings until then; its feat are folded into the [exporter's](/quest/m1/ffi-export/README.md). The [binding benchmark](/bench/ffi/README.md) continues as the exporter's baseline. -Decided 2026-11-02 (maintainer, moq's /quest-unblock): the fork is not published to crates.io. moq skips it and switches straight to the exporter ([moq's switch](https://github.com/moq-dev/moq/blob/main/quest/m2/ffi-exporter.md)), so publishing would only commit permanent `rust2-*` names to code already slated for retirement. moq keeps its own pinned generator forks until the exporter is ready. +Decided 2026-11-02 (maintainer, moq's /quest-unblock): the fork is not published to crates.io. moq skips it and switches straight to the exporter ([moq's switch](https://github.com/moq-dev/moq/blob/main/quest/m3/ffi-exporter.md)), so publishing would only commit permanent `rust2-*` names to code already slated for retirement. moq keeps its own pinned generator forks until the exporter is ready. ## Required diff --git a/quest/m1/ffi-export/README.md b/quest/m1/ffi-export/README.md index b7b12ba23..6b68fe4b9 100644 --- a/quest/m1/ffi-export/README.md +++ b/quest/m1/ffi-export/README.md @@ -49,7 +49,7 @@ Decided with the maintainer on 2026-11-01: the Swift, Python, Go, and Dart gener independent and unranked, and each generates the crate's own API in its language's idiom, as Kotlin does. Only Python skips the C ABI, since a CPython extension calls about 40 times faster than `ctypes`; the others call C natively. moq's packages switch to them in moq's own -[quest](https://github.com/moq-dev/moq/blob/main/quest/m2/uniffi-fork.md). +[quest](https://github.com/moq-dev/moq/blob/main/quest/m3/ffi-exporter.md). Decided with the maintainer on 2026-11-02 ([moq-dev/rust2#460](https://github.com/moq-dev/rust2/pull/460)): the RSS glibc's per-thread @@ -61,5 +61,5 @@ names and defaults, and fails the export on a public item it cannot export. ## Required -- [Sessions on sans-IO QUIC](/quest/m1/ffi-export/session-quic.md) - bindings move UDP datagrams and drive the session's poll themselves, over moq's runtime-free QUIC driver once it lands - [Every public item exports](/quest/m1/ffi-export/refusals/README.md) - the census lists no refusals and `ffi.toml` is deleted, so the exporter reads no config +- [Sessions on sans-IO QUIC](/quest/m1/ffi-export/session-quic.md) - bindings move UDP datagrams and drive the session's poll themselves, over moq's runtime-free QUIC driver once it lands diff --git a/quest/m1/ffi-export/refusals/README.md b/quest/m1/ffi-export/refusals/README.md index f9c102166..20faf940f 100644 --- a/quest/m1/ffi-export/refusals/README.md +++ b/quest/m1/ffi-export/refusals/README.md @@ -6,14 +6,11 @@ `just ffi census` lists no refusals at the moq pin and `ffi.toml` is deleted: the exporter reads no config, and an item it cannot export fails the export with a span. -What stays here once every child has merged: - -- The stand-in facade (`just ffi standin`) re-exports only the session side moq-ffi ships - from moq-tokio: `Client`, `Server`, `server::Request`, `connect::Config`, - `listen::Config`, `quic::Config`, `Connection`, `Transport`, `Log`, `tls`, `panic`, and - the errors, in place of `pub use ::moq_tokio::*;`. -- `ffi/moq/ffi.toml`, `--config`, and `ffi/src/config.rs` are deleted, and `ffi/README.md` - and the recipes stop naming them. +What stays here once every child has merged: `ffi/moq/ffi.toml`, the `--config` flag, +`RUST2_FFI_CONFIG`, and `config.rs`'s `Config` and `exclude` are deleted, with the +fixtures' `ffi/fixtures/api/ffi.toml` and `ffi/fixtures/reject/ffi.toml` and the exclude +tests in `ffi/tests/fixtures.rs`; `ffi/README.md` and the recipes stop naming them. +`config.rs`'s `Package` and environment constants stay. ## Plan @@ -38,6 +35,8 @@ Decided with the maintainer on 2026-11-07, from binding calls; the sans-IO driver bindings use instead is [session-quic](/quest/m1/ffi-export/session-quic.md). Children leave the moq-tokio items outside that session side alone, since narrowing the stand-in drops them. +- ✅ Narrowing the stand-in is the first child, so the others count only what stays + (decided in the quest audit). Rejected: narrowing last, as this epic's own work. - ✅ What C can never hold, an object no export returns or lends to a closure, or a generic type no export instantiates, is left out of the bindings and kept listed in the census. Rejected: refusing them, which would make moq add constructors it has no use for. @@ -47,13 +46,15 @@ Decided with the maintainer on 2026-11-07, from The census at moq `ffcb7cd9` (2026-11-07) refuses 1111 items, which `ffi/moq/ffi.toml`'s 300 excludes hide by naming whole modules. About 790 lie in crates moq re-exports whole. The other 324 are moq's own, 107 of them in moq-tokio (61 under `quic`); each child gives -its construct's count, and an item counts under every construct it hits. 17 more refuse +its construct's count, and an item counts under every construct it hits. The counts +include the moq-tokio items narrowing drops; each child measures again once it lands. 17 more refuse only because a type they name is refused, and clear with it. Public API: the exporter's command line and the C API of every item that exports. ## Required +- [The stand-in exports only moq-tokio's session side](/quest/m1/ffi-export/refusals/narrow-standin.md) - `just ffi standin` names the session items moq-ffi ships in place of `pub use ::moq_tokio::*;` - [Whole-crate re-exports are refused with one span](/quest/m1/ffi-export/refusals/reexports.md) - the exporter stops at a `pub use` of a crate root, turning about 790 refusals into one per re-export - [Generic parameters and traits export](/quest/m1/ffi-export/refusals/generics.md) - `Client::connect`'s `Boxable`, futures and readers as parameters, trait objects, associated types, and callbacks - [Standard and common types export by role](/quest/m1/ffi-export/refusals/std-types.md) - maps, sets, errors, `NonZero`, addresses, instants, sockets, levels, JSON maps, and iterator adapters @@ -61,5 +62,4 @@ Public API: the exporter's command line and the C API of every item that exports - [Borrow shapes export](/quest/m1/ffi-export/refusals/borrows.md) - `Option<&T>`, `&mut` buffers, `&Bytes`, `Arc`, `'static` borrows, and borrowed returns that are not `Clone` - [Small shapes export](/quest/m1/ffi-export/refusals/small-shapes.md) - `u128`, arrays, const generics, unit and `Result` off a return, `!`, empty enums, and what is not `Send` - [What C can never hold is left out and listed](/quest/m1/ffi-export/refusals/unholdable.md) - objects no export returns or lends to a closure, and generics no export instantiates leave the bindings but stay in the census -- [moq drops its whole-crate re-exports](/quest/m1/ffi-export/refusals/moq-reexports.md) - the moq pin moves past the change, and the census has no re-export refusal left -- [moq's types are reachable from their crate root](/quest/m1/ffi-export/refusals/moq-unreachable.md) - the moq pin moves past moq-mux's `Guard` and moq-quic's `Side` fixes +- [Bump the moq pin past its re-export and reachability fixes](/quest/m1/ffi-export/refusals/moq-pin.md) - the census has no whole-crate re-export refusal and no unreachable type of moq's own diff --git a/quest/m1/ffi-export/refusals/moq-pin.md b/quest/m1/ffi-export/refusals/moq-pin.md new file mode 100644 index 000000000..93271d7eb --- /dev/null +++ b/quest/m1/ffi-export/refusals/moq-pin.md @@ -0,0 +1,46 @@ +# [XS] Bump the moq pin past its re-export and reachability fixes + +## Goal + +The moq pin moves to a revision whose crates re-export no whole crate, moq's own included, +and where every type a facade export names is reachable from the facade's root. `just ffi +census` there lists no whole-crate re-export refusal and no "type not reachable from the +crate root" refusal of moq's own (18 at moq `ffcb7cd9`). + +## Plan + +Decided with the maintainer on 2026-11-07, from +[moq-dev/rust2#655](https://github.com/moq-dev/rust2/pull/655) and +[moq-dev/moq#8233](https://github.com/moq-dev/moq/pull/8233): ✅ moq re-exports no whole +crate, its own included: the ten first listed (moq-net's `bytes` and `kio`; `rustls` in +moq-quic, moq-rtmp, and moq-tokio; moq-tokio's `notify`, `usage_rs`, and `http`; +moq-uring's `http`; moq-mux's `mp4_atom`), hang's and moq-tokio's `pub use moq_net;`, +moq-tokio's `moq_congestion as congestion`, `web_transport_proto as proto` in moq-tokio and +moq-uring, and the rest, such as `axum`, `url`, `object_store`, moq-video's platform crates, +`jni`, and `tikv_jemallocator`. moq-quic-unused-surface names the moq-quic items it needs +instead of adding `pub use moq_quic;`. Each whole-crate re-export makes that crate's whole +API part of moq's, so any breaking release of it breaks moq's semver; moq re-exports by +name only the items its signatures need, or wraps them. Rejected: only the ten; only +third-party crates. + +At `ffcb7cd9`, `container::Producer::modify` returns moq-mux's +`container::producer::Guard`, in a private module (16 refusals), and moq-tokio's +`quic::driver` re-exports moq-quic's `stream::Id` but not the `Side` its `new` and +`initiator` take (2). Both are moq's to fix; the stand-in's narrowing to moq-tokio's +session side also drops `quic::driver`. + +Decided with the maintainer on 2026-11-07, in the quest audit: one pin bump for both, since +each is an XS bump of the same pin line. Rejected: one bump per moq quest. + +Then bump the pin in `ts/fixtures/model/Cargo.toml`, which `ts/justfile`'s `moq_rev` reads, +and report the census before and after. + +## Waiting + +- moq-dev/moq: [quest/m2/api-shape/reexports.md](https://github.com/moq-dev/moq/blob/main/quest/m2/api-shape/reexports.md), + planned in [moq-dev/moq#8233](https://github.com/moq-dev/moq/pull/8233), drops every + whole-crate re-export, moq's own included, and re-exports `Guard` from `container`. Done + once that quest is gone from moq's `main` and the census at its head lists no whole-crate + re-export. +- moq-dev/moq: [quest/m2/moq-quic-unused-surface.md](https://github.com/moq-dev/moq/blob/main/quest/m2/moq-quic-unused-surface.md) + removes `stream::Id::{new, initiator}`. Done once that quest is gone from moq's `main`. diff --git a/quest/m1/ffi-export/refusals/moq-reexports.md b/quest/m1/ffi-export/refusals/moq-reexports.md deleted file mode 100644 index 34cf952e6..000000000 --- a/quest/m1/ffi-export/refusals/moq-reexports.md +++ /dev/null @@ -1,32 +0,0 @@ -# [XS] moq drops its whole-crate re-exports - -## Goal - -The moq pin moves to a revision whose crates re-export no whole crate, moq's own included, -and `just ffi census` there lists no whole-crate re-export refusal. - -## Plan - -Decided with the maintainer on 2026-11-07, from -[moq-dev/rust2#655](https://github.com/moq-dev/rust2/pull/655) and -[moq-dev/moq#8233](https://github.com/moq-dev/moq/pull/8233): ✅ moq re-exports no whole -crate, its own included: the ten first listed (moq-net's `bytes` and `kio`; `rustls` in -moq-quic, moq-rtmp, and moq-tokio; moq-tokio's `notify`, `usage_rs`, and `http`; -moq-uring's `http`; moq-mux's `mp4_atom`), hang's and moq-tokio's `pub use moq_net;`, -moq-tokio's `moq_congestion as congestion`, `web_transport_proto as proto` in moq-tokio and -moq-uring, and the rest, such as `axum`, `url`, `object_store`, moq-video's platform crates, -`jni`, and `tikv_jemallocator`. moq-quic-unused-surface names the moq-quic items it needs -instead of adding `pub use moq_quic;`. Each whole-crate re-export makes that crate's whole -API part of moq's, so any breaking release of it breaks moq's semver; moq wraps the few -third-party types its signatures need instead. Rejected: only the ten; only third-party -crates. - -Then bump the pin, as `ts/justfile`'s `moq_rev` sets it, and report the census before and -after. - -## Waiting - -- moq-dev/moq: [quest/m2/api-shape/reexports.md](https://github.com/moq-dev/moq/blob/main/quest/m2/api-shape/reexports.md), - planned in [moq-dev/moq#8233](https://github.com/moq-dev/moq/pull/8233), drops every - whole-crate re-export, moq's own included. Done once that quest is gone from moq's - `main` and the census at its head lists no whole-crate re-export. diff --git a/quest/m1/ffi-export/refusals/moq-unreachable.md b/quest/m1/ffi-export/refusals/moq-unreachable.md deleted file mode 100644 index f2618b75b..000000000 --- a/quest/m1/ffi-export/refusals/moq-unreachable.md +++ /dev/null @@ -1,24 +0,0 @@ -# [XS] moq's types are reachable from their crate root - -## Goal - -The moq pin moves to a revision where every type a facade export names is reachable from -the facade's root, and `just ffi census` there lists no "type not reachable from the crate -root" refusal of moq's own: 18 at moq `ffcb7cd9`. - -## Plan - -At `ffcb7cd9`, `container::Producer::modify` returns moq-mux's -`container::producer::Guard`, in a private module (16 refusals), and moq-tokio's -`quic::driver` re-exports moq-quic's `stream::Id` but not the `Side` its `new` and -`initiator` take (2). Both are moq's to fix; then bump the pin, as `ts/justfile`'s -`moq_rev` sets it. - -## Waiting - -- moq-dev/moq: [quest/m2/api-shape/reexports.md](https://github.com/moq-dev/moq/blob/main/quest/m2/api-shape/reexports.md), - planned in [moq-dev/moq#8233](https://github.com/moq-dev/moq/pull/8233), re-exports - `Guard` from `container`, and - [quest/m2/moq-quic-unused-surface.md](https://github.com/moq-dev/moq/blob/main/quest/m2/moq-quic-unused-surface.md) - removes `stream::Id::{new, initiator}`. The stand-in's narrowing to moq-tokio's session - side also drops `quic::driver`. Done once both quests are gone from moq's `main`. diff --git a/quest/m1/ffi-export/refusals/narrow-standin.md b/quest/m1/ffi-export/refusals/narrow-standin.md new file mode 100644 index 000000000..61dbd57cf --- /dev/null +++ b/quest/m1/ffi-export/refusals/narrow-standin.md @@ -0,0 +1,27 @@ +# [XS] The stand-in exports only moq-tokio's session side + +## Goal + +The stand-in facade (`just ffi standin`, in `ffi/justfile`) re-exports only the session +side moq-ffi ships from moq-tokio: `Client`, `Server`, `server::Request`, +`connect::Config`, `listen::Config`, `quic::Config`, `Connection`, `Transport`, `Log`, +`tls`, `panic`, and the errors, in place of `pub use ::moq_tokio::*;`. `ffi/moq/ffi.toml` +loses the excludes that only hid items outside it, and the census at the pin reports the +refusals before and after, per crate. + +## Plan + +Decided with the maintainer on 2026-11-07, from +[moq-dev/rust2#655](https://github.com/moq-dev/rust2/pull/655): ✅ the stand-in re-exports +only that session side. Rejected: all of moq-tokio, whose tokio QUIC driver, CLI helpers, +worker groups, and listeners no binding calls; bindings drive the sans-IO driver of +[session-quic](/quest/m1/ffi-export/session-quic.md) instead. + +Decided with the maintainer on 2026-11-07, in the quest audit: ✅ this lands first, so the +other children count only what stays. Narrowing drops moq-tokio's `rustls`, `notify`, +`congestion`, `cli::usage_rs`, and `quic::web`'s `http` and `proto` re-exports, and +`quic::driver`'s `Side` refusals. Every item named exists at moq `ffcb7cd9` +(`rs/moq-tokio/src/lib.rs`). Update the counts in the +[epic](/quest/m1/ffi-export/refusals/README.md) and its children from the new census. + +Public API: the stand-in's exports, which the census and the bindings' fixtures read. diff --git a/quest/m1/ffi-export/refusals/reexports.md b/quest/m1/ffi-export/refusals/reexports.md index 3fd0635cf..37dfbb498 100644 --- a/quest/m1/ffi-export/refusals/reexports.md +++ b/quest/m1/ffi-export/refusals/reexports.md @@ -20,8 +20,13 @@ skipping them silently, as rustdoc does; exporting their whole APIs. The rule goes by form, with no list and no exception for moq's own crates: hang's and moq-tokio's `pub use moq_net;` refuse too, although the census folds them into the root's moq-net today. A facade's own named re-exports from a crate, such as the stand-in's -`pub use kio::{Park, Waiter, ...};`, are not whole-crate and export as now. -[moq-reexports](/quest/m1/ffi-export/refusals/moq-reexports.md) waits on moq to drop them. +`pub use kio::{Park, Waiter, ...};`, are not whole-crate and export as now. So does a glob +of a crate's root, such as the stand-in's `pub use moq_net::*;`: it re-exports that crate's +items, not the crate as a module (decided with the maintainer on 2026-11-07, in the quest +audit: "we probably need to support re-exports", while moq re-exports only the items it +needs, not all of `rustls`). [moq-pin](/quest/m1/ffi-export/refusals/moq-pin.md) waits on +moq to drop the whole-crate ones. Document the rule in `ffi/README.md` beside the other refusals, and test it on a fixture -that re-exports a crate whole and by rename. +that re-exports a crate whole, by rename, and by a glob of its root, the last exporting its +items. diff --git a/quest/m1/ffi-export/session-quic.md b/quest/m1/ffi-export/session-quic.md index a871f78f9..e5a7cd814 100644 --- a/quest/m1/ffi-export/session-quic.md +++ b/quest/m1/ffi-export/session-quic.md @@ -7,11 +7,6 @@ the library owns QUIC and TLS, and the binding moves UDP datagrams and drives th poll from its own event loop, as driving decided for the origin's `Driver`. Proven from C in `moq/moq.c` against a local server. -This waits on moq's [runtime-free QUIC driver](https://github.com/moq-dev/moq/blob/main/quest/m2/quic-sansio-driver.md), -planned in [moq-dev/moq#7197](https://github.com/moq-dev/moq/pull/7197), which itself -waits on moq-quic owning its waits. It has landed once that quest is gone from moq's -`main` and the driver's connections implement moq-net's `transport::poll::Session`. - ## Plan Decided with the maintainer on 2026-10-27: the full-control path, after sessions on @@ -29,6 +24,14 @@ crosses the ABI, and no buffer is lent mutably. Then bump the moq pin and export the driver from its crate's `pub` API, with no wrapper. At `30f25c04`, the census exports 60% of moq-quic's types and 86% of its methods, and none of its traits. It refuses `Endpoint::handle`, `connection::Connection`, `Transmit`, -and `poll_transmit`'s `&mut Vec`. Report the driver crate's coverage before and after. +and `poll_transmit`'s `&mut Vec`. Those figures predate the pin at `ffcb7cd9`; measure +them again at the start, and report the driver crate's coverage before and after. Public API: the C names moq's bindings get. + +## Waiting + +- moq-dev/moq: the [runtime-free QUIC driver](https://github.com/moq-dev/moq/blob/main/quest/m3/quic-sansio-driver.md), + planned in [moq-dev/moq#7197](https://github.com/moq-dev/moq/pull/7197), lands. moq ranks + it in m3, after its next breaking release. Done once that quest is gone from moq's `main` + and the driver's connections implement moq-net's `transport::poll::Session`. diff --git a/quest/m2/ubrn-release.md b/quest/m2/ubrn-release.md index e919d4f46..78a0af9cf 100644 --- a/quest/m2/ubrn-release.md +++ b/quest/m2/ubrn-release.md @@ -4,15 +4,16 @@ uniffi-bindgen-react-native (ubrn) publishes a release whose `wasm2` flavour reads UniFFI 0.32 metadata, so the [binding benchmark](/bench/ffi/README.md)'s -browser runner pins a release instead of an open PR's head. Check: -[jhugman/uniffi-bindgen-react-native#468](https://github.com/jhugman/uniffi-bindgen-react-native/pull/468) -or a successor has merged, and a tag after `0.31.0-6` carries it. +browser runner pins a release instead of an open PR's head. ## Plan Then bump `[ubrn]` in `bench/ffi/pins.toml` to the release in its own change, and take `@ubjs/core`, `@ubjs/wasm`, and `uniffi-runtime-wasm` from npm and -crates.io instead of building them from source. moq waits on the same release -([moq-dev/moq `quest/m2/rs2ts/ubrn-032.md`](https://github.com/moq-dev/moq/blob/main/quest/m2/rs2ts/ubrn-032.md)). -If ubrn stalls, carrying its TypeScript generator in the fork is the +crates.io instead of building them from source. If ubrn stalls, carrying its TypeScript generator in the fork is the alternative; that is the maintainer's call. + +## Waiting + +- jhugman/uniffi-bindgen-react-native: [#468](https://github.com/jhugman/uniffi-bindgen-react-native/pull/468) + or a successor merges, and a tag after `0.31.0-6` carries it (neither on 2026-11-07). diff --git a/quest/m2/uniffi-swift-arm.md b/quest/m2/uniffi-swift-arm.md index 9a05d529c..29acd1fe1 100644 --- a/quest/m2/uniffi-swift-arm.md +++ b/quest/m2/uniffi-swift-arm.md @@ -3,11 +3,7 @@ ## Goal The nightly `uniffi-fixtures` job runs on `ubuntu-24.04-arm` again, and -`nix develop .#uniffi` evaluates on aarch64-linux. Check: the latest -`nixpkgs-unstable` carries Swift 6.4.0 -([NixOS/nixpkgs#574002](https://github.com/NixOS/nixpkgs/pull/574002), merged to -`staging`), and `nix build --dry-run .#devShells.aarch64-linux.uniffi` succeeds -after a bump. +`nix develop .#uniffi` evaluates on aarch64-linux. ## Plan @@ -17,6 +13,16 @@ aarch64-linux in the pinned nixpkgs, where its gold link fails, so the job moved x86_64. Then bump the `nixpkgs` input in its own change, move the job back, and drop the workflow comment. The default shell then has Swift on aarch64-linux too, so drop rust2-ffi's skips there as well: `api_swift`'s `ignore`, and the Swift's in -`just ffi moq` and `just ffi bench`. Never allow the broken package. +`just ffi moq` and `just ffi bench`, and the default dev shell's "not on aarch64-linux" +comment in `flake.nix` that [#653](https://github.com/moq-dev/rust2/pull/653) added. Keep +that shell's `swiftc.meta.broken` guard, which covers any later broken Swift. Never allow +the broken package. Public API: none. + +## Waiting + +- NixOS/nixpkgs: the latest `nixpkgs-unstable` carries Swift 6.4.0 + ([NixOS/nixpkgs#574002](https://github.com/NixOS/nixpkgs/pull/574002), merged to + `staging` on 2026-10-22, not in `nixpkgs-unstable` on 2026-11-07). Check that + `nix build --dry-run .#devShells.aarch64-linux.uniffi` succeeds after a bump. diff --git a/quest/m3/cranelift-unwind.md b/quest/m3/cranelift-unwind.md index 0eefeed9c..6b6e71f38 100644 --- a/quest/m3/cranelift-unwind.md +++ b/quest/m3/cranelift-unwind.md @@ -2,7 +2,7 @@ ## Goal -Waits on upstream. 58 of moq's 6379 tests fail under the Cranelift backend +58 of moq's 6379 tests fail under the Cranelift backend that ships with nightly, so moq's CI cannot build its tests with it: - It builds every panic as an abort, so the 27 tests that catch one fail. @@ -18,7 +18,12 @@ operands so a Cranelift build of moq picks LLVM for rind by profile, as the benchmark does. -Check the issues, or the [build benchmark](/bench/build/README.md)'s moq -report after a nightly pin bump: once `test-run` cranelift fails no tests, -decide again from the test scenarios whether moq's CI builds its tests with +Once `test-run` cranelift fails no tests, decide again from the test scenarios whether moq's CI builds its tests with Cranelift, and delete this quest. + +## Waiting + +- rust-lang/rustc_codegen_cranelift: [#1567](https://github.com/rust-lang/rustc_codegen_cranelift/issues/1567) + and [#171](https://github.com/rust-lang/rustc_codegen_cranelift/issues/171) are fixed in + the Cranelift backend nightly ships. Check the issues, or the + [build benchmark](/bench/build/README.md)'s moq report after a nightly pin bump. diff --git a/quest/m3/relink.md b/quest/m3/relink.md index 3d9ace055..ff5ba9409 100644 --- a/quest/m3/relink.md +++ b/quest/m3/relink.md @@ -4,10 +4,7 @@ Upstream's relinking without rebuilding reaches the pinned nightly, and the [build-speed benchmark](/bench/build/README.md) measures it as a nightly -variant on moq's edits. Check with `rustc -Z help` (a public API hash, as in -[rust-lang/rust#156940](https://github.com/rust-lang/rust/pull/156940)) and -`cargo -Z help` (an early cutoff, as in -[rust-lang/cargo#17439](https://github.com/rust-lang/cargo/issues/17439)). +variant on moq's edits. ## Plan @@ -18,10 +15,10 @@ moq-net changed, 0.6 s of it the final link that would remain. Cranelift and the parallel frontend move the edit within noise; the backend is 6% of its rustc time. -Status on 2026-10-24: neither flag is on nightly 2026-10-21. rust#156940 is a -draft that its author is splitting into smaller PRs, tracked in -[rust-lang/rust#158844](https://github.com/rust-lang/rust/issues/158844). -Cargo's early cutoff is a design discussion. +Status on 2026-11-07: neither flag is on the pinned nightly 2026-10-22. rust#156940 is +a draft that its author is splitting into smaller PRs, tracked in +[rust-lang/rust#158844](https://github.com/rust-lang/rust/issues/158844), whose steps +are all open. Cargo's early cutoff is a design discussion. The early cutoff spike (2026-10-24) replayed moq-net's real edits through rust#156940 and decided against carrying it: its public hash stayed unchanged @@ -57,3 +54,10 @@ root, whose module span is the whole file, so it always changes the hash. `edit-async` is the case the replay says it misses. Public API: a benchmark variant. + +## Waiting + +- rust-lang/rust: a public API hash, as in [rust-lang/rust#156940](https://github.com/rust-lang/rust/pull/156940), + reaches nightly. Check `rustc -Z help` on a nightly from `rust-bin.nightly` in `flake.nix`. +- rust-lang/cargo: an early cutoff, as in [rust-lang/cargo#17439](https://github.com/rust-lang/cargo/issues/17439), + reaches nightly. Check `cargo -Z help`. diff --git a/quest/m3/ts-array-ref-conversions.md b/quest/m3/ts-array-ref-conversions.md index 75fdee6b0..ee764bbe3 100644 --- a/quest/m3/ts-array-ref-conversions.md +++ b/quest/m3/ts-array-ref-conversions.md @@ -18,3 +18,7 @@ and lends parts of arrays where they are: its tests use `first_chunk_mut` in pla Decided with the maintainer on 2026-11-07: ✅ plan it, in m3 (from #662's follow-ups). Public API: the translator's output. + +## Related + +- [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) - its exact `ptr::eq` for `as_chunks` copies changes how an array taken from a flat slice is represented, on the same `rs.ArraySlice` diff --git a/quest/m3/ts-fma-allowance.md b/quest/m3/ts-fma-allowance.md index 4fec469b8..143fbdab2 100644 --- a/quest/m3/ts-fma-allowance.md +++ b/quest/m3/ts-fma-allowance.md @@ -2,15 +2,19 @@ ## Goal -Waits on upstream. wasm32's `fma`, compiler-builtins' generic libm, rounds some +wasm32's `fma`, compiler-builtins' generic libm, rounds some near-ties a last place off ([rust-lang/compiler-builtins#1353](https://github.com/rust-lang/compiler-builtins/issues/1353)), so the `lang/floats` fixture's differential accepts a `floats::mul_adds` result where the translation is the exactly rounded one and native is a last place from it (`ts/fixtures/lang/floats/fma.ts`). -Once [rust-lang/compiler-builtins#1358](https://github.com/rust-lang/compiler-builtins/pull/1358) -ships in the compiler-builtins that the pinned nightly in `ts/rust-toolchain.toml` -builds, delete `fma.ts` and every use of it: `floats/caller.ts`'s import and +Then delete `fma.ts` and every use of it: `floats/caller.ts`'s import and `within`'s call, and its test `ts/tests/fma.ts` with `fma_allowance` in `ts/tests/runtime.rs`, so any difference fails again, and delete this quest. + +## Waiting + +- rust-lang/compiler-builtins: [#1358](https://github.com/rust-lang/compiler-builtins/pull/1358) + ships in the compiler-builtins that the pinned nightly in `ts/rust-toolchain.toml` + builds. diff --git a/quest/m3/ts-tsrs.md b/quest/m3/ts-tsrs.md index e52959d62..014d4c6a3 100644 --- a/quest/m3/ts-tsrs.md +++ b/quest/m3/ts-tsrs.md @@ -2,11 +2,10 @@ ## Goal -Condition: [tsrs](https://blog.cloudflare.com/typescript-rust-port/), the Rust port of -TypeScript, publishes a stable release usable as a Rust library. Then rust2-ts +Once [tsrs](https://blog.cloudflare.com/typescript-rust-port/), the Rust port of +TypeScript, is a stable Rust library, rust2-ts type-checks its output and bindings in process through it, with no `tsc` on `PATH`, failing at the Rust span as today, and measures the translation's time against running `tsc`. -Check: tsrs's releases, and whether it ships a crate (it is moving into Oxc). ## Plan @@ -18,3 +17,10 @@ TypeScript 7 is the vendor's own. - Its checks must agree with `tsc`'s on every fixture, or the switch waits. Public API: none, unless the translator drops its `tsc` requirement. + +## Waiting + +- oxc-project/tsrs: [tsrs](https://github.com/oxc-project/tsrs) publishes a stable release + as a crate. On 2026-11-07 it ships 0.x releases (v0.23.0) on npm only, its Cargo + workspace sets `publish = false`, and its stable release targets TypeScript 7.1 with full + conformance. Check its releases and crates.io. diff --git a/quest/m3/ts-wasm-bindgen/README.md b/quest/m3/ts-wasm-bindgen/README.md index e89b8b368..3745cfcf5 100644 --- a/quest/m3/ts-wasm-bindgen/README.md +++ b/quest/m3/ts-wasm-bindgen/README.md @@ -50,7 +50,7 @@ Decided with the maintainer on 2026-11-06: [spike](https://github.com/moq-dev/rust2/pull/638) has the measurements: `macro_rules!`-made imports, which source reading misses, are on the encoder's path. - ✅ web-codecs' encoders and decoders also hand their frames and errors through tokio's - `sync::mpsc` and `sync::watch`, which translation refuses today (about 50 refusals in + `sync::mpsc` and `sync::watch`, which translation refused then (about 50 refusals in the spike's census), so [tokio's sync channels are mapped into the runtime](/ts/src/lower/tokio.rs) (rejected: translating tokio's source, whose intrusive lists and raw pointers into diff --git a/quest/m4/README.md b/quest/m4/README.md index 570b8fb9c..f899a8193 100644 --- a/quest/m4/README.md +++ b/quest/m4/README.md @@ -30,4 +30,4 @@ build's retired instructions by 16%. - [Bench TPDE on moq](/quest/m4/tpde.md) - what TPDE cuts from moq's clean build and non-incremental edit; waits on `-Zllvm-tpde` reaching a nightly - [Waiting: a rustc and cargo fork](/quest/m4/fork.md) - the evidence ledger for carrying a fork; condition: enough measured reasons to pay for one -- [moq in pure Go](/quest/m4/go/README.md) - rust2-go translating moq-net, with no cgo, fast enough to replace the cgo bindings for Go users +- [moq in pure Go](/quest/m4/go/README.md) - rust2-go translating moq-net, with no cgo, fast enough to replace the cgo bindings for Go users; parked until a consumer needs moq in pure Go diff --git a/quest/m4/fork.md b/quest/m4/fork.md index 8ed2386e1..5b5822bc5 100644 --- a/quest/m4/fork.md +++ b/quest/m4/fork.md @@ -4,8 +4,7 @@ Whether rust2 should carry its own rustc and cargo fork, installed as a rustup toolchain so moq needs no code changes. This quest is the evidence ledger. -Condition: the reasons to fork add up to enough to pay for maintaining one, -which is the maintainer's call. Later reports append their data points here. +Later reports append their data points here. ## Plan @@ -48,3 +47,9 @@ Each names what the fork changes, the measured gain on moq, and its source. ([moq-dev/rust2#62](https://github.com/moq-dev/rust2/pull/62)). Public API: none until the fork has a consumer. + +## Waiting + +- Maintainer: decide that the reasons to fork below add up to enough to pay for + maintaining one. An agent can only gather the evidence; the cost of carrying a fork is + the maintainer's to weigh. diff --git a/quest/m4/go/enums.md b/quest/m4/go/enums.md index 85d5c0e36..c65595de6 100644 --- a/quest/m4/go/enums.md +++ b/quest/m4/go/enums.md @@ -4,9 +4,10 @@ Go callers in another package build, match, and pass a `pub enum` that [rust2-go](/go/README.md) exports: an exported tag, a constant per variant, and a -constructor per variant, with the enum passed whole across every root. An external Go test -package imports the translated `go/fixtures/items` package and constructs and calls its -exports, so what another package can reach is pinned, not only spelled. +constructor per variant, with the enum passed whole across every root. The external Go test +package `go/tests/exports`, which [#645](https://github.com/moq-dev/rust2/pull/645) added, +constructs and calls the enum's exports too, so what another package can reach is pinned, +not only spelled. ## Plan @@ -29,12 +30,12 @@ variant with fields, which code outside the package can neither build nor match needs no conversion at a root and keeps enums as values; record any difference a Go user moving off the cgo bindings would meet in `go/README.md`. -The external test package is the review's suggestion on #614 -([its OpenAI review](https://github.com/moq-dev/rust2/pull/614#pullrequestreview-5809902393)): -it imports the translated items package, constructs `Count`, `Meter`, the aliases, and a -`pub enum`, and calls `ShapesArea` and the other roots, so `go vet` and `go test` fail -when an export is unreachable from another package. It runs in `go/tests/fixtures.rs` -with the other fixture tests. +The external test package was the review's suggestion on #614 +([its OpenAI review](https://github.com/moq-dev/rust2/pull/614#pullrequestreview-5809902393)), +and #645 landed it as `go/tests/exports/main.go`, run by `exports()` in +`go/tests/fixtures.rs`. Extend it to construct a `pub enum` and call `ShapesArea` and the +other roots that take or return one, so `go vet` and `go test` fail when an export is +unreachable from another package. Public API: what a translation exports for an enum (breaking for Go code reading the lowercase `tag`, which only the package itself could). From f99bdaea8371a458f08258839b5701e3c5107c58 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 18:54:26 -0800 Subject: [PATCH 2/4] quest: map smallvec, refuse slice_ref, and rerank the ts bugs Plan smallvec's runtime mapping under the unsafe policy's rule 2, refuse Bytes::slice_ref of other storage at translation, move the two tsc-failing safe-Rust bugs to the top of m3's ts quests, and give ts-model its wait on moq's subset quest. Co-Authored-By: Claude Opus 5.5 --- quest/m2/ts-model/README.md | 35 +++++++++++++++++++++++++---------- quest/m2/ts-model/smallvec.md | 28 ++++++++++++++++++++++++++++ quest/m3/README.md | 13 +++---------- quest/m3/ts-runtime-throws.md | 20 +++++++++++++++++--- ts/README.md | 5 +++++ 5 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 quest/m2/ts-model/smallvec.md diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index 596e4c225..b1227c4dd 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -10,7 +10,7 @@ calls (kio, with its clock, and moq-pattern), as it translates the codec today, Decided with the maintainer on 2026-11-01, after moq removed its WASM build ([moq-dev/moq#7475](https://github.com/moq-dev/moq/pull/7475)): the translated model is -moq's browser route, which moq's [JS-only `@moq/net`](https://github.com/moq-dev/moq/blob/main/quest/m2/rs2ts-js.md) +moq's browser route, which moq's [JS-only `@moq/net`](https://github.com/moq-dev/moq/blob/main/quest/m5/rs2ts-js.md) adopts once it matches the handwritten package in a full browser workload. Once the model translates, moq measures it on the [TS benchmark](/ts/README.md#bench), consuming the driver as the `ts` package of this flake. That adds moq's `js/net/bench/frames.ts`, a group stream @@ -31,12 +31,15 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi - Each group of refusals is its own quest. moq must work, but nothing in the translator is specific to moq: dependencies follow general rules, and moq's own - changes (to its `unsafe` and its address keys, dropping or binding `hashlink`, `slab`, and - `smallvec`) are one research-tier quest in moq's tree. -- A dependency containing `unsafe` is bound by the consumer unless it uses only the - `unsafe` [m3's unsafe policy](/quest/m3/README.md) translates: decided - with the maintainer on 2026-10-27, a stock crate translates with no annotations or config. - Only true platform boundaries (clocks and DEFLATE) stay bound. + changes (to its `unsafe` and its address keys, dropping or binding `hashlink` and + `slab`) are one research-tier quest in moq's tree, + [rs2ts-model-subset](https://github.com/moq-dev/moq/blob/main/quest/m5/rs2ts-model-subset.md). +- A dependency containing `unsafe` translates when it uses only the `unsafe` + [m3's unsafe policy](/quest/m3/README.md) translates; a common crate whose `unsafe` it + does not is mapped into the runtime at its safe API, as hashbrown is. Decided with the + maintainer on 2026-10-27: a stock crate translates with no annotations or config. The + model's binds (DEFLATE, its clock, and its task sets) are gaps + [ts-configless](/quest/m3/ts-configless.md) closes. - Each child measures this target by the census of `ts/moq/model.toml`, rooted at those types with the boundaries bound, which nightly reports beside the whole crate. - The end-to-end work stays here: the model's roots, its differential harness, and the @@ -47,9 +50,11 @@ the [model fixture](/ts/README.md#tests)'s harness runs it against native moq-ne pin; each case it adds as a type translates. Every other root reaches the rest of the model, all of it moq's, for its research quest: kio's `log.rs` reads bytes as its segments and retires them through crossbeam-epoch, whose tagged pointers and inline closures (36 -refusals) translation refuses for good, and smallvec's union over a type parameter and -raw parts (4) stay refused under [m3's unsafe policy](/quest/m3/README.md), so moq -rewrites or drops them, as it does the `unsafe` slice of `model/frame.rs`'s `Filled`. +refusals) translation refuses for good, so moq rewrites or drops them, as it does the +`unsafe` slice of `model/frame.rs`'s `Filled`. smallvec's union over a type parameter and +raw parts (4) go once [smallvec maps into the runtime](/quest/m2/ts-model/smallvec.md) +(decided with the maintainer on 2026-11-07, in the quest audit, under rule 2 of +[m3's unsafe policy](/quest/m3/README.md); rejected: moq dropping smallvec). Decided with the maintainer on 2026-11-04, from a census at the bumped pin (moq-dev/rust2#390): the refusals no quest owned became child quests, since finished. @@ -74,3 +79,13 @@ or pointers whose comparison disagreed with Rust after an address was reused: Rejected: twins until rust2-ts translates them, which [m3's unsafe policy](/quest/m3/README.md) mostly refuses. Public API: the translator's output for moq-net's model. + +## Waiting + +- moq-dev/moq: [rs2ts-model-subset](https://github.com/moq-dev/moq/blob/main/quest/m5/rs2ts-model-subset.md) + lands: moq rewrites kio's `log.rs` and its own `unsafe` in safe Rust, or drops them from the + model's path. moq defers it to its m5. Check by the model census at the pin. + +## Required + +- [smallvec maps into the runtime](/quest/m2/ts-model/smallvec.md) - `SmallVec` is the runtime's `rs.Vec`, so smallvec's 4 refusals go without moq dropping it diff --git a/quest/m2/ts-model/smallvec.md b/quest/m2/ts-model/smallvec.md new file mode 100644 index 000000000..61ca3559a --- /dev/null +++ b/quest/m2/ts-model/smallvec.md @@ -0,0 +1,28 @@ +# [S] smallvec maps into the runtime + +## Goal + +[rust2-ts](/ts/README.md) maps smallvec's `SmallVec` to the runtime's `rs.Vec`, as it +maps hashbrown's tables to std's, so the model's uses of smallvec translate with no +`unsafe` from its source. The model census at the moq pin loses smallvec's 4 refusals +(its `SmallVecData` union over a type parameter and its raw parts), and a fixture proves +every method the model calls, and smallvec's other common safe API, by differential tests +against native Rust. + +## Plan + +Decided with the maintainer on 2026-11-07, in the quest audit: ✅ map smallvec into the +runtime under rule 2 of m3's [unsafe policy](/quest/m3/README.md), which wraps a common +crate's `unsafe` at its safe API. Rejected: moq dropping smallvec from the model's path, +which the [model epic](/quest/m2/ts-model/README.md) had planned. + +- Add smallvec to the stock crates in `ts/src/lower/stock.rs`, with `SmallVec` as std's + `Vec`. Its inline capacity is a layout detail no JS caller sees: `inline_size` and + `spilled` answer as Rust does from the length and the array type, and `capacity` as + Rust's, so a differential that prints them still matches. +- Methods with no `Vec` twin (`grow`, `try_grow`, `into_inner`, `from_buf_and_len`) map + where they are safe; `from_raw_parts` and the other `unsafe` methods are refused with a + span. +- List the mapping in `ts/README.md`'s table beside hashbrown's. + +Public API: the translator's output for a crate using smallvec. diff --git a/quest/m3/README.md b/quest/m3/README.md index bcfe067a6..a8bdd7e11 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -11,20 +11,13 @@ std the translators cover before a consumer calls them. Decided with the maintainer on 2026-10-27: rust2 aims to support all of std, so a generic std gap is planned here even when moq never calls it. -Decided with the maintainer on 2026-11-07: ✅ little time goes to unsafe Rust. A -translator may refuse unsafe code with its span, requiring safe Rust, and a common -library's unsafe internals may be replaced by a runtime mapping over its public API, as -`hashbrown`'s and tokio's channels are (rejected: planning each unsafe std gap that no -consumer reaches, such as `MaybeUninit::zeroed`, `Rc::new_uninit`, and -`ptr::write_bytes`). - Decided with the maintainer on 2026-11-07, the unsafe policy, to stop one-off fixes for ever rarer `unsafe`: 1. ✅ Safe Rust is the contract, and where translation effort goes. 2. ✅ `unsafe` inside std and common crates is wrapped at their safe API, as std, `hashbrown`, `bytes`, and [tokio's channels](/ts/src/lower/tokio.rs) - are, rather than translated. + are, and [smallvec](/quest/m2/ts-model/smallvec.md) will be, rather than translated. 3. ✅ Other `unsafe` translates only where it fits the translator's existing pointer forms (a place, an array element, a byte in a buffer). A construct needing a new pointer form or pointer metadata is refused with its span, unless a consumer needs it and no library @@ -42,12 +35,12 @@ memory model for values whose address is reinterpreted, without a consumer. - [Retrial Cranelift on moq's tests](/quest/m3/cranelift-unwind.md) - waits on upstream: moq's tests pass under Cranelift, so its CI can weigh it again - [Relink without rebuilding](/quest/m3/relink.md) - measure upstream's skip of dependency-only rebuilds on moq's edits once it reaches nightly +- [`if let` of a temporary whose rest drops](/quest/m3/ts-if-let-drop.md) - `if let Some(E::A(x)) = temp()` with a destructor in the rest of `temp` emits TypeScript that type-checks +- [A Pin of a crate's own Deref type](/quest/m3/ts-pin-own-deref.md) - `Pin

` over a crate's own `Deref` derefs through its impl and type-checks - [Waiting: drop the lang fixture's mul_add allowance](/quest/m3/ts-fma-allowance.md) - the differential fails on any `mul_add` difference again once wasm32's `fma` rounds exactly - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library - [Throw only where Rust panics](/quest/m3/ts-runtime-throws.md) - no runtime `rust2-ts:` error where native Rust goes on: exact, or refused at translation - [rust2-ts with no ts.toml](/quest/m3/ts-configless.md) - a crate's whole `pub` API translates with no config and no bound items - [Browser APIs through wasm-bindgen](/quest/m3/ts-wasm-bindgen/README.md) - web-sys and js-sys imports become direct JS calls, so idiomatic crates such as web-rs's web-codecs translate as stock crates -- [`if let` of a temporary whose rest drops](/quest/m3/ts-if-let-drop.md) - `if let Some(E::A(x)) = temp()` with a destructor in the rest of `temp` emits TypeScript that type-checks - [`u64` wrapping arithmetic in V8](/quest/m3/ts-u64-wrapping.md) - `rs.u64WrappingAdd` and its kin cost near a plain `+` while the value is a safe integer - [Array references from slices and cells](/quest/m3/ts-array-ref-conversions.md) - `try_into` of a slice into an array reference, `Cell::as_array_of_cells`, and `identity` of a `&mut [T; N]` lend the array where it is -- [A Pin of a crate's own Deref type](/quest/m3/ts-pin-own-deref.md) - `Pin

` over a crate's own `Deref` derefs through its impl and type-checks diff --git a/quest/m3/ts-runtime-throws.md b/quest/m3/ts-runtime-throws.md index 33adc171e..05fb9d0f8 100644 --- a/quest/m3/ts-runtime-throws.md +++ b/quest/m3/ts-runtime-throws.md @@ -20,15 +20,20 @@ only because translation can't see where a pointer came from: - ✅ Refuse at translation, with a span, where the body doesn't show the pointer fits: `rs.arcErase` (done in #661) and `alloc`'s bytes. - ✅ Keep as loud run-time checks: `ptrUnerase`, `offset_from` across arrays, - `AtIndex.reach`, raw slices of a field's byte, and `Bytes::slice_ref` of copied bytes, - safe code with no exact answer while a `&str` stays a JS string. AGENTS.md allows the - unsafe ones under m3's [unsafe policy](/quest/m3/README.md). + `AtIndex.reach`, and raw slices of a field's byte, which AGENTS.md allows under m3's + [unsafe policy](/quest/m3/README.md). Two pointers made from one reference, one through a branch or loop, may each be in an array of one of its own; keep the loud check rather than trace joins (decided 2026-11-07 in [#621](https://github.com/moq-dev/rust2/pull/621)). - Rejected: refusing all of them at translation, which refuses pointer arithmetic on any pointer parameter; keeping all as run-time checks. +Decided with the maintainer on 2026-11-07, in the quest audit: ✅ `Bytes::slice_ref` of +bytes from other storage is refused at translation, since it is safe code and AGENTS.md +allows a run-time check only for an `unsafe` pointer. Rejected: keeping its run-time +check, as #661 had, and widening AGENTS.md's exception to safe code with no exact answer +while a `&str` stays a JS string. + What remains: - `alloc`'s bytes: a byte of memory from `alloc` read or written, or the memory cast to a @@ -45,6 +50,11 @@ What remains: offset it came from, compared through that, is exact; it builds on the `rs.ArraySlice` [#662](https://github.com/moq-dev/rust2/pull/662) left, where `rs.arraySliceItems` still copies. +- `Bytes::slice_ref` of bytes from other storage throws in `bytesSliceRef` + (`ts/runtime/rs/bytes.ts`), since Rust may share what JS copied, such as a + `&'static str`'s bytes that `as_bytes` encodes anew. Refuse the call whose `subset` the + body doesn't show borrowed from that same `Bytes`, through `Deref`, `as_ref`, or a slice + of one; the throw is then a translator guard, and `ts/README.md` says so. - `bytes`' panics in a hook program: `BYTES` in [`caller.rs`](/ts/src/lower/caller.rs) finds each by searching the source of the program's `bytes` for text, so a `--remap-path-prefix` of a directory inside `bytes` fails translation of a hook program. @@ -55,3 +65,7 @@ What remains: follow-ups). Public API: the translator's output, and which crates translate. + +## Related + +- [Array references from slices and cells](/quest/m3/ts-array-ref-conversions.md) - also changes how an array taken from a flat slice is represented, on the same `rs.ArraySlice` diff --git a/ts/README.md b/ts/README.md index 0c40dff3f..ca986b487 100644 --- a/ts/README.md +++ b/ts/README.md @@ -1229,6 +1229,11 @@ functions without a mapping in `src/lower/library.rs`, but for `bool`'s, `Option `Result`'s, and `Poll`'s methods that take no `&mut`, and `cmp`'s `max` and `min` forms, which lower from core's MIR. +These emit TypeScript that `tsc` rejects, so translation fails at its type check rather +than with a span: `if let` of a temporary whose rest has a destructor +([ts-if-let-drop](/quest/m3/ts-if-let-drop.md)), and a `Pin` of a crate's own `Deref` +type ([ts-pin-own-deref](/quest/m3/ts-pin-own-deref.md)). + ## Tests ```bash From d0dfc67276dc85bed4522f0ab2fba56b0779d76c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 18:57:59 -0800 Subject: [PATCH 3/4] quest: smallvec keeps its own spill state and drop order Co-Authored-By: Claude Opus 5.5 --- quest/m2/ts-model/smallvec.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/quest/m2/ts-model/smallvec.md b/quest/m2/ts-model/smallvec.md index 61ca3559a..5c68ef37a 100644 --- a/quest/m2/ts-model/smallvec.md +++ b/quest/m2/ts-model/smallvec.md @@ -16,10 +16,19 @@ runtime under rule 2 of m3's [unsafe policy](/quest/m3/README.md), which wraps a crate's `unsafe` at its safe API. Rejected: moq dropping smallvec from the model's path, which the [model epic](/quest/m2/ts-model/README.md) had planned. -- Add smallvec to the stock crates in `ts/src/lower/stock.rs`, with `SmallVec` as std's - `Vec`. Its inline capacity is a layout detail no JS caller sees: `inline_size` and - `spilled` answer as Rust does from the length and the array type, and `capacity` as - Rust's, so a differential that prints them still matches. +- Add smallvec to the stock crates in `ts/src/lower/stock.rs`, storing a `SmallVec` as + an `rs.Vec` plus its own state where smallvec's observable behaviour differs from + `Vec`'s, so every method answers as smallvec does at the pinned version: + - `spilled` and `capacity` follow smallvec's allocation, not the length: + `with_capacity(8)` of a `[u8; 4]` is spilled at capacity 8 while `new()` is not, a + `truncate` keeps a spilled allocation, and only `shrink_to_fit` or `into_inner` moves + back inline. `inline_size` is the array's length. + - Drop order follows smallvec's code, not `Vec`'s mapping: its `truncate`, and `clear` + through it, drop the removed elements from the end, where the runtime's `vecTruncate` + drops them forwards. + - The fixture's differential covers reservation, growth past the inline size, + truncation, shrinking, and a destructor log for `truncate`, `clear`, `drain`, and the + `SmallVec`'s own drop. - Methods with no `Vec` twin (`grow`, `try_grow`, `into_inner`, `from_buf_and_len`) map where they are safe; `from_raw_parts` and the other `unsafe` methods are refused with a span. From 8fb6563ba440977037b24899906bdd711c4ad833 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 18:58:43 -0800 Subject: [PATCH 4/4] quest: smallvec ports its transitions from its source Co-Authored-By: Claude Opus 5.5 --- quest/m2/ts-model/smallvec.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/quest/m2/ts-model/smallvec.md b/quest/m2/ts-model/smallvec.md index 5c68ef37a..6e54b1444 100644 --- a/quest/m2/ts-model/smallvec.md +++ b/quest/m2/ts-model/smallvec.md @@ -19,16 +19,19 @@ which the [model epic](/quest/m2/ts-model/README.md) had planned. - Add smallvec to the stock crates in `ts/src/lower/stock.rs`, storing a `SmallVec` as an `rs.Vec` plus its own state where smallvec's observable behaviour differs from `Vec`'s, so every method answers as smallvec does at the pinned version: - - `spilled` and `capacity` follow smallvec's allocation, not the length: - `with_capacity(8)` of a `[u8; 4]` is spilled at capacity 8 while `new()` is not, a - `truncate` keeps a spilled allocation, and only `shrink_to_fit` or `into_inner` moves - back inline. `inline_size` is the array's length. + - `spilled`, `capacity`, and `inline_size` follow smallvec's own code for each + method, not the length: `with_capacity(8)` of a `[u8; 4]` is spilled at capacity 8 + while `new()` is not, a `truncate` keeps a spilled allocation, `grow` and `try_grow` + to the inline size move it back, `into_inner` of a spilled one is its `Err`, and + `inline_size` of zero-sized elements is `usize::MAX`. Port each transition from + smallvec's source at the pin rather than from this list. - Drop order follows smallvec's code, not `Vec`'s mapping: its `truncate`, and `clear` through it, drop the removed elements from the end, where the runtime's `vecTruncate` drops them forwards. - The fixture's differential covers reservation, growth past the inline size, - truncation, shrinking, and a destructor log for `truncate`, `clear`, `drain`, and the - `SmallVec`'s own drop. + truncation, `shrink_to_fit`, `grow` and `try_grow` back inline, `into_inner` of an + inline and a spilled one, zero-sized elements, and a destructor log for `truncate`, + `clear`, `drain`, and the `SmallVec`'s own drop. - Methods with no `Vec` twin (`grow`, `try_grow`, `into_inner`, `from_buf_and_len`) map where they are safe; `from_raw_parts` and the other `unsafe` methods are refused with a span.