build: torb build --explain-copies #61

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

Context

Copy on write hides where a copy happens; torb build --explain-copies reports each copy the compiler can prove, at its source span, with the reason. Design: PERFORMANCE.md section 6 ("torb build --explain-copies"). It is a flag and not a warning: a copy is the semantics working, not a mistake.

Source

Decisions (refined)

  • Seed impact: none, one commit, implementable now. tools/bootstrap.sh builds the compiler with build ./compiler --output ... only, so the compiler's own build does not use the new flag (CLAUDE.md "Seed and breaking changes": only a flag the build uses needs the two-commit dance). The change must use no syntax or native the published seed lacks. Do not touch seed/.

  • Flag: --explain-copies on torb build, boolean, no value; parsed in buildOptionsOf (compiler/src/main.trb, next to --timings), carried as BuildOptions.explainCopies: Bool = false (compiler/src/cli/build.trb), listed in the usage text (compiler/src/cli/usage.trb). torb run/test/check do not accept it (existing unknown-flag error).

  • Which pass: the report is produced by a new compiler/src/ir/explain-copies.trb, run in cli/build.trb right after insertOwnership (where --timings notes "ownership"), only when the flag is set. With the flag off nothing runs and the emitted C is byte-identical (the bootstrap fixpoint must still hold).

  • What counts as a copy: a MakeUnique(reference) the ownership pass inserted (uniqueOwnersOf, ir/ownership.trb) whose owner is provably shared at that point: the base slot (or the prefix being made unique) has a retained alias (Retain/Copy target) that liveness (ir/liveness.trb) still holds live after the write. That is PERFORMANCE.md's "a MakeUnique on a reference whose base is live afterwards". A MakeUnique that may copy only depending on runtime counts is not reported (no false positives; the report says "the compiler can see", not "all"). Shared-storage owners (boxes, lazy) are already skipped by uniqueOwnersOf.

  • Spans: Instruction.MakeUnique has no location today (only Block.at, Call.at, Element.at do). Add at: LocationId? = None to MakeUnique (a defaulted case field like Call.at), set it where ir/lower/place.trb forms the path and in uniqueOwnersOf from the guarded instruction's location, falling back to Block.at. The IR text format (ir/print.trb) does not print it, so no existing IR snapshot changes. Locations are workspace-relative (Location), so no machine path leaks.

  • Scope: only copies whose location is in the built project's own sources; std/ and package dependencies are excluded.

  • Output: standard error (like --timings), stdout and exit code unchanged (always 0). One note per distinct (path, line, column, reason), sorted by path, line, column - monomorphized instances of one generic body are reported once. Format, per PERFORMANCE.md section 6, without the second hint line (see follow-up):

    note: this write copies a shared value
     --> src/main.trb:12:3
      |
    12 |     rows[row][column] = rows[row][column] + 1
      |     ^^^^^^^^^^^^^^^^^
      = `rows` still holds the row, so the row is not unique and the write copies it (O(length of the row))
    

    followed by one summary line, explain-copies: N copies at M places (0 copies at 0 places when none). The reason line names the holder slot by its source name when it has one.

  • Works with --emit-c and --timings; it is printed before the C compiler step, so it needs no C compiler.

  • Follow-up (out of scope here): the "how to avoid it" hint line is #302.

Acceptance criteria

  • torb build --explain-copies is accepted by torb build only, appears in torb build --help/usage, and BuildOptions.explainCopies defaults to false (parse test in the style of compiler/tests/deprecation.test.trb lines ~545-552 or driver.test.trb).
  • MakeUnique carries an optional at; the IR text of all existing snapshots is unchanged (compiler/tests/ownership.test.trb, devirtualize.test.trb stay green unmodified).
  • New compiler/tests/explain-copies.test.trb (using ownedIrOf from lower-harness.trb) pins the report text for: (a) the rows[row][column] = ... program of PERFORMANCE.md section 6 while rows[row] is still held: exactly one note at the right line:column with the reason; (b) the same write when the row is not held elsewhere: no note; (c) a var array/list written after a let alias that is dead: no note; (d) two instances of one generic body: one note.
  • With the flag, a program with a provable copy prints the note(s) and the summary line on stderr and exits 0; stdout is unchanged. A conformance-style CLI case in tests/command-line/ (or the existing harness there) pins stderr for one program with a copy and one without.
  • Notes name only project-relative paths; copies in std/ are not reported.
  • Without the flag, torb build --emit-c output is byte-identical to before the change on compiler/ (program.c diff empty) and the bootstrap fixpoint holds.
  • docs/tooling/torb-build.md documents the flag (synopsis line, a ### \--explain-copies`section with the sample output, in the style of### `--timings`), and PERFORMANCE.md section 6 / docs/ROADMAP.md` line 131 mark it built.
  • Tier A is green; no change to seed/ and no seed refresh needed.

Dependencies

None. (#302, the hint line, depends on this.)

## Context Copy on write hides where a copy happens; `torb build --explain-copies` reports each copy the compiler can prove, at its source span, with the reason. Design: [PERFORMANCE.md section 6](https://git.torb.dev/torbscript/language/src/branch/main/docs/PERFORMANCE.md) ("`torb build --explain-copies`"). It is a flag and not a warning: a copy is the semantics working, not a mistake. ## Source - [PERFORMANCE.md section 6](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/PERFORMANCE.md#L1280) ## Decisions (refined) - **Seed impact: none, one commit, implementable now.** `tools/bootstrap.sh` builds the compiler with `build ./compiler --output ...` only, so the compiler's own build does not use the new flag (CLAUDE.md "Seed and breaking changes": only a flag *the build uses* needs the two-commit dance). The change must use no syntax or native the published seed lacks. Do not touch `seed/`. - **Flag**: `--explain-copies` on `torb build`, boolean, no value; parsed in `buildOptionsOf` (`compiler/src/main.trb`, next to `--timings`), carried as `BuildOptions.explainCopies: Bool = false` (`compiler/src/cli/build.trb`), listed in the usage text (`compiler/src/cli/usage.trb`). `torb run`/`test`/`check` do not accept it (existing unknown-flag error). - **Which pass**: the report is produced by a new `compiler/src/ir/explain-copies.trb`, run in `cli/build.trb` right after `insertOwnership` (where `--timings` notes "ownership"), only when the flag is set. With the flag off nothing runs and the emitted C is byte-identical (the bootstrap fixpoint must still hold). - **What counts as a copy**: a `MakeUnique(reference)` the ownership pass inserted (`uniqueOwnersOf`, `ir/ownership.trb`) whose owner is *provably shared at that point*: the base slot (or the prefix being made unique) has a retained alias (`Retain`/`Copy` target) that liveness (`ir/liveness.trb`) still holds live after the write. That is PERFORMANCE.md's "a `MakeUnique` on a reference whose base is live afterwards". A `MakeUnique` that may copy only depending on runtime counts is **not** reported (no false positives; the report says "the compiler can see", not "all"). Shared-storage owners (boxes, `lazy`) are already skipped by `uniqueOwnersOf`. - **Spans**: `Instruction.MakeUnique` has no location today (only `Block.at`, `Call.at`, `Element.at` do). Add `at: LocationId? = None` to `MakeUnique` (a defaulted case field like `Call.at`), set it where `ir/lower/place.trb` forms the path and in `uniqueOwnersOf` from the guarded instruction's location, falling back to `Block.at`. The IR text format (`ir/print.trb`) does **not** print it, so no existing IR snapshot changes. Locations are workspace-relative (`Location`), so no machine path leaks. - **Scope**: only copies whose location is in the built project's own sources; `std/` and package dependencies are excluded. - **Output**: standard error (like `--timings`), stdout and exit code unchanged (always 0). One note per distinct (path, line, column, reason), sorted by path, line, column - monomorphized instances of one generic body are reported once. Format, per PERFORMANCE.md section 6, without the second hint line (see follow-up): ```text note: this write copies a shared value --> src/main.trb:12:3 | 12 | rows[row][column] = rows[row][column] + 1 | ^^^^^^^^^^^^^^^^^ = `rows` still holds the row, so the row is not unique and the write copies it (O(length of the row)) ``` followed by one summary line, `explain-copies: N copies at M places` (`0 copies at 0 places` when none). The reason line names the holder slot by its source name when it has one. - **Works with** `--emit-c` and `--timings`; it is printed before the C compiler step, so it needs no C compiler. - **Follow-up (out of scope here)**: the "how to avoid it" hint line is #302. ## Acceptance criteria - [ ] `torb build --explain-copies` is accepted by `torb build` only, appears in `torb build --help`/usage, and `BuildOptions.explainCopies` defaults to false (parse test in the style of `compiler/tests/deprecation.test.trb` lines ~545-552 or `driver.test.trb`). - [ ] `MakeUnique` carries an optional `at`; the IR text of all existing snapshots is unchanged (`compiler/tests/ownership.test.trb`, `devirtualize.test.trb` stay green unmodified). - [ ] New `compiler/tests/explain-copies.test.trb` (using `ownedIrOf` from `lower-harness.trb`) pins the report text for: (a) the `rows[row][column] = ...` program of PERFORMANCE.md section 6 while `rows[row]` is still held: exactly one note at the right line:column with the reason; (b) the same write when the row is not held elsewhere: no note; (c) a `var` array/list written after a `let` alias that is dead: no note; (d) two instances of one generic body: one note. - [ ] With the flag, a program with a provable copy prints the note(s) and the summary line on stderr and exits 0; stdout is unchanged. A conformance-style CLI case in `tests/command-line/` (or the existing harness there) pins stderr for one program with a copy and one without. - [ ] Notes name only project-relative paths; copies in `std/` are not reported. - [ ] Without the flag, `torb build --emit-c` output is byte-identical to before the change on `compiler/` (`program.c` diff empty) and the bootstrap fixpoint holds. - [ ] `docs/tooling/torb-build.md` documents the flag (synopsis line, a `### \`--explain-copies\`` section with the sample output, in the style of `### \`--timings\``), and PERFORMANCE.md section 6 / `docs/ROADMAP.md` line 131 mark it built. - [ ] Tier A is green; no change to `seed/` and no seed refresh needed. ## Dependencies None. (#302, the hint line, depends on this.)
orion added this to the Kernel of natives milestone 2026-10-05 21:07:15 +00:00
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop), source PERFORMANCE.md section 6, not a duplicate, no collision with a design record. New driver flag (torb build), so check in refine whether the build uses it (seed impact); sonnet/medium unless the compiler needs a copy trace. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z

[loop:torbscript] intake: in scope (author orion, label loop), source PERFORMANCE.md section 6, not a duplicate, no collision with a design record. New driver flag (`torb build`), so check in refine whether the build uses it (seed impact); sonnet/medium unless the compiler needs a copy trace. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261010 until 2026-10-10T04:06Z

[loop:torbscript] claim refine by session:loop-torbscript-20261010 until 2026-10-10T04:06Z
Author
Collaborator

[loop:torbscript] refine: body rewritten with decisions and testable criteria; no seed impact (bootstrap does not use the flag, one commit), MakeUnique needs an optional at, report from a new ir/explain-copies.trb after insertOwnership; hint line split off as #302, next: ready for implement

[loop:torbscript] refine: body rewritten with decisions and testable criteria; no seed impact (bootstrap does not use the flag, one commit), `MakeUnique` needs an optional `at`, report from a new ir/explain-copies.trb after insertOwnership; hint line split off as #302, next: ready for implement
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T08:08Z

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T08:08Z
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T14:02Z (re-claim: the previous worker died in the gateway gap between 02:10Z and 08:00Z without a write-back)

[loop:torbscript] claim implement by session:loop-torbscript-20261010 until 2026-10-10T14:02Z (re-claim: the previous worker died in the gateway gap between 02:10Z and 08:00Z without a write-back)
Author
Collaborator

[loop:torbscript] implement: PR #303 opened (head a26c683e1a, argus requested, CI pending); the worker ended before its write-back, posted by the loop session; est. tokens unknown, next: waiting (review by argus)

[loop:torbscript] implement: PR https://git.torb.dev/torbscript/language/pulls/303 opened (head a26c683e1a, argus requested, CI pending); the worker ended before its write-back, posted by the loop session; est. tokens unknown, next: waiting (review by argus)
argus closed this issue 2026-10-10 12:22:11 +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#61
No description provided.