aboutsummaryrefslogtreecommitdiff
path: root/docs/BOOTSTRAP-OWNERSHIP.md
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 09:28:59 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 09:29:04 +0000
commitf13f84044be1bd18160f4845a739592219638862 (patch)
treede51ba649bf1f32adc64b890d5b8bb882b12a718 /docs/BOOTSTRAP-OWNERSHIP.md
parenttodo: record the reverted unchecked trial and what it showed (diff)
docs: remove temporary notes and fix onboarding drift
- delete TASKLIST.md and docs/design/handoff.md; the handoff content lives in README.md - move docs/BOOTSTRAP-OWNERSHIP.md to docs/history/ and index it in docs/history/README.md - point ONBOARD.md, docs/BORROW.md and the ticked TODO.md item at the new path - ONBOARD.md section 9 no longer mentions TASKLIST.md - ONBOARD.md section 3 and 4 markers are now `// ...` (line comments lost their colon) verified from a clean build: make clean, make (fixpoint 376903 words), the section 0-7 commands (section 4 after the marker fix), and the rejection suites. make check was started but did not complete (stopped before it finished).
Diffstat (limited to 'docs/BOOTSTRAP-OWNERSHIP.md')
-rw-r--r--docs/BOOTSTRAP-OWNERSHIP.md183
1 files changed, 0 insertions, 183 deletions
diff --git a/docs/BOOTSTRAP-OWNERSHIP.md b/docs/BOOTSTRAP-OWNERSHIP.md
deleted file mode 100644
index 6cc61ce..0000000
--- a/docs/BOOTSTRAP-OWNERSHIP.md
+++ /dev/null
@@ -1,183 +0,0 @@
-# the bootstrap did not compile: owned values could not be moved between locals
-
-status: resolved. the analyzer gained the two rules proposed below and a clearer
-recast diagnostic, and `make genome` reaches its fixpoint from a clean checkout.
-see "resolution" at the end; the rest of this note is the record of the problem.
-affects: `make genome` and the `fixpoint` check
-see also: `docs/BORROW.md`
-
-## summary
-
-`make` bootstraps the compiler with `zc0` compiling `src/runtime/rt.zc`,
-`src/core/core.zc` and `src/compiler/compiler.zc` into a stage-0 image, stage-0
-becoming `build/zy0`, and `zy0` compiling the same three files again into the
-genome. the second compilation runs with lifetime checking enabled and rejects
-the compiler's own source. `mkgenome` then read the diagnostics as if they
-were an image:
-
-```
-mkgenome: input is not a genome for this map
-```
-
-the real output of `zy0` is a diagnostic list, not an image. nothing after the
-fixpoint can run, so a checkout with no prebuilt genome cannot be built.
-
-this is a completeness gap in the ownership rules, not a soundness failure. the
-checker is refusing correct programs; it is not accepting unsafe ones. the
-practical cost is that the compiler can no longer compile itself.
-
-## the affected code
-
-`methdef` in `src/compiler/compiler.zc` reassembles a typed generic method's
-token list. it saves the output stream state, redirects the stream at a scratch
-buffer, renders the return type into it, splits the result into tokens, builds a
-replacement buffer that interleaves the new tokens with the old ones, and hands
-the replacement back in place of the original.
-
-```zc
-word output = rt.outbuf, grow = rt.outgrow, buffer = rt.new(4096), return_tokens, joined;
-rt.outmode = 2; rt.outbuf = m*buffer; rt.outn = 0; rt.outcap = 4095; rt.outgrow = 0;
-tprint(rty); if (rtag) rt.put_s(".u"); buffer[rt.outn] = 0;
-rt.outmode = mode; rt.outbuf = output; rt.outn = length; rt.outcap = capacity; rt.outgrow = grow;
-return_tokens = tokens(buffer);
-...
-joined = rt.new((r[0] + return_tokens[0]) * 3 + 1);
-...
-word previous = m*r; r = d*joined; rt.free(return_tokens); rt.free(buffer); rt.free(previous);
-```
-
-three separate moves are needed here, and each one hits a different limit:
-
-1. lend the local scratch buffer to the output stream, then keep using it
-2. replace `r`, an owned local, with `joined`, another owned local
-3. store the local block into the global code-block list
-
-## what the checker reports
-
-```
-zc: line 5571: buffer: used after it was given away; owner buffer was transferred on line 5570 (declared on line 5569)
-zc: line 5573: buffer: used after it was given away; owner buffer was transferred on line 5570 (declared on line 5569)
-zc: line 5589: r: is an owned object and can't be copied into another variable
-zc: line 5589: r: still owns an object; overwriting it would leak it
-zc: line 5589: buffer: used after it was given away; owner buffer was transferred on line 5570 (declared on line 5569)
-zc: line 5590: joined: d object was never given away
-zc: line 10572: only a named owned object can be recast
-zc: line 10575: only a named owned object can be recast
-zc: 8 errors
-```
-
-the line numbers are into the concatenation, not into `compiler.zc`. subtract
-1401 lines: 5570 is `rt.outbuf = m*buffer;`, 5589 is the `previous` line, and
-10572 and 10575 are `rt.code_blocks = m*block;` in `syntax_resident`.
-
-## what was tried
-
-| change | result |
-|---|---|
-| `rt.outbuf = buffer;` — drop the `m*` | still reported as a transfer; any owned value assigned into a global transfers, and a second diagnostic appeared at the following line |
-| move the scratch buffer to a module global `rbuf`, assign it plainly, borrow it into `rt.outbuf` | accepted; removes five of the eight errors. this is the existing idiom, see `sgb` at `sig_attach` and `ltxt` in the literal pool |
-| `word previous = m*r; r = d*joined; rt.free(previous);` unchanged | `initial declarations still cannot copy an owned local`, per BORROW.md line 30 |
-| declare `previous` in the block's declaration list, then `previous = m*r;` | `r: is an owned object and can't be copied into another variable` |
-| `rt.free(r); r = joined;` — the documented free-then-replace idiom | `joined: is an owned object and can't be copied into another variable`, and `r` is then reported as given away at its next use |
-
-## is this a borrow checker issue
-
-yes, and it is worth separating two kinds of gap.
-
-**a move between two locals has no syntax.** the accepted moves in the
-compiler's own source all originate at a call:
-
-```zc
-word p = m*rt.new(capacity); // src/compiler/compiler.zc:644
-buf = m*inc_open(full); // src/compiler/compiler.zc:4632
-dgbuf = chk_resize(dgbuf, dgpos, cap) // src/compiler/compiler.zc:401
-rt.free(d*buf); // src/compiler/compiler.zc:4447
-```
-
-an owned value already held in a local can be disposed with `d*`, but it cannot
-be handed to another local, and it cannot replace one. the free-then-replace rule
-in BORROW.md line 30 covers `rt.free(buffer); buffer = replacement;` — but only
-when `replacement` arrives from a call. with a local on the right-hand side the
-same rule rejects the assignment and then reports the destination as given away.
-
-the replacement built here is a genuine local: it is written field by field and
-then has to become `r`. no current spelling expresses that. the shortest
-workaround is to route the swap through a call that takes both pointers and
-returns the survivor, so the move originates at a call result.
-
-**a local cannot be lent to a global.** the output stream is global state.
-redirecting it at a local scratch buffer is a borrow, not a transfer, but every
-spelling is a transfer:
-
-- `rt.outbuf = m*buffer;` transfers, so every later use of `buffer` is a use
- after transfer
-- `rt.outbuf = buffer;` also transfers
-- the three working redirects in the compiler all assign a global or a pointer
- derived from one — `rt.outbuf = dgbuf + dgpos + 5;`, `rt.outbuf = sgb;`,
- `rt.outbuf = ift + at;`, `rt.outbuf = ltxt;`
-
-so the scratch buffer has to live at module scope. that is workable here, and it
-is what the module-global `rbuf` change adopts, but it costs something the
-local version did not: the buffer is shared state for the duration of the call,
-so any reentrancy through `methdef` would collide, and the checker cannot see
-that the borrow ends at the restore.
-
-## proposal
-
-in rough order of value:
-
-1. allow a move into an existing local when the source is an owned local and the
- destination's previous owner is released in the same statement list, generalizing
- the free-then-replace rule beyond call results. this alone resolves the
- `methdef` reassembly.
-2. allow a global to hold a borrow of a local for a bounded region, so an output
- stream can be pointed at a scratch buffer without a module-level variable. a
- scoped-borrow rule on assignment into a global field would do it, and it removes
- the reentrancy hazard that the module-global workaround introduces.
-3. give `rt.code_blocks = m*block;` a supported spelling. the error, `only a named
- owned object can be recast`, does not describe what is wrong: `block` is named and
- owned. it is used again at `here = block + 3;` two lines later, which is the real
- constraint, and the diagnostic should say so and point at the later use.
-
-none of these accept a program that is currently accepted unsafely. each widens
-the set of programs the checker can describe. item 3 is also a message bug
-independent of the rule change: a wrong diagnostic on a correct-looking transfer.
-
-## reproducing
-
-`src/tools/bootstrap-check.sh` rebuilds the two bootstrap stages and prints the
-compiler's diagnostics on its own source instead of the word dump. it exits
-through `mkgenome` when the image is malformed, which is the same failure `make`
-reports.
-
-```
-bash src/tools/bootstrap-check.sh
-```
-
-a passing run prints `image ok: <n> bytes`.
-
-## resolution
-
-the analyzer (`src/compiler/compiler.zc`) now accepts the three spellings:
-
-1. **moves into a released owner.** `rt.free(r); r = joined;` moves `joined` into `r`
- when `r` was released first and `joined` is an owner of the same block or a deeper
- one. `joined` is retired by the move. a destination that still owns something is
- still rejected (`still owns an object; overwriting it would leak it`), and so is a
- copy of an owner into a fresh declaration.
-2. **bounded lends to globals.** `g = m*x;` with `x` an owned local lends `x` to `g`:
- `x` keeps ownership and stays usable, and the lend ends when the same block assigns
- `g` again. giving `x` away, returning, breaking or leaving the block while the lend
- is open is an error (`is lent to global g; assign the global again before ...`). the
- lend list is per function and per block depth (`lend_add`, `lend_clear`,
- `lend_exit`), so a restore inside a nested branch does not end a lend made outside it.
-3. **recasts of plain locals.** `rt.code_blocks = m*block;` failed because `block` came
- from `chk_resize`, which returns collector-managed storage, so `block` never owned
- anything. the compiler now writes `rt.code_blocks = block;`, and a recast of a plain
- local reports `block: isn't an owned object, so there is no ownership to recast`
- instead of the old message.
-
-`methdef` uses the lend for its scratch buffer and the move for the joined token list.
-the regressions are `tests/programs/ownmove.zc` and `tests/errors/lend_*.zc`,
-`recast_plain.zc` and `move_live_owner.zc`.
-