parallel: Window and windows (CONCURRENCY slice F, gap 6) #4

Closed
opened 2026-10-05 21:07:09 +00:00 by orion · 12 comments
Collaborator

Context

Gap 6 and the barrier exposure of slice F in CONCURRENCY.md: data-parallel work over plain numbers without copies. Gaps 4 and 5 of the same slice are split out (see Dependencies); this issue keeps Window, windows and the gate.

Source

Goal

windows(items, count, body) splits a buffer into count disjoint Windows and runs body on each at the same time on the worker pool, the caller taking window 0, returning only when every window is done. A window is a pointer, an offset and a length into the caller's heap: no count change, no copy.

Acceptance criteria

  • public shared type Window<Item> in std/parallel with Length and [index] read and write (it implements MutableIndex<Int, Item>; the record's R4 forbids only what resizes: no add, insert, removeAt), produced only by the region, with no Close. Item: Plain is the bound (needs #155).
  • public fn windows<Item: Plain>(var items: ..., count: Int, body: (var window: Window<Item>) => Void) in std/parallel, imported by name (not in the prelude, section 15 question 2). The body is a closure (needs #156).
  • The borders depend on the length and count only, never on workers (the rule of section 4); a length that is not a multiple of count gives windows that differ by at most one element, none empty unless count > length; count < 1 is a panic with a message.
  • The region blocks its caller; a cancelled region still joins (section 6, "The region blocks its caller"). A Task function cannot answer a windows call as a Task (TYPECHECKER gap 49 already refuses a var parameter on a function that answers a Task).
  • The checker lets a Window reach the region's body despite the shared type confinement of gap 3 (a region's body is the exception beside Task and Channel), and still refuses a Window that escapes the body (stored, returned, captured by spawn): one probe per escape.
  • Gate (slice F): a data-parallel scale over a million Floats, windows(values, 64) { var window => for i in 0..window.length { window[i] = window[i] * 2.0 } }, gives the same output as the sequential loop, byte-identical, at workers: 1, 2 and Workers.count(); zero live blocks afterwards; zero copies at the crossing (how the counter is read is the second owner decision of the comment below).
  • Runs in both back ends: conformance program under tests/conformance/ with a .workers file (4 and 1), plus a runtime test beside runtime/tests/pool_test.c for the fork-join and the borders.
  • A write through a window is visible in the caller's buffer after the region, with the storage made unique by the caller's var access before the region starts (a shared storage is copied once, before, and that copy is not a crossing copy).
  • Docs: docs/standard-library page for std/parallel gets windows, Window and Plain; CONCURRENCY.md slice F and section 16 say "Built" with the date; docs check docs green; skills/ regenerated.

Touches the seed?

  • A new native: Window's two members (length, [index]) in the manifest of natives (both back ends), the fork-join entry exposed to std. The compiler itself does not use them, so no seed refresh is needed for the repo to build; if declaring windows in std/ turns out to need one (the seed compiler builds std/), that is an owner call and the loop stops there.
  • No syntax change here (that is #156), no driver flag, and no std name the compiler looks up by string except Plain (#155).

Size

Larger than one PR as written; proposed split (this issue is the last part):

  1. #155 Plain (one commit, checker).
  2. #156 closure var parameter (one or two commits, parser, checker, IR).
  3. This issue: Window, windows, the gate. Two Conventional Commits: feat(runtime): a window over a buffer and the region that hands them out (C runtime, VM native, manifest) and feat(parallel): windows and Window (std, conformance, docs). If the owner picks Buffer<Item> (#54) as the parameter type, this also depends on #54.

Implementer: opus/high (VM, both back ends, runtime/).

Dependencies

  • Depends on #155 (Plain) and #156 (closure var parameter).
  • The fork-join barrier (gap 7, slice E), the pool and Workers.count() are built on main; gap 12's counter exists as torb_pool_statistics.copied in runtime/task.c but nothing reads it from a program yet.
  • Buffer<Item> is #54 (not built); see the owner decision comment.

Moved from the planning lists of the docs; the design record stays the specification.

## Context Gap 6 and the barrier exposure of slice F in CONCURRENCY.md: data-parallel work over plain numbers without copies. Gaps 4 and 5 of the same slice are split out (see Dependencies); this issue keeps `Window`, `windows` and the gate. ## Source - [CONCURRENCY.md slice F](https://git.torb.dev/torbscript/language/src/commit/61d7075c05bd85fd27c71a4c191dd7a9fd5af4c0/docs/design/CONCURRENCY.md#L1267) and [section 6, "Borrowing instead of copying"](https://git.torb.dev/torbscript/language/src/commit/61d7075c05bd85fd27c71a4c191dd7a9fd5af4c0/docs/design/CONCURRENCY.md#L366) (rules R1 to R4) - [Section 10](https://git.torb.dev/torbscript/language/src/commit/61d7075c05bd85fd27c71a4c191dd7a9fd5af4c0/docs/design/CONCURRENCY.md#L980) for the signatures, [gap 6 and gap 7 of section 13](https://git.torb.dev/torbscript/language/src/commit/61d7075c05bd85fd27c71a4c191dd7a9fd5af4c0/docs/design/CONCURRENCY.md#L1149) - ROADMAP milestone 7 names "the parallel `Plain` and `Window`" as open work. ## Goal `windows(items, count, body)` splits a buffer into `count` disjoint `Window`s and runs `body` on each at the same time on the worker pool, the caller taking window 0, returning only when every window is done. A window is a pointer, an offset and a length into the caller's heap: no count change, no copy. ## Acceptance criteria - [ ] `public shared type Window<Item>` in `std/parallel` with `Length` and `[index]` read and write (it implements `MutableIndex<Int, Item>`; the record's R4 forbids only what resizes: no `add`, `insert`, `removeAt`), produced only by the region, with no `Close`. `Item: Plain` is the bound (needs #155). - [ ] `public fn windows<Item: Plain>(var items: ..., count: Int, body: (var window: Window<Item>) => Void)` in `std/parallel`, imported by name (not in the prelude, section 15 question 2). The body is a closure (needs #156). - [ ] The borders depend on the length and `count` only, never on `workers` (the rule of section 4); a length that is not a multiple of `count` gives windows that differ by at most one element, none empty unless `count > length`; `count < 1` is a panic with a message. - [ ] The region blocks its caller; a cancelled region still joins (section 6, "The region blocks its caller"). A `Task` function cannot answer a `windows` call as a `Task` (TYPECHECKER gap 49 already refuses a `var` parameter on a function that answers a `Task`). - [ ] The checker lets a `Window` reach the region's body despite the `shared type` confinement of gap 3 (a region's body is the exception beside `Task` and `Channel`), and still refuses a `Window` that escapes the body (stored, returned, captured by `spawn`): one probe per escape. - [ ] **Gate** (slice F): a data-parallel scale over a million `Float`s, `windows(values, 64) { var window => for i in 0..window.length { window[i] = window[i] * 2.0 } }`, gives the same output as the sequential loop, byte-identical, at `workers: 1`, `2` and `Workers.count()`; zero live blocks afterwards; zero copies at the crossing (how the counter is read is the second owner decision of the comment below). - [ ] Runs in both back ends: conformance program under `tests/conformance/` with a `.workers` file (4 and 1), plus a runtime test beside `runtime/tests/pool_test.c` for the fork-join and the borders. - [ ] A write through a window is visible in the caller's buffer after the region, with the storage made unique by the caller's `var` access before the region starts (a shared storage is copied once, before, and that copy is not a crossing copy). - [ ] Docs: `docs/standard-library` page for `std/parallel` gets `windows`, `Window` and `Plain`; CONCURRENCY.md slice F and section 16 say "Built" with the date; `docs check docs` green; `skills/` regenerated. ## Touches the seed? - **A new native**: `Window`'s two members (`length`, `[index]`) in the manifest of natives (both back ends), the fork-join entry exposed to std. The compiler itself does not use them, so no seed refresh is needed for the repo to build; if declaring `windows` in `std/` turns out to need one (the seed compiler builds `std/`), that is an owner call and the loop stops there. - No syntax change here (that is #156), no driver flag, and no std name the compiler looks up by string except `Plain` (#155). ## Size Larger than one PR as written; **proposed split** (this issue is the last part): 1. #155 `Plain` (one commit, checker). 2. #156 closure `var` parameter (one or two commits, parser, checker, IR). 3. This issue: `Window`, `windows`, the gate. Two Conventional Commits: `feat(runtime): a window over a buffer and the region that hands them out` (C runtime, VM native, manifest) and `feat(parallel): windows and Window` (std, conformance, docs). If the owner picks `Buffer<Item>` (#54) as the parameter type, this also depends on #54. Implementer: opus/high (VM, both back ends, `runtime/`). ## Dependencies - Depends on #155 (`Plain`) and #156 (closure `var` parameter). - The fork-join barrier (gap 7, slice E), the pool and `Workers.count()` are built on `main`; gap 12's counter exists as `torb_pool_statistics.copied` in `runtime/task.c` but nothing reads it from a program yet. - `Buffer<Item>` is #54 (not built); see the owner decision comment. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop). Implements CONCURRENCY.md slice F (gaps 4, 5, 6); design record is the specification, no collision with CONCEPT "Open Questions". Touches the VM and both back ends (opus/high worker when implemented). Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued, buffer >= 2 ready)

[loop:torbscript] intake: in scope (author orion, label loop). Implements CONCURRENCY.md slice F (gaps 4, 5, 6); design record is the specification, no collision with CONCEPT "Open Questions". Touches the VM and both back ends (opus/high worker when implemented). Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued, buffer >= 2 ready)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261007 until 2026-10-07T17:39:15Z

[loop:torbscript] claim refine by session:loop-torbscript-20261007 until 2026-10-07T17:39:15Z
orion changed title from parallel: Plain, Window and windows (CONCURRENCY slice F) to parallel: Window and windows (CONCURRENCY slice F, gap 6) 2026-10-07 15:42:01 +00:00
Author
Collaborator

[loop:torbscript] owner decision: two choices slice F depends on are not settled in a design record.

1. What does windows borrow from, while Buffer<Item> (#54) does not exist?
The record writes windows<Item: Plain>(var items: Buffer<Item>, ...) and says only that the signature "type checks today with List in place of the planned Buffer" (CONCURRENCY.md section 6). #54 is a kernel-of-natives issue that needs a seed refresh and has no date.

  • (a) windows over List<Item: Plain> now; when #54 lands the parameter type widens to Buffer<Item> (a List of plain items stays accepted or is converted by the one-line change the COLLECTIONS record already plans for Bytes).
  • (b) Wait for #54; slice F stays blocked until the kernel of natives lands.
  • Recommendation: (a). A List of Plain items is one contiguous block, the var access makes it exclusive, and a unique storage is the only precondition the borrow needs. The ECS columns (#92) wait for #54 anyway.

2. How does the gate read the copy counter (gap 12)?
The gate is "zero copies (the counter of gap 12 at zero)". The counter exists as torb_pool_statistics.copied in runtime/task.c, read today only by C tests; no program, and no conformance run, can read it. A conformance program is the only gate that runs in both back ends.

  • (a) No new surface: a C test beside runtime/tests/pool_test.c pins copied unchanged around a windows region; the conformance program checks output equality and the leak gate. Zero copies is then proven for the C pool the VM also uses, but not asserted by a TorbScript program.
  • (b) A read-only native Workers.copies(): Int in std/task (or std/parallel) that a conformance program compares before and after the region. One new native in both back ends, a public name, no compiler use (no seed refresh).
  • (c) Wait for the profile output PERFORMANCE/BACKEND 6.3 plans (#61 --explain-copies is the build-time half); nothing is scheduled.
  • Recommendation: (b), named as a statistic and documented as such, because the gate is written in terms of the counter and must hold in both back ends. Choose (a) if you do not want a public name for it.

Taken as given, no decision needed: Window implements MutableIndex (the record lists Index in the declaration, but its output-buffer shape in section 6 writes through windows); Plain and the closure var parameter are settled in the record and split out as #155 and #156.

[loop:torbscript] owner decision: two choices slice F depends on are not settled in a design record. **1. What does `windows` borrow from, while `Buffer<Item>` (#54) does not exist?** The record writes `windows<Item: Plain>(var items: Buffer<Item>, ...)` and says only that the signature "type checks today with `List` in place of the planned `Buffer`" (CONCURRENCY.md section 6). #54 is a kernel-of-natives issue that needs a seed refresh and has no date. - (a) `windows` over `List<Item: Plain>` now; when #54 lands the parameter type widens to `Buffer<Item>` (a `List` of plain items stays accepted or is converted by the one-line change the COLLECTIONS record already plans for `Bytes`). - (b) Wait for #54; slice F stays blocked until the kernel of natives lands. - Recommendation: (a). A `List` of `Plain` items is one contiguous block, the `var` access makes it exclusive, and a unique storage is the only precondition the borrow needs. The ECS columns (#92) wait for #54 anyway. **2. How does the gate read the copy counter (gap 12)?** The gate is "zero copies (the counter of gap 12 at zero)". The counter exists as `torb_pool_statistics.copied` in `runtime/task.c`, read today only by C tests; no program, and no conformance run, can read it. A conformance program is the only gate that runs in both back ends. - (a) No new surface: a C test beside `runtime/tests/pool_test.c` pins `copied` unchanged around a `windows` region; the conformance program checks output equality and the leak gate. Zero copies is then proven for the C pool the VM also uses, but not asserted by a TorbScript program. - (b) A read-only native `Workers.copies(): Int` in `std/task` (or `std/parallel`) that a conformance program compares before and after the region. One new native in both back ends, a public name, no compiler use (no seed refresh). - (c) Wait for the profile output PERFORMANCE/BACKEND 6.3 plans (#61 `--explain-copies` is the build-time half); nothing is scheduled. - Recommendation: (b), named as a statistic and documented as such, because the gate is written in terms of the counter and must hold in both back ends. Choose (a) if you do not want a public name for it. Taken as given, no decision needed: `Window` implements `MutableIndex` (the record lists `Index` in the declaration, but its output-buffer shape in section 6 writes through windows); `Plain` and the closure `var` parameter are settled in the record and split out as #155 and #156.
Author
Collaborator

[loop:torbscript] refine: issue #4 was too large for one PR and rests on two unsettled choices. Split out the settled parts as #155 (Plain, gap 4) and #156 (closure var parameter, gap 5), both labelled ready; #4 now covers Window, windows and the gate (gap 6, depends on #155 and #156), with a rewritten body (goal, acceptance criteria, design record points, seed impact, size). Found Buffer<Item> is #54 (unbuilt) and the copy counter of gap 12 is not readable from a program, so the owner decision above is filed (element container while #54 is missing; how the gate reads the counter). #4 is not ready. Worker usage: roughly 100k tokens (sonnet), read-only checks of the design records and main at 61d7075c. next: waiting (owner decision)

[loop:torbscript] refine: issue #4 was too large for one PR and rests on two unsettled choices. Split out the settled parts as #155 (`Plain`, gap 4) and #156 (closure `var` parameter, gap 5), both labelled `ready`; #4 now covers `Window`, `windows` and the gate (gap 6, depends on #155 and #156), with a rewritten body (goal, acceptance criteria, design record points, seed impact, size). Found `Buffer<Item>` is #54 (unbuilt) and the copy counter of gap 12 is not readable from a program, so the owner decision above is filed (element container while #54 is missing; how the gate reads the counter). #4 is not `ready`. Worker usage: roughly 100k tokens (sonnet), read-only checks of the design records and `main` at 61d7075c. next: waiting (owner decision)
Author
Collaborator

[loop:torbscript] decided by the loop: both choices of the earlier owner-decision comment are internal technical design (no public statement, cost, account, or breaking change), and the owner guidance recorded on #17 and #164 is to decide such choices and record the reasoning. (1) windows takes List<Item: Plain> now, option (a): a list of plain items is one contiguous block, the var access makes it exclusive, and unique storage is the only precondition the borrow needs; the parameter widens to Buffer<Item> when #54 lands. Waiting for #54 would block slice F and the ECS columns on an undated kernel change. (2) The gate reads the counter through a read-only native, option (b), named and documented as a statistic: the gate is written in terms of the copy counter and must hold in both back ends, which only a TorbScript conformance program checks. It adds one native in both back ends and one public name, no compiler use, so no seed refresh. Dependencies #155 and #156 are closed. Labelled ready. next: implement (ready)

[loop:torbscript] decided by the loop: both choices of the earlier owner-decision comment are internal technical design (no public statement, cost, account, or breaking change), and the owner guidance recorded on #17 and #164 is to decide such choices and record the reasoning. (1) `windows` takes `List<Item: Plain>` now, option (a): a list of plain items is one contiguous block, the `var` access makes it exclusive, and unique storage is the only precondition the borrow needs; the parameter widens to `Buffer<Item>` when #54 lands. Waiting for #54 would block slice F and the ECS columns on an undated kernel change. (2) The gate reads the counter through a read-only native, option (b), named and documented as a statistic: the gate is written in terms of the copy counter and must hold in both back ends, which only a TorbScript conformance program checks. It adds one native in both back ends and one public name, no compiler use, so no seed refresh. Dependencies #155 and #156 are closed. Labelled `ready`. next: implement (ready)
Author
Collaborator

[loop:torbscript] implement: ready, but it touches the VM and both back ends while the workers on #164 (vm) and #207 (IR) run on the same paths. next: waiting (live workers on the same paths) until 2026-10-09T05:10:00Z

[loop:torbscript] implement: ready, but it touches the VM and both back ends while the workers on #164 (vm) and #207 (IR) run on the same paths. next: waiting (live workers on the same paths) until 2026-10-09T05:10:00Z
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-09T09:58Z

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-09T09:58Z
Author
Collaborator

[loop:torbscript] implement: worker started (opus/high, branch loop/4-parallel-windows); the earlier wait on #164/#207 ended, both landed (#239, #238). next: implement (worker in flight, claim above)

[loop:torbscript] implement: worker started (opus/high, branch `loop/4-parallel-windows`); the earlier wait on #164/#207 ended, both landed (#239, #238). next: implement (worker in flight, claim above)
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-09T09:58Z (worker running, branch loop/4-parallel-windows)

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-09T09:58Z (worker running, branch `loop/4-parallel-windows`)
Author
Collaborator

[loop:torbscript] implement: windows and Window landed in PR #248 (Closes #4). The PR adds the region runtime (torb_region_*, Workers.copies()) plus windows<Item: Plain>(var items: ArrayList<Item>, count, body) and Window in std/parallel. The checker keeps a window inside its body: it cannot be stored in a field, returned, rebound or captured by a task. It also adds conformance programs (borders, count, bounds, a million scaled), region tests in pool_test and docs (parallel.md, task.md, CONCURRENCY.md slice F / gap 6). CI is green on a71d6f33: gates, tier A, tier B (conformance in both back ends), bootstrap, commit messages. No seed refresh was needed.

Open points (listed in the PR):

  • Window is an ordinary type, not a shared type, because a shared type can only implement shared traits and Length/MutableIndex are not shared.
  • Coercing a window to a trait (MutableIndex<Int, Item>) drops the scoped marker, which is a possible escape.
  • values == sequential on two ArrayList<Float> is an internal error of the native back end. The conformance program works around it by comparing item by item; this deserves its own issue.
  • The buffer half of gap 6 waits on #54. Until then windows takes a var ArrayList.

Tokens: ~0.9M.

next: review (waiting on the merge gate argus)

[loop:torbscript] implement: `windows` and `Window` landed in PR https://git.torb.dev/torbscript/language/pulls/248 (Closes #4). The PR adds the region runtime (`torb_region_*`, `Workers.copies()`) plus `windows<Item: Plain>(var items: ArrayList<Item>, count, body)` and `Window` in `std/parallel`. The checker keeps a window inside its body: it cannot be stored in a field, returned, rebound or captured by a task. It also adds conformance programs (borders, count, bounds, a million scaled), region tests in pool_test and docs (parallel.md, task.md, CONCURRENCY.md slice F / gap 6). CI is green on a71d6f33: gates, tier A, tier B (conformance in both back ends), bootstrap, commit messages. No seed refresh was needed. Open points (listed in the PR): - `Window` is an ordinary `type`, not a `shared type`, because a shared type can only implement shared traits and `Length`/`MutableIndex` are not shared. - Coercing a window to a trait (`MutableIndex<Int, Item>`) drops the scoped marker, which is a possible escape. - `values == sequential` on two `ArrayList<Float>` is an internal error of the native back end. The conformance program works around it by comparing item by item; this deserves its own issue. - The buffer half of gap 6 waits on #54. Until then `windows` takes a `var ArrayList`. Tokens: ~0.9M. next: review (waiting on the merge gate argus)
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-09T12:36Z (fix of the review of PR #248: restore CRLF in three checker files)

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-09T12:36Z (fix of the review of PR #248: restore CRLF in three checker files)
argus closed this issue 2026-10-09 07:16:02 +00:00
Author
Collaborator

[loop:torbscript] PR #248 was merged by argus (merge commit f9b867d0). No after-merge steps in the PR; issue closed. Non-blocking follow-ups stay in #253.

[loop:torbscript] PR #248 was merged by argus (merge commit f9b867d0). No after-merge steps in the PR; issue closed. Non-blocking follow-ups stay in #253.
Sign in to join this conversation.
No labels
loop
ready
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#4
No description provided.