RUST.md: record E0 and I0b, with what they measured

E0 is done (NDK r29, cargo-ndk 4.1.2, verified by cross-compiling to both
ABIs) and I0b is done. Corrects this file's guess at why iris would not
build, notes the const-traits family as the gates to re-read whenever the
pin is advanced, and records the cold build weight against the "slow in
debug" worry: 43s and 2.1 GB plain, 1m46s and 1.5 GB with dependencies at
opt-level 2.

Also records an open defect found on the way -- iris sometimes keeps its
pre-configure window size for good -- with what was ruled out, since it
is timing-sensitive enough that any added print hides it, and I2 will
meet it on every rotation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-04 17:47:44 -04:00
1 parent b6b0928087
commit e0ee7d6e94
1 file changed
+119 -23
+119 -23
View File
@@ -418,9 +418,20 @@ Android backend wants API 26 (a libc symbol). The real phone has Vulkan.
Per the standing rule, a rig limit is something to fix before it is
accepted.
- [ ] **E0 — toolchain.** No NDK is installed (`~/Android/Sdk/ndk` is
empty; the Android Rust targets are). Install the NDK under the
user-owned SDK, `cargo-ndk`, and record the versions here.
- [x] **E0 — toolchain (done 2026-09-04).** Installed under the
user-owned SDK: **NDK r29 (`29.0.14206865`)**, 2.4 GB at
`~/Android/Sdk/ndk/29.0.14206865`, the newest stable — r30 is still
at rc.3. **cargo-ndk 4.1.2**. Verified by cross-compiling a scratch
`cdylib` to both ABIs: `file` reports "for Android 26, built by NDK
r29 (14206865)" for `aarch64-linux-android` and
`x86_64-linux-android`. Two things to know at the call site.
**cargo-ndk 4's API-level flag is `-P`, not `-p`**`-p` is now
passed through to cargo as `--package`, so the old
`cargo ndk -t arm64-v8a -p 26` panics with `unknown package: 26`
*and dumps the whole environment to stdout* as a bug report, which is
worth not doing in a log somebody might paste. And the Android
targets were installed for **stable** only; the pinned nightly needs
its own, which `iris/rust-toolchain.toml` now declares.
- [ ] **E1 — android-view's Masonry demo on this emulator.** Pass: it
builds with `cargo-ndk` + Gradle, renders on the GPU, and the phone's
own keyboard types into its editor with autocorrect and suggestions.
@@ -461,26 +472,111 @@ step measured.
`7b54aaf` ("readme", 2026-01-29), byte-identical to the public
GitHub copy, so a later reconciliation has a known base. A crate
that uses it says `iris = { path = "../iris" }`.
- [ ] **I0b — make it build here.** Pin a dated nightly in
`iris/rust-toolchain.toml` (the machine has
`nightly-x86_64-unknown-linux-gnu`, rustc 1.100.0-nightly
2026-08-25); list every `#![feature]` gate with what it is for, so
each can be retired when it stabilises. Get `cargo build`, `clippy`
and `fmt` clean at the defaults and the `tabs` example running in a
window (this VM has no display: a headless compositor is what
`emu` uses for the emulator, and the same trick serves here).
Pass: a fresh clone builds unattended from the pinned toolchain.
**Measured 2026-09-04**: `cargo +nightly check` on that nightly
fails in `iris-core` with 36 errors, all one cause — the
`const_trait_impl` feature changed shape between January and
August: `impl const Trait for T` is now rejected with "expected a
trait, found type" unless the trait itself is declared
`const trait`, at seven sites (`num.rs`, `align.rs`, `axis.rs`,
`pos.rs`, `color.rs`, `math.rs`, `vec2.rs`), and the unresolved
`UiVec2`/`Vec2`/`impl_op` imports cascade from those. That is the
nightly-drift risk in one build; the fix is mechanical (declare
the traits `const trait`), and it is the first item of I0b rather
than pinning back to a January nightly, which would only defer it.
- [x] **I0b — make it build here (done 2026-09-04).** iris now builds,
clippy-clean and rustfmt-clean at the defaults, on a pinned dated
nightly, and the `tabs` example draws on this VM's GPU.
**The pin** is `nightly-2026-09-03` (rustc 1.100.0-nightly,
`2e2b193f8`), declared in `iris/rust-toolchain.toml` along with the
`clippy`/`rustfmt` components and the two Android targets, so a
fresh clone provisions itself. It is dated rather than `nightly`
because the whole failure below was a rolling channel moving under
an unattended build. Installed with `--profile minimal`: 912 MB.
**The 36 errors were one syntax change, and the earlier diagnosis in
this file was wrong.** It is not that a trait must now be declared
`const trait` — the vendored tree already declares them that way,
which is how it was written in January. What changed is the *impl*
keyword order: `impl const Trait for T` is now
`const impl Trait for T`, and generics go on the `impl`
(`const impl<T: [const] Foo> Bar for T`). Bounds are unaffected;
`T: const Foo`, `T: [const] Foo` and `impl const Foo` in argument
position all still compile. Everything else — the unresolved
`UiVec2`/`Vec2`/`impl_op` imports, and a `Color<u8>` that resolved
to `wgpu_types::Color` — cascaded from the seven files that failed
to parse. The rewrite was mechanical across 20 sites and took the
workspace from 36 errors to 0.
**`#![feature]` gates, 12 after this step** (two were declared and
unused, and were removed: `map_try_insert`, `const_cmp`).
Load-bearing and worth watching: `const_trait_impl`, `const_ops`,
`const_convert`, `const_destruct` are the const-traits family and
the one that has already broken once — they move together, so
advancing the pin means re-reading this section. `unboxed_closures`
+ `fn_traits` (postfix builder API) and `unsize` +
`coerce_unsized` (widget handles) are pairs. The rest are
individually small: `macro_metavar_expr_concat`, `portable_simd`,
`associated_type_defaults`, `option_into_flat_iter`, and `gen_blocks`
in the top crate.
**Running it headless.** `iris/run-headless.sh EXAMPLE [--shot PNG]`
with `iris/headless.conf`, the same trick `emu` uses: a headless
sway, and `grim` for the picture. It deliberately starts its *own*
compositor rather than joining `emu`'s — sway tiles, so adding a
window to the one an emulator sits in resizes that emulator.
Unlike `emu`'s it disables Xwayland, since winit speaks Wayland.
**This VM has a real GPU for this**: Vulkan 1.4 through Venus onto
the host's RX 7900 XT, and GL 4.6 through virgl — so desktop wgpu
work here is not software-rasterised, unlike inside the emulator.
**iris has no tests at all** (`cargo test --workspace`: 0 passed
across 6 targets). Nothing to keep passing, and nothing to catch a
regression — worth knowing before I1 changes the text stack.
**`iris-core` no longer depends on winit, and now cross-compiles to
Android.** It wanted exactly one thing from it — `PhysicalSize<u32>`
in `UiRenderNode::resize`'s signature, for two numbers it immediately
turned into floats — and that pulled a whole windowing backend into
the layer below it, the wrong direction. `resize` takes
`impl Into<Vec2>` now, like `UiRenderState::resize` beside it already
did. The consequence is the point: with winit in the graph an Android
build of the core failed in `android-activity` (which needs a backend
feature nothing here selects), and without it
`cargo ndk -t arm64-v8a -P 26 build -p iris-core` finishes in 30s and
produces an rlib, wgpu's Android backend included. So **iris's
widget, layout and render core already builds for the phone**, and
what I2 has to supply is the surface, the input and the IME — not a
port of the library.
**Build weight, cold, on this VM's 8 cores** (`rm -rf target`, then
`cargo build --example tabs`), since "the Linebender stack is slow in
debug" was the worry behind this question: plain debug **43s** and a
2.1 GB `target/`; with the `[profile.dev.package."*"] opt-level = 2`
knob, **1m46s** and 1.5 GB. So iris's own wgpu + winit + cosmic-text
graph is not the slow thing — which makes it a calibration for E1
rather than an answer about Masonry, whose graph adds Vello, Parley,
Fontique and Skrifa. Runtime cost of the knob was not measured here.
**Open defect found while doing this: iris sometimes never adopts
the window's real size.** Measured on the headless rig, ~3 starts in
15: the `tabs` example settles showing its 800×600 startup layout in
the top-left of a 1920×1200 surface, black around it, and stays that
way indefinitely — it is not a screenshot taken too early, since the
picture is byte-identical for the next four seconds. What is *not*
the cause, each checked: the winit event order is identical in good
and bad runs (`Resized(800×600)`, two redraws, `Resized(1920×1200)`,
one redraw), the swapchain reports `1920×1200` and
`suboptimal=false` on that last draw, and `output_size` is
`(1920, 1200)` going into it. It is timing-sensitive in the way that
makes it expensive: adding a single `eprintln!` anywhere in the draw
or event path hides it completely (0 in 16), which is why the
instrumentation above could not catch it in the act. A pointer move
does not repair it, because iris only redraws when something
changed; an output mode change does, because that is another resize.
Left open rather than guessed at. It matters most for **I2**, where
every rotation and every keyboard open is a resize, so a stale frame
would be the normal case rather than a rare one; a Wayland-level
trace of the xdg-surface configure/ack/commit sequence is the next
step, not more `eprintln`.
One thing was fixed on the way, and it is not that bug: `update`
redrew everything when `resized` was set, but `needs_redraw` — which
is what decides whether to *ask* for a frame — did not know about
`resized` at all. The two now share one `needs_redraw_all`, since a
condition in one and not the other is a frame nobody requests. It is
latent on Wayland only because winit asks for a redraw after a resize
by itself; on Android, where the surface work of I2 will not have
winit underneath it, nothing else here would have asked.
- [ ] **I1 — the text stack decision.** iris uses cosmic-text; the
transcript needs rich inline spans (links, code chips, colour),
selection across many widgets with the platform's handles on the