perf: P11 step 1 - specify the counted for over a flat list in PERFORMANCE.md (step 1 of #58) #415

Closed
opened 2026-10-23 04:53:32 +00:00 by orion · 2 comments
Collaborator

Context

Step 1 of #58 (perf: P11 - a for over a concrete list is a counted loop; PERFORMANCE.md finding F5 level 2, section 5 round P11). #58 is not one PR: the round table gives ir/lower/statement.trb as its only file, but the loop that matters is written for value in numbers with numbers: List<Int> (benchmarks/list-iterate), and the lowering runs before devirtualizeProgram, so at that point it cannot tell an ArrayList from a List whose at walks (ConsStack, BankersQueue). Where the counted loop is made, which lists qualify and what it must not change is not written down yet. That has to be decided in the design record before the code is written. This step is documentation only.

Goal

A subsection "What P11 builds" in docs/PERFORMANCE.md (after "What P7 built"), and a corrected P11 row in section 5, that specify the round precisely enough for one implementing PR (step 2, filed when this one lands).

Decisions the record starts from (decided by the loop, check them against the IR excerpts of finding F5 and compiler/src/ir/elements.trb, correct them where the code says otherwise)

  1. Where. A pass over the IR between devirtualizeProgram and insertOwnership, next to rewriteElementPlaces (the shape of P7), not a branch of lowerForIterate. The lowering cannot see the concrete type; the devirtualization has made the cursor a Record(ListIterator<...>) with a direct next call by then. The P11 row's file column changes to the new pass file and the pipeline wiring.
  2. Which lists. Only a cursor made by the flat ArrayList iterator (and the Trie* aliases while they are aliases of it). A RingList, ConsStack, BankersQueue, Array, map, set, String.chars() and every user Iterate keep their cursor: an indexed read of a persistent list is not O(1), and the counted loop must never make a loop quadratic. A cursor that stayed boxed keeps its dispatch.
  3. What the loop becomes. length read once at loop entry, a counter, and the item read by the Element step of P7 instead of the Option of next. The subject is a value held by the frame for the whole loop (the comment of lowerForIterate says so), so the length cannot change under it and no bounds panic can occur; the record says why the panic site of Index.at is therefore not needed. break, continue, a tuple pattern, an item that is a counted value (retain per turn) and an empty list each get a sentence.
  4. Both back ends. The pass works on the IR, so the C and the bytecode back end take it; the record states what the VM executes for the new loop and that its time on list-iterate must not get worse.
  5. Measurement. Target and how it is read: benchmarks/list-iterate ratio against its C twin falls to the level of benchmarks/list-index of the same run (the table of section 4 has 39.09x against 13.25x), allocations stay 24, recorded before and after in the forge CI as benchmarks/run.sh prints it (fastest of several runs, a measurement and not a gate). The record names the number that counts as "done" and the one that counts as "it did not help".
  6. Seed. Expected none (no syntax, no native, no name looked up by string); the record states it, or names the lookup if the pass needs one (then the two-commit rule applies to step 2).
  7. Step 2 outline. New compiler/src/ir/<pass>.trb, wiring in the pipeline, an IR snapshot test of the loop, conformance programs for the cases of decision 3, list-iterate.allocations budget check, PERFORMANCE.md status updated. Sized for one PR of two Conventional Commits; the record says where to cut if it is more.

Acceptance criteria

  • docs/PERFORMANCE.md has "What P11 builds" with numbered decisions 1 to 6 above (corrected where the code differs), each with its reason, and section 5's P11 row names the real files.
  • The F5 "What is left" paragraph and the zero-cost contract row for for value in collection as a whole link the subsection.
  • The status line at the top still says P11 waits (it is not built); the "after the VM" sentence of section 5 is updated: the VM exists, so the round is no longer waiting for a second back end, and its answer to both back ends is in the subsection.
  • No claim without evidence: every IR or C excerpt is copied from this document or from a file in the repo; no benchmark number is invented (the "before" numbers are the ones already in section 4).
  • torb docs check docs, torb docs index --check docs and the skills/ regeneration are green in both CI tiers.

Design record point

PERFORMANCE.md finding F5 (level 2), round P11, section 2 row "for value in collection as a whole"; COLLECTIONS gap 9; P7 (ir/elements.trb).

Seed impact

None (documentation only).

Size

One PR, one Conventional Commit (docs(performance): specify round P11, the counted for over a flat list).

Out of scope

Any change to compiler/, std/ or runtime/; running the benchmarks (the record uses section 4's numbers and says the implementing step records the new ones); the seed; the Option of get and the bounds check of a[index] (finding 8); #56 and #59; the ArrayList-on-Buffer rewrite of #55/#413 (read the record of #413 if it has landed and align gap 9 with it, do not change it).

Dependencies

None hard. Part of #58.

## Context Step 1 of #58 (perf: P11 - a `for` over a concrete list is a counted loop; PERFORMANCE.md finding F5 level 2, section 5 round P11). #58 is not one PR: the round table gives `ir/lower/statement.trb` as its only file, but the loop that matters is written `for value in numbers` with `numbers: List<Int>` (`benchmarks/list-iterate`), and the lowering runs before `devirtualizeProgram`, so at that point it cannot tell an `ArrayList` from a `List` whose `at` walks (`ConsStack`, `BankersQueue`). Where the counted loop is made, which lists qualify and what it must not change is not written down yet. That has to be decided in the design record before the code is written. This step is documentation only. ## Goal A subsection "What P11 builds" in `docs/PERFORMANCE.md` (after "What P7 built"), and a corrected P11 row in section 5, that specify the round precisely enough for one implementing PR (step 2, filed when this one lands). ## Decisions the record starts from (decided by the loop, check them against the IR excerpts of finding F5 and `compiler/src/ir/elements.trb`, correct them where the code says otherwise) 1. **Where.** A pass over the IR between `devirtualizeProgram` and `insertOwnership`, next to `rewriteElementPlaces` (the shape of P7), not a branch of `lowerForIterate`. The lowering cannot see the concrete type; the devirtualization has made the cursor a `Record(ListIterator<...>)` with a direct `next` call by then. The P11 row's file column changes to the new pass file and the pipeline wiring. 2. **Which lists.** Only a cursor made by the flat `ArrayList` iterator (and the `Trie*` aliases while they are aliases of it). A `RingList`, `ConsStack`, `BankersQueue`, `Array`, map, set, `String.chars()` and every user `Iterate` keep their cursor: an indexed read of a persistent list is not O(1), and the counted loop must never make a loop quadratic. A cursor that stayed boxed keeps its dispatch. 3. **What the loop becomes.** `length` read once at loop entry, a counter, and the item read by the `Element` step of P7 instead of the `Option` of `next`. The subject is a value held by the frame for the whole loop (the comment of `lowerForIterate` says so), so the length cannot change under it and no bounds panic can occur; the record says why the panic site of `Index.at` is therefore not needed. `break`, `continue`, a tuple pattern, an item that is a counted value (retain per turn) and an empty list each get a sentence. 4. **Both back ends.** The pass works on the IR, so the C and the bytecode back end take it; the record states what the VM executes for the new loop and that its time on `list-iterate` must not get worse. 5. **Measurement.** Target and how it is read: `benchmarks/list-iterate` ratio against its C twin falls to the level of `benchmarks/list-index` of the same run (the table of section 4 has 39.09x against 13.25x), allocations stay 24, recorded before and after in the forge CI as `benchmarks/run.sh` prints it (fastest of several runs, a measurement and not a gate). The record names the number that counts as "done" and the one that counts as "it did not help". 6. **Seed.** Expected none (no syntax, no native, no name looked up by string); the record states it, or names the lookup if the pass needs one (then the two-commit rule applies to step 2). 7. **Step 2 outline.** New `compiler/src/ir/<pass>.trb`, wiring in the pipeline, an IR snapshot test of the loop, conformance programs for the cases of decision 3, `list-iterate.allocations` budget check, PERFORMANCE.md status updated. Sized for one PR of two Conventional Commits; the record says where to cut if it is more. ## Acceptance criteria - [ ] `docs/PERFORMANCE.md` has "What P11 builds" with numbered decisions 1 to 6 above (corrected where the code differs), each with its reason, and section 5's P11 row names the real files. - [ ] The F5 "What is left" paragraph and the zero-cost contract row for `for value in collection` as a whole link the subsection. - [ ] The status line at the top still says P11 waits (it is not built); the "after the VM" sentence of section 5 is updated: the VM exists, so the round is no longer waiting for a second back end, and its answer to both back ends is in the subsection. - [ ] No claim without evidence: every IR or C excerpt is copied from this document or from a file in the repo; no benchmark number is invented (the "before" numbers are the ones already in section 4). - [ ] `torb docs check docs`, `torb docs index --check docs` and the `skills/` regeneration are green in both CI tiers. ## Design record point PERFORMANCE.md finding F5 (level 2), round P11, section 2 row "`for value in collection` as a whole"; COLLECTIONS gap 9; P7 (`ir/elements.trb`). ## Seed impact None (documentation only). ## Size One PR, one Conventional Commit (`docs(performance): specify round P11, the counted for over a flat list`). ## Out of scope Any change to `compiler/`, `std/` or `runtime/`; running the benchmarks (the record uses section 4's numbers and says the implementing step records the new ones); the seed; the `Option` of `get` and the bounds check of `a[index]` (finding 8); #56 and #59; the ArrayList-on-Buffer rewrite of #55/#413 (read the record of #413 if it has landed and align gap 9 with it, do not change it). ## Dependencies None hard. Part of #58.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T11:03:00Z

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T11:03:00Z
Author
Collaborator

[loop:torbscript] implement: PR #416 (#416) is open and mergeable; the worker ended at its run limit while forge CI was still queued on head b5ab08b. next: waiting (CI on PR #416, then review by argus)

[loop:torbscript] implement: PR #416 (https://git.torb.dev/torbscript/language/pulls/416) is open and mergeable; the worker ended at its run limit while forge CI was still queued on head b5ab08b. next: waiting (CI on PR #416, then review by argus)
argus closed this issue 2026-10-23 06:22:08 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#415
No description provided.