Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
rust2 holds Rust tools that take Rust somewhere else: to TypeScript through an own rustc driver, to any language through a fork of UniFFI, and to faster builds for a pure-Rust subset.
rust2 holds Rust tools that take Rust somewhere else: to any language through an exporter of a crate's `pub` API, which replaces a fork of UniFFI, to TypeScript through an own rustc driver, and to faster builds by measuring upstream's levers.
Each project lives in its own top-level directory; the [quest tree](quest/README.md) says what each is doing now.

# Required

- An unsupported construct or malformed input is a compile error with a source span, never a warning, a silent fallback, or a runtime failure. Generated code panics only where the Rust source does.
The one exception is an `unsafe` pointer from elsewhere whose origin translation can't see, such as a parameter's: a loud run-time check may stand in for the refusal, under m3's [unsafe policy](quest/m3/README.md), since refusing every such pointer would refuse pointer arithmetic on any parameter.
One exception is an `unsafe` pointer from elsewhere whose origin translation can't see, such as a parameter's: a loud run-time check may stand in for the refusal, under m3's [unsafe policy](quest/m3/README.md), since refusing every such pointer would refuse pointer arithmetic on any parameter.
The other is a binding, which checks at run time only the Rust rules its language cannot express: borrow exclusivity, use after a move or close, and the thread of a value that is not `Send`. A failed check raises the language's own error, or aborts where it has none; a Rust panic aborts.
- Generated output must match the source's semantics exactly: integer widths and overflow, drop order, laziness, and error paths. Prove it with differential tests against native Rust, not by inspection.
- A binding's types are lossless: every value of the Rust type crosses and returns exactly. A native type stands in only where it is exact; otherwise the runtime's own type crosses, with checked conversions to and from the idiomatic one. Speed ranks above ergonomics.
- Everything is configless, with opinionated names and defaults. Translators take stock crates as they are: no annotations, and no config beyond true platform boundaries. Each config key or source change a consumer needs is a gap to close.
- A vanity rename, if ever supported, is opt-in and lives inline in source next to the item, never in a config file.
- moq is the first consumer of every project here.
- moq is the first consumer of every project here; a binding target may be planned before any consumer asks for it.
- Prefer upstream and maintained crates over hand-rolling. Measure before claiming an improvement over them.

# Development
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

Rust to other places.

- **TypeScript**: an own rustc driver that translates a [documented Rust subset](ts/README.md) to readable TypeScript, without WASM.
- **TypeScript**: an own rustc driver that translates a [documented Rust subset](ts/README.md) to TypeScript, without WASM.
- **C, then any language**: an [exporter](ffi/README.md) that turns a crate's `pub` API into a C ABI and header with no attributes, sharing the TypeScript driver's front end.
- **Any language**: [a hard fork](uniffi/README.md) of [UniFFI](https://github.com/mozilla/uniffi-rs) with every generator [moq](https://github.com/moq-dev/moq) uses.
- **Faster builds**: a reproducible [build-speed benchmark](bench/build/README.md), then a pure-Rust subset (no build scripts, no C linking) that builds faster, and [rust2-lint](driver/src/bin/rust2-lint/main.rs), which fails a build whose generics' copies cost its codegen more than a budget (`nix run github:moq-dev/rust2#lint -- --help`).
- **Faster builds**: a reproducible [build-speed benchmark](bench/build/README.md) of upstream's levers on moq, and [rust2-lint](driver/src/bin/rust2-lint/main.rs), which fails a build whose generics' copies cost its codegen more than a budget (`nix run github:moq-dev/rust2#lint -- --help`).

Everything here is early; the [quest tree](quest/README.md) holds the plan.

Expand Down
31 changes: 19 additions & 12 deletions quest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ 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). 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.
m0 holds urgent fixes and what every run pays for. m1 is the work with a consumer
waiting on it now: the exporter moq switches its bindings to. m2 is research with a named
consumer: rust2-wasm, moq's browser route, and the TypeScript translator, an experiment
that has to beat it. m3 holds work that waits on evidence or has no consumer yet:
upstream's build-speed levers, which the [build-speed benchmark](/bench/build/README.md)
measures, the translator's features that wait on that gate, and the binding targets
planned ahead of a consumer. m4 holds long-term research.

Decided with the maintainer on 2026-11-09, in the quest audit, the goal: one Rust code
base callable from any language or framework, with no manual wrappers or annotations.
Speed ranks above API ergonomics, Rust's correctness is kept wherever the target can hold
it, and what is unsupported fails the export, not the run. The
[exporter](/quest/m1/ffi-export/README.md) is the product, WebAssembly its browser
target, the translator an experiment against that, and build speed a side track that
keeps only what costs nothing.

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)):
Expand All @@ -29,8 +36,8 @@ Decided with the maintainer on 2026-10-23, when rust2 was planned from moq

## Required

- [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) - upstream's build-speed levers, measured by the benchmark, and the std gaps and translator features no consumer calls yet
- [m0: immediate priorities](/quest/m0/README.md) - urgent fixes, a green nightly, and the fork's deletion
- [m1: consumers waiting](/quest/m1/README.md) - the exporter moq can pin and switch its bindings to
- [m2: research](/quest/m2/README.md) - rust2-wasm as the browser route, and the TypeScript translator that has to beat it
- [m3: deferred](/quest/m3/README.md) - upstream's build-speed levers, the translator features that wait on the gate, and binding targets planned ahead of a consumer
- [m4: long-term research](/quest/m4/README.md) - ideas no consumer needs yet, such as a rustc and cargo fork
6 changes: 5 additions & 1 deletion quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

## Goal

Urgent fixes to anything a consumer already depends on.
Urgent fixes to anything a consumer already depends on, and what every agent pays for on
each run: the checks, a red nightly, and code no consumer uses.

## Required

- [Checks at moq's cost](/quest/m0/checks/README.md) - `just ci` selects by the diff, builds warm, and runs the differential optimized, with a nightly full `just gate`
- [Delete the UniFFI fork](/quest/m0/delete-uniffi.md) - `uniffi/`, its workspace, flake packages, nightly jobs, and the benchmark harness built on it are gone
- [The nightly passes](/quest/m0/nightly-green.md) - red since 2026-10-29: the wasm-bindgen fixture's NaN, Kotlin on Android API 35, and `just ffi moq`
- [Trim the build-speed benchmark](/quest/m0/bench-trim.md) - it runs on a pin change, not nightly, without the nix job and the `pure` corpora
29 changes: 29 additions & 0 deletions quest/m0/bench-trim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# [S] Trim the build-speed benchmark

## Goal

The [build-speed benchmark](/bench/build/README.md) costs CI only when its result can
change: `bench.yml` runs when a pin or the benchmark itself changes and on manual
dispatch, not nightly; the `Bench (moq nix)` job and `bench/build/nix.*` are deleted, and
so are the `pure` corpora (regex, wasmparser) and their job, with every table they
produced kept in the README.

## Plan

Decided with the maintainer on 2026-11-09, in the quest audit: ✅ trim it, in m0 since it
is CI cost. Build speed is outside rust2's goal as the maintainer restated it, any Rust
crate callable from any language, so the track keeps only what costs nothing. Rejected:
keeping the nightly schedule and fixing the nix job; removing the track.

- The nightly `bench.yml` costs about 3.5 runner-hours on inputs that are all pinned (the
moq revision, the nightly, ra-jit), and a pin change already runs it on its PR.
- `Bench (moq nix)` has failed every scheduled run since 2026-11-02, and the choice it
informed was made on 2026-10-26 (`bench/build/README.md`).
- The `pure` corpora were built for the subset compiler, closed on 2026-11-06
([m4](/quest/m4/README.md#plan)).
- What stays: the four quests that wait on upstream
([Cranelift](/quest/m3/cranelift-unwind.md), [relink](/quest/m3/relink.md),
[TPDE](/quest/m4/tpde.md), [the fork ledger](/quest/m4/fork.md)), the benchmark they
measure with, and rust2-lint, which moq's CI plans to adopt.

Public API: none.
3 changes: 2 additions & 1 deletion quest/m0/checks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ change touches, builds warm, and runs the differential fixtures optimized, while

Decided with the maintainer on 2026-11-01, from an audit of the gate's and agents' checks
(nixos2 and the host's systemd accounting): copy how moq works. `just ci` before every push
stays the rule, and costs less once it selects by the diff; CONTRIBUTING.md and the skills
stays the rule, and costs less now that it selects by the diff
([#421](https://github.com/moq-dev/rust2/pull/421)); CONTRIBUTING.md and the skills
keep their rules. All of it is m0, since every agent pays the cost now.

Each child reports its CPU saved, cold and warm, against `origin/main`.
Expand Down
40 changes: 40 additions & 0 deletions quest/m0/delete-uniffi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# [M] Delete the UniFFI fork

## Goal

`uniffi/` and everything that exists only for it are gone: the root Cargo workspace, which
holds only the fork (`sh/dispatch.sh`), the flake's `uniffi*` packages and `.#uniffi`
shell, the nightly `uniffi-*` jobs, and the [binding benchmark](/bench/ffi/README.md)'s
harness, which builds moq-ffi against the fork (`run.sh`, its `fork`, `control`, and `base`
variants, the ubrn browser runner, `pins.toml`, and `bench-ffi.yml`). The benchmark's
README keeps what was measured: the BoltFFI comparison and the fork's generated sizes.
`just ci` and the nightly report their cost before and after.

## Plan

Decided with the maintainer on 2026-11-09, in the quest audit: ✅ delete it now. moq decided
on 2026-11-02 never to build on it ([m1](/quest/m1/README.md#plan)), so its consumers here
were only the benchmark's baseline and CI: 1,426 of the repository's 3,596 tracked files
(118k lines), about 7 minutes of each nightly gate, and nightly jobs of 46 minutes
(fixtures), 11 (install), 14 (C and C++), and 100 to 125 (the benchmark on Linux and
macOS). `UniFFI fixtures` has failed every scheduled run since 2026-11-02, and the
benchmark's Swift job since then too. Rejected: keeping it until moq switches, with a
retirement quest; keeping it as is.

- The baseline no longer matched what moq ships anyway: moq's generator pins moved on
(go `.2`, dart `.7`, cpp `.7`) from the ones `uniffi/README.md` lists.
- Docs the deletion makes stale change with it: the root `README.md`'s fork bullet,
nix command, and license note, `AGENTS.md`'s first line ("to any language through a fork
of UniFFI"), `quest/README.md`'s Plan, and `ffi/README.md`'s first paragraph.
- Rename [the Swift-on-arm wait](/quest/m2/uniffi-swift-arm.md) with `quest mv` to a name
without `uniffi`, and rewrite its Goal around rust2-ffi's skips, which are all it then
holds; `ffi/tests/fixtures.rs`, `ffi/justfile`, and `ffi/README.md` cite its path.
- The exporter's own numbers come from
[its benchmark quest](/quest/m1/ffi-export/bench.md), which rebuilds the harness on
rust2-ffi.
- moq's `quest/m3/ffi-exporter.md` links quests here that are long deleted
(`quest/m1/uniffi.md`, `ffi-bytes.md`, `kt-ffi-calls.md`, `ffi-poll-protocol.md`) and
carries fork-era requirements; report them in the PR for moq's tree to fix.

Public API: the flake's `uniffi*` packages and `.#uniffi` shell are removed (breaking for
anyone pinning them; no consumer does).
43 changes: 43 additions & 0 deletions quest/m0/nightly-green.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# [M] The nightly passes

## Goal

Every job of the scheduled `nightly.yml` run passes on `main`, each failure fixed at its
cause, and a regression in a suite only the nightly runs cannot go unseen for a week again.

## Plan

Found in the 2026-11-09 quest audit: the nightly has failed every scheduled run since
2026-10-29, with no quest owning it. Decided with the maintainer on 2026-11-09: ✅ m0,
after [the fork's deletion](/quest/m0/delete-uniffi.md), which removes the `uniffi-*`
jobs, so this fixes only what remains. Rejected: m1.

What failed on 2026-11-09 (run
[42655190735](https://github.com/moq-dev/rust2/actions/runs/42655190735), at `f7a0c279`):

- `Gate` and `TypeScript package`: rust2-ts's `bindgen` fixture differs from native in 1
of 5073 cases, a NaN's payload through the variadic import (`max_of` in
`ts/fixtures/bindgen/src/harness.rs`: native `18446472823932715226`, translated
`9221120237041090560`). "Encoder in Chromium" runs after that step, so the WebCodecs
encoder proof [#681](https://github.com/moq-dev/rust2/pull/681) added has never run in
CI. Reproduce first; the fix is in the translation or the import's glue, not in making
the harness blind to NaN payloads, unless JS cannot keep one through a call, which the
PR then shows.
- "Export to Kotlin on Android" at API 35: `the GC frees lent bytes nothing reaches`, for
at least three nights.
- `just ffi moq` likely fails on `main` since
[#668](https://github.com/moq-dev/rust2/pull/668): the exporter now refuses every
whole-crate `pub use` (`ffi/src/collect.rs`), and `ffi/moq/ffi.toml` excludes nine of
the eleven at the pin but not hang's and moq-tokio's `pub use moq_net;`. Not yet run in
CI, since no nightly ran after the merge. [The pin bump](/quest/m1/ffi-export/refusals/moq-pin.md)
removes them if it lands first.

Open: what keeps it green. Recommended: the nightly's failure opens or updates one GitHub
issue the maintainer sees, and `just ffi moq`'s export step joins `just ci` when `ffi/`
or the pin changes, if it fits the gate's budget; measure it and report.

Public API: none.

## Required

- [Delete the UniFFI fork](/quest/m0/delete-uniffi.md) - the nightly's `uniffi-*` jobs, one of them red, are deleted rather than fixed
15 changes: 9 additions & 6 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@

## Goal

The work moq is waiting on: the exporter that replaces the UniFFI fork moq
The work moq is waiting on: the exporter that replaces the UniFFI generator forks moq
builds its bindings with today.

## Plan

Decided with the maintainer on 2026-10-25, from the
[spike](https://github.com/moq-dev/rust2/pull/41): the [exporter](/ffi/README.md), which
turns a crate's `pub` API into bindings with no attributes, replaces the UniFFI fork. The
fork stays in maintenance and keeps building moq's bindings until then; its feature quests
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.
turns a crate's `pub` API into bindings with no attributes, replaces the UniFFI fork,
whose feature quests are folded into the [exporter's](/quest/m1/ffi-export/README.md).

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.

Decided with the maintainer on 2026-11-09, in the quest audit: ✅ the fork is deleted now
([its quest](/quest/m0/delete-uniffi.md)), since moq never builds on it and its only
consumers here were the binding benchmark's baseline and nightly jobs. Rejected: keeping it
until moq switches; keeping it as is.

## Required

- [Exporter](/quest/m1/ffi-export/README.md) - moq's bindings exported from its crates' `pub` API, replacing the fork
- [Exporter](/quest/m1/ffi-export/README.md) - moq can pin an exporter that exports its crates' `pub` API in every language it ships
Loading