From 32151f7f9f90e7b4e6e185c85bd0a45b70f3d425 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Thu, 17 Sep 2026 16:41:06 -0400 Subject: [PATCH] Record retained extent-frame progress and remaining layout gap --- docs/HANDOFF.md | 91 ++++++++++++++++++++++++++++++++++++++++--------- docs/PLAN.md | 9 ++++- 2 files changed, 83 insertions(+), 17 deletions(-) diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index dc473de..a8449a8 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -423,7 +423,7 @@ The original proposal was implemented by Claude as `5fcace1` on `wip/region-and-placement` in `/home/bob/repos/iris-pr18`. It kept the fraction reference stable but failed five layout cases and three draw-count cases. The first correction is `efb416b`, exact recomposition is `2ed5503`, and the -current performance continuation is **`a7307d9`**, pushed to origin's +current performance continuation is **`c44bd19`**, pushed to origin's `wip/region-and-placement`, in the isolated checkout `/home/bob/repos/iris-layout-experiment`; **it is not on PR #18**. The original checkout is unchanged. Keep the experiment's build directory separate: Cargo @@ -517,22 +517,24 @@ regression fails with four draws instead of three when the optimization is disabled. **Still not ready to replace PR #18.** The prototype does substantially more -nested layout work than `e44dea3`, even after removing intermediate placements -(seed 1, depth 8): +nested layout work than `e44dea3`, even after retaining extent-relative child +frames (seed 1, depth 8): | phase | widget draws at e44dea3 | current | primitive writes at e44dea3 | current | | --- | ---: | ---: | ---: | ---: | -| cold | 369 | 484 | 9635 | 9179 | +| cold | 369 | 463 | 9635 | 9179 | | repaint | 1 | 1 | 1 | 1 | -| many | 157 | 274 | 4721 | 5873 | -| size | 16 | 247 | 106 | 5200 | +| many | 157 | 263.1 | 4721 | 5873 | +| size | 16 | 240 | 106 | 5200 | | scroll | 2 | 2 | 0 | 0 | -| resize | 13 | 292 | 0 | 5730 | +| resize | 13 | 278 | 0 | 5730 | These are instrumented work counts, not speedups (50 frames per phase except -cold, which is one; 578 generated widgets, 191 finally active). Against -`2ed5503`, the changes remove 7 widget draws and 525 primitive writes per size -frame, and 8 draws and 525 writes per resize frame. +cold, which is one; 578 generated widgets, 191 finally active). The +measurement-only change at `7601aa2` removed 7 widget draws and 525 +primitive writes per size frame, and 8 draws and 525 writes per resize frame, +relative to `2ed5503`. The extent-relative continuation below skips additional +container evaluations but does not reduce primitive writes. **Measure cycles as well as instructions** (Bryan, 2026-09-17). The following are whole-process medians from seven alternating before/after runs of direct, @@ -578,7 +580,7 @@ executables under `perf stat -e cycles:u,instructions:u`, seed 1, depth 8, All nine samples per side were retained; before/after cycle ranges do not overlap. No scans or builds ran during measurement. All instrumented work counters match `7601aa2` on all six phases (50 frames, cold once), including -the draw and primitive counts in the table above: this reduces the cost of +the primitive counts in the table above: this reduces the cost of emitting the repeated glyphs, not the repeated layout evaluations themselves. Additional depth-6 fixtures, nine alternating pairs and 4000 frames each: @@ -602,14 +604,71 @@ saved under 0.8% of instructions and no cycles; special-casing equal endpoint fractions in `UiSpan::within` cost 11% cycles and 7% instructions despite still inlining. Keep that hot general composition small. +**Child frames can now follow the extent without evaluating their container.** +`widget_within` accepts the same `DrawRegion::{Frame, Extent}` reference as +primitives and masks. `Pad` retains its pixel inset in extent coordinates; +`Stack` inherits placement for its sizing child and retains extent-relative +frames for its other children. Moving the extent replays those requests through +`ask_box`, preserving child alignment and region nodes. This extends the existing +inherited-child walk rather than adding a separate layout body or a second cache. +It stores a region-bearing relation per extent-following child, so retained +child records are larger than the old widget-id-only list. + +Two dependencies stay conservative. Fractional endpoints compose before pixel +evaluation, so their bounds cannot in general be pulled back through the extent +length alone; those frames still depend on raw placement. Reading a child's +fractional report also depends on placement because composing that report into +the parent's frame can change its value without changing the child's answer. +Pixel-inset frame bounds use the existing exact `Holds::through`. + +**Keep the measured slot when repositioning.** A first trial recomputed it from +`active.size`, which is the final draw's report. Text measured narrow and placed +wider may report a different height at the latter width; the slot is still the +one chosen from measurement. Seed 20/depth 4 caught this. The retained slot is +now preserved on axes not explicitly assigned by the parent. A small synthetic +width-dependent widget pins the same failure without fonts. + +Against `a7307d9`, seed 1/depth 8 skips 21 draws cold, 10.9 per `many` frame, +7 per size frame and 14 per resize frame. Repaint, scroll, and all primitive-write +counts are unchanged. Nine alternating pairs of direct uninstrumented release +executables under `perf stat -e cycles:u,instructions:u`, including setup: + +| phase | median cycles before → after (billions) | median instructions before → after (billions) | +| --- | ---: | ---: | +| size (2000 frames) | 4.0819 → 4.0714 | 10.4107 → 10.4017 | +| resize (2000 frames) | 4.8108 → 4.7237 | 12.1854 → 11.9252 | +| many (2000 frames) | 4.7918 → 4.7787 | 12.0925 → 12.0567 | +| repaint (1,000,000 frames) | 2.6701 → 2.6851 | 6.0175 → 6.0195 | +| scroll (300,000 frames) | 2.6296 → 2.6273 | 6.5025 → 6.5817 | + +Only resize establishes a cycle improvement: **1.8%**, with nonoverlapping sample +ranges, and 2.1% fewer instructions. Every other phase's cycle ranges overlap; +scroll uses 1.2% more instructions. All samples were retained and no scans or +builds ran during measurement. This is a small reduction in work, not closure of +the main gap. + +Verification: workspace formatting, clippy with `layout-diagnostics`, tests +(104 suite tests, 21 core tests), exact release scans of 2000 seeds/depth 4 and +1000/depth 6, the shrinker over 400 trees/depth 5, and a debug scan of 120 +seeds/depth 4 with assertions enabled. +The new no-redraw test checks nested padding/stacks, fixed child +alignment, primitives, masks, and region nodes; restoring the old container +calls makes it fail with three draws instead of two. Additional tests cover +fractional reports and numeric dependencies. All five reference renders match +`a7307d9` pixel for pixel at 1920x1200, and the resized 900x1200 `tabs` matches a +cold render there. The GPU probe reports Venus on the RX 7900 XT. + **The remaining gap has not been established as an unavoidable correctness -cost.** A temporary failure trace of one frame on seed 1, depth 8 counted -39/108 failed reuse attempts in size, 65/151 in resize, and 45/109 in many +cost.** Before the extent-relative change, a temporary failure trace of one +frame on seed 1, depth 8 counted 39/108 failed reuse attempts in size, 65/151 in resize, and 45/109 in many where only the raw placement snapshot differed: both numeric frame and extent ranges still held. Containers read that snapshot to construct child positions, -so discarding the check would be wrong; a way to retain those child positions -relative to the extent is the next protocol question. There is also necessary -width-dependent work: the hot text at widget 199 visits 68 px and 89.53613 px +so discarding the check would be wrong. Padding and stacks now retain their +extent-relative child frames, but `Span` still constructs its mixed-reference +slots from a raw placement read: along the row, child fractions refer to the +frame while cursor positions and leftover room refer to the extent. Retaining +those expressions without changing their fixed-point evaluation order is the +next protocol question. There is also necessary width-dependent work: the hot text at widget 199 visits 68 px and 89.53613 px and reports different heights (316.80078 and 228.80078). It is evaluated repeatedly at those widths, so that fact alone does not justify all the repeats. diff --git a/docs/PLAN.md b/docs/PLAN.md index f1811c6..9bc092f 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -879,7 +879,14 @@ prototype has significant container-update costs and is not yet adopted into the pinned framework. A container's length query can evaluate the offered extent without first placing the child's reported size; the parent's subsequent draw assigns the final slot using the same `Widget::draw` body. A parent-selected slot -still honors a fixed child's alignment within that slot. +still honors a fixed child's alignment within that slot. Child frames can also +retain an extent-relative region through `widget_within(DrawRegion::Extent)`; +padding and stacks use this instead of baking the current placement into child +coordinates. Pixel insets carry numeric validity through the extent. General +fractional endpoints and fractional reports retain conservative placement +dependencies; fixed-point composition must not be reassociated to remove them. +Retained movement preserves the slot chosen from measurement, even if the final +draw reports another size. **A `Span` leaves its children in the offered orthogonal region unless explicitly compacted** (2026-09-12). It still reports the widest orthogonal