Every citation of a moved document now resolves from where it is written

This commit is contained in:
Joseph Ferano 2026-09-14 07:12:27 +07:00
parent eb641cf8eb
commit 9d5689ffa2
53 changed files with 203 additions and 203 deletions

200
NEXT.md
View File

@ -7,7 +7,7 @@ of taste, and it is worth your reading of it: `runtime/flan_rt.c:70-86` allocate
`restart-case` that offers it, on that frame's own stack**, so the runtime cannot push one on a program's
behalf. Offering `use-value` at a division would mean an alloca plus a push and pop emitted at every
division in every checked build — which is the identical cost `lib/prelude.ml:70-76` already refuses, in
prose and on the record, for indexing. `HANDOFF-arith.md` is the full write-up.
prose and on the record, for indexing. `docs/handoffs/HANDOFF-arith.md` is the full write-up.
So the question back to you: **is signal-and-die-with-a-location enough for arithmetic, the way it is for
`BoundsError`, or do you want `saturate` on the cast arm** — which is the one case where clamping is
@ -26,14 +26,14 @@ Nothing is running and nothing is half-built. Read this first.
`spike/x86/survey.sh` builds every program in `test/programs` and every probe in `spike/x86` both
ways and compares **stdout, stderr and exit status**: **97 MATCH, 0 DIFFER, 0 refused by name.** That
is up from 41/1/41 at the start of the day. Conditions, bounds checks, `Fnval`'s indirection cell and
`--x86 --dev` all landed. `DISCUSS.md` items 16, 17 and 18 are the three reports.
`--x86 --dev` all landed. `docs/DISCUSS.md` items 16, 17 and 18 are the three reports.
**`HANDOFF-x86-rt.md` at the root has the remaining seven items in order.** Item 1 is the substantial
**`docs/handoffs/HANDOFF-x86-rt.md` at the root has the remaining seven items in order.** Item 1 is the substantial
one and is the reason the backend exists:
> **A redefinition emitter in `lib/x86.ml`** — the counterpart to `Emit.redefinition`, producing a
> `.so`. Until it exists the backend builds whole programs and cannot serve a single `C-c C-c`, which
> is where `DISCUSS.md` item 13's ~21ms of a 35ms redefinition actually lives.
> is where `docs/DISCUSS.md` item 13's ~21ms of a 35ms redefinition actually lives.
**And the constraint the last lane found, which is the thing not to rediscover:** publishing a cell an
LLVM-built module can store into puts an edge on `lib/x86.ml`'s own licence that *"a dev build is
@ -78,7 +78,7 @@ request and `Session.eval` commits below the checker.
## Queued, nothing blocked on a decision
1. `HANDOFF-x86-rt.md`'s seven items, item 1 first.
1. `docs/handoffs/HANDOFF-x86-rt.md`'s seven items, item 1 first.
2. `text_codepoints_loading` and `textures_image_processing` — ordinary porting now that
`slice-from-ptr` exists.
3. Generic structs and `$n` array lengths — decided, unblocked, sections below. **Stopped once
@ -101,7 +101,7 @@ something that is not memory. Their old entries stand; do not schedule either.
**Built.** `flan_slice_promise_error` is its own function in `runtime/flan_rt.c`, shaped like the two
beside it — it signals `BoundsError`, walks the handlers, offers the break loop, and only then falls
through to a message and a status. The sentence names the promise and the number that broke it, and a
second line says what is *not* checked. See [`BUILT.md`](BUILT.md), "`slice-from-ptr` refuses in its
second line says what is *not* checked. See [`docs/BUILT.md`](docs/BUILT.md), "`slice-from-ptr` refuses in its
own words". The original entry follows.
Small, and known rough at the time it shipped. `(slice-from-ptr p n)` with a **computed** negative
@ -127,10 +127,10 @@ is the whole reason it was left. Say what the caller promised and what was passe
**Named here because it is not on the list below and would otherwise be discovered late.**
Finishing items 1-6 below gives a second **whole-program** compiler. `--x86` is refused with `--dev`
today, deliberately, and `DISCUSS.md` item 16 says it has no cells. So at the end of that list the
today, deliberately, and `docs/DISCUSS.md` item 16 says it has no cells. So at the end of that list the
backend can build `sand.flan` and cannot serve a single `C-c C-c`.
What the backend was for is `DISCUSS.md` item 13's measurement: `llc` and `ld` are **about 21ms of a
What the backend was for is `docs/DISCUSS.md` item 13's measurement: `llc` and `ld` are **about 21ms of a
35ms redefinition**, and removing them is the only thing that touches that number. Collecting it means
emitting a **redefinition module** — one function, compiled and installed into a running process
through an indirection cell — which is what `Build.shared` does on the LLVM path.
@ -150,13 +150,13 @@ Concretely, and none of it is scoped yet:
- **DWARF.** `flan dev --debug` exists and `--x86` refuses `--debug`. A dev backend that cannot be
debugged is a narrower tool than the one it replaces.
- **Then measure, because the whole case rests on it.** If a hand-written backend does not actually
beat 35ms end to end, the argument for it was wrong and that is worth knowing. `DISCUSS.md` item 13
beat 35ms end to end, the argument for it was wrong and that is worth knowing. `docs/DISCUSS.md` item 13
says the backend is decided at step 4 on a measurement taken at step 3, and this is that
measurement finally being takeable.
## The backend, in the order that finishes it
`DISCUSS.md` item 16 is the report and its verdict is the sequencing: *"the wiring is done and it was
`docs/DISCUSS.md` item 16 is the report and its verdict is the sequencing: *"the wiring is done and it was
the easy half. What is left is conditions, and the measurement moved them from 'first obstacle' to
'the only obstacle'."* 40 of 111 programs lower today; the other 40 are refused by name.
@ -211,7 +211,7 @@ question 4 seriously now that there are two backends that can disagree.
policy that distinguishes it from `two_process`. `HANDOFF-f1.md` is gone with it.
- ~~**The memcheck half of the registry**~~ — done. `flan_arena_proc`'s `free-all` issues memcheck's
`MAKE_MEM_UNDEFINED` over the arena, vendored rather than included; it is a control in
`test_valgrind.ml` now, and `BUILT.md` carries the cost. The registry answer and the memcheck
`test_valgrind.ml` now, and `docs/BUILT.md` carries the cost. The registry answer and the memcheck
answer stay two claims.
## Not in the freeze, because it is already decided and unblocked
@ -236,7 +236,7 @@ everything below it is the standing queue and the decision record.
- **`pause` marking, both halves.** `C-u` before an eval marks the innermost form, `C-u C-u` the
top-level one.
- **`spy-num`.** A watch slot keeps count, min, max, last, mean, windowed to the editor's last tick.
- **Frame rollback**, `PORTING.md` Tier 1 item 6. `restore` goes in the `continue` clause, not a defer.
- **Frame rollback**, `docs/PORTING.md` Tier 1 item 6. `restore` goes in the `continue` clause, not a defer.
- **The dev allocation registry is finished.** `M-x flan-inspect-address`, `M-x flan-allocations`,
`M-x flan-leaks`.
- **15 raylib examples**, an idiomatic layer over the bindings, and `vendor/raylib/vector.flan`.
@ -252,7 +252,7 @@ everything below it is the standing queue and the decision record.
~~**`worktree-agent-a859480edc827ab73` — macros importable from a package.**~~ **Finished.** The suite
is green, the acceptance wiring asserts both halves of the rule, and the dev loop is covered. See
BUILT.md, "A package may declare a macro, and its name is the package's".
docs/BUILT.md, "A package may declare a macro, and its name is the package's".
The finding is worth keeping: the old refusal claimed collecting a package's macros needed a second
import resolver at the Form level. It did not. The file being compiled is parsed before `Load` runs
@ -260,7 +260,7 @@ too, so no shape of the feature could have left import resolution where it was
forms now and uses the one resolver that always existed.
~~One gap left, and it is older than this work: **`C-x C-e` expands no macros at all.**~~ **Done**
see [`BUILT.md`](BUILT.md), "`C-x C-e` expands too, and a declaration is not an expression". It was
see [`docs/BUILT.md`](docs/BUILT.md), "`C-x C-e` expands too, and a declaration is not an expression". It was
the wrap `Parse.decl` already had, and the decision it was waiting on came out as: an expression that
expands to a declaration is **refused by name**, in `Parse.expr`'s head dispatch, so a `defn` nested
anywhere in the expansion and a `defvar` typed by hand get the same sentence. The spin refusal fires
@ -269,7 +269,7 @@ expands is the prelude's macros and the imported packages' — and, since the la
the file's own as well.
Two things left behind it, neither this lane's. ~~**A session expands no macro the buffer itself
declares**~~ — **done**, see [`BUILT.md`](BUILT.md), "A session expands the buffer's own macros". The
declares**~~ — **done**, see [`docs/BUILT.md`](docs/BUILT.md), "A session expands the buffer's own macros". The
session seeds its file's own `defmacro`s in `Session.create`, from the same read that produced
`decls`, and an evaluated `defmacro` joins the set so the next evaluation can call it — the shape
`defn` already has. Re-reading the file was the alternative and was refused: it would put
@ -303,7 +303,7 @@ the Emacs side". **Superseded** — the registry lane built that properly. Delet
## For siam-farmer
`PORTING.md`'s verdict is unconditional now: the game's state is **fixed arrays with counts**, so it
`docs/PORTING.md`'s verdict is unconditional now: the game's state is **fixed arrays with counts**, so it
fits in `defvar` globals, nothing is move-only, and generics is off its critical path. Tier 0 and Tier 1
items 4, 5 and 6 are all done. Nothing blocks writing it.
@ -407,7 +407,7 @@ allocation**, which is Odin's `Small_Array` and a good fit for a game that refus
(defvar enemies (Small-Array 64 Enemy))
```
`PORTING.md`'s recommendation for the game's state was fixed arrays with counts held in `defvar` globals, kept
`docs/PORTING.md`'s recommendation for the game's state was fixed arrays with counts held in `defvar` globals, kept
deliberately away from `Vec` so nothing is move-only. `(Small-Array $n $t)` is that pattern with a type behind it
instead of two variables kept in step by hand. That is the argument for building this, and it is stronger than
"Odin has it".
@ -436,7 +436,7 @@ separate question and should be answered deliberately rather than falling out of
## Decided 2026-09-13: generics by monomorphisation, checked abstractly, with `where` predicates
The spike answered it (`SPIKE-GENERICS.md`, on `worktree-agent-afcd2406f3660629b`): **it runs**, the whole feature
The spike answered it (`docs/SPIKE-GENERICS.md`, on `worktree-agent-afcd2406f3660629b`): **it runs**, the whole feature
is `lib/check.ml` and nothing else in `lib/`, and instantiation is under the noise floor at 1.8ms whole-program
re-check. The bill is `llc`+`ld` at **+1.7ms per extra body**, so a generic used at three types adds ~7ms to a
redefinition. `plan.org` is updated; what follows is the decision and the reasoning that does not belong there.
@ -615,13 +615,13 @@ why they are here and not in a binding list.
check now accepts an enum where the header says `int`, symmetrically, and still refuses
anything that is not four bytes. `Camera3D.projection` is a `CameraProjection` again and the
`rl/camera-projection` helper is gone; `.projection :perspective` resolves at the construction
site, so the keyword half of the problem went away with it. See BUILT.md.
site, so the keyword half of the problem went away with it. See docs/BUILT.md.
3. ~~**The header check does not reach `defconst` or `defenum`.**~~ **Closed.** It reaches both.
`bindings` gained `enum`, `const` and `constant` lines that say what a Flan constant is called in
C; every mapped name is compared by value, and a name the mapping cannot find, a rule that
reaches nothing, and a `defenum` with no line at all are each reported rather than skipped. All
eight raylib enums and all 16 `ConfigFlags` bits check out against 5.5. See BUILT.md.
eight raylib enums and all 16 `ConfigFlags` bits check out against 5.5. See docs/BUILT.md.
4. ~~**raymath is `static inline`, so there is no symbol to bind.**~~ **Closed for the vector half.**
`Clamp`, `Vector2Add`, `Remap` and the rest exist only in the header and `declare-c` has nothing
@ -662,7 +662,7 @@ tip themselves. A lane that did not notice would produce plausible work against
**Built.** All seven items landed: the `eval_expr` pause path, `flan-dev--eval` sending the position,
`flan-eval-defun` taking `C-u`, the overlay and its face, `test/programs/dev-pause.flan`, the `test_dev.ml` block and
the `emacs/test-flan-dev.el` one. `HANDOFF-f2.md` is deleted; the reasoning is in `BUILT.md`, "A breakpoint is a
the `emacs/test-flan-dev.el` one. `HANDOFF-f2.md` is deleted; the reasoning is in `docs/BUILT.md`, "A breakpoint is a
function call, and the editor only says where".
### ~~From `HANDOFF-f1.md`~~ — the socket flake is fixed, and everything that followed it is done
@ -730,7 +730,7 @@ actually worth having. The timers are 30s, down from a minute, because the build
### ~~From `HANDOFF-f3.md` — the watch is built; two tests are not~~
`spy-num` landed and `PORTING.md` Tier 1 item 5 is closed. The `spy` half already existed — the finding that
`spy-num` landed and `docs/PORTING.md` Tier 1 item 5 is closed. The `spy` half already existed — the finding that
reframed the task is that the table, the agent commands, the buffer and the ghost text were all there at `344e571`,
and only the numeric accumulator was missing. A slot keeps count, min, max, last and mean; the window is since the
editor's last tick rather than cumulative, because cumulative min/max reach the session's extremes within seconds of
@ -745,19 +745,19 @@ stopped program does not sample, so an epoch-aware reader would blank the watch
is exactly when the numbers are being read. What was actually wrong was in the editor — `flan-watch--tick` sent
`:reset t` five times a second at a program that could not answer it — and it is now guarded on `flan-dev--stopped`,
with the read still going out every tick. `emacs/test-flan-watch.el` pins both halves. `HANDOFF-f3.md` is deleted;
the reasoning is in `BUILT.md`, "A hot loop keeps five numbers, and the window is the editor's".
the reasoning is in `docs/BUILT.md`, "A hot loop keeps five numbers, and the window is the editor's".
### Not started: five more raylib core examples
Ten are in `examples/`. Five more were queued and the lane was stopped before it wrote anything, so there is nothing
to pick up — only the brief. Pick for what they exercise of the bindings rather than for how they look, since the
point of the corpus is finding gaps in `vendor/raylib/raylib.flan`. Upstream is
`vendor/raylib/web/raylib-5.5/examples/core/`. `PORTING.md` §1's rule binds: a raylib function on a real per-frame
`vendor/raylib/web/raylib-5.5/examples/core/`. `docs/PORTING.md` §1's rule binds: a raylib function on a real per-frame
path is hand-written and header-checked, not left to the opt-in `FLAN_RAYLIB_H` import, because the default build
has no `FLAN_RAYLIB_H` and still has to draw. An enum value has to be added by hand whatever happens — the importer
generates functions and only functions.
### Landed: frame rollback, `PORTING.md` Tier 1 item 6
### Landed: frame rollback, `docs/PORTING.md` Tier 1 item 6
`test/programs/frame-rollback.flan`, with three acceptance rows beside `bounds-condition.flan`'s (plain, `-O0`,
dev). Nothing was added to the language, which was the premise: `snapshot` is `(set grid-backup grid)` and
@ -820,7 +820,7 @@ written in `vendor/raylib` now, and have not been.
## ~~Queued: a restart is not a transaction, and the docs must say so~~ — **landed**
See [`BUILT.md`](BUILT.md), "A restart is not a transaction". **This entry was stale** — all three places already
See [`docs/BUILT.md`](docs/BUILT.md), "A restart is not a transaction". **This entry was stale** — all three places already
carried the note when it was re-read: `conditions.org` under *Gotchas*, `spec-conditions.md` §5, and
`web/index.html`'s restart gotcha list, which was rewritten in place rather than gaining a second bullet beside the
existing one. Nothing was left to write.
@ -841,7 +841,7 @@ skip a frame and carry on rather than die — exactly the case where a non-idemp
## ~~Queued: a dev-build allocation registry~~ — **landed; one item left, and it is not a registry item**
Items 1 to 5 are built and the test is written. `BUILT.md`'s *"An address answers with a type"* is the account of the
Items 1 to 5 are built and the test is written. `docs/BUILT.md`'s *"An address answers with a type"* is the account of the
table, the note and the inspector's pointer arm; *"An address you have in your hand"* is the account of the reader —
the address root, the breakdown, the leak report, and what "at exit" turned out to mean.
@ -861,7 +861,7 @@ do not need the registry. This is for plain structs, `Vec`, `Map` and pool stora
**On cost, as built.** One insert per allocation, always on in a dev build, no opt-out — the author's instruction,
followed literally. Nothing was built per-region, no range recording, no per-allocator opt-out. Revisit only if a real
program shows a problem, and `BUILT.md` still names **two** places a release build is not quite free: the readers
program shows a problem, and `docs/BUILT.md` still names **two** places a release build is not quite free: the readers
added since are functions nothing in a release build calls, and the exit report is registered by `atexit` from inside
`flan_dev_reg_enable` rather than by a file-scope destructor, precisely so that it is not a third.
@ -871,14 +871,14 @@ Three things, in order. The first two are one line each and unblock a real game.
**1. `DrawTexturePro` is not bound — DONE.** It is `draw-texture-pro` in `vendor/raylib/raylib.flan` now, hand-written
beside `draw-texture-rec` and read off a raylib header rather than remembered. `image-from-image` and `window-ready?`
went in with it. The rule the gap exposed is written down in `BUILT.md` and `PORTING.md` §1: *a raylib function on a
went in with it. The rule the gap exposed is written down in `docs/BUILT.md` and `docs/PORTING.md` §1: *a raylib function on a
game's per-frame path is hand-written and header-checked, not left to the opt-in import* — the import widens the
surface and must not be load-bearing, because the default build has no `FLAN_RAYLIB_H` and still has to draw.
**2. `Key` has no `left-shift` — DONE.** `left-shift 340`, and nothing else: `PORTING.md` §5 checked every other enum
**2. `Key` has no `left-shift` — DONE.** `left-shift 340`, and nothing else: `docs/PORTING.md` §5 checked every other enum
value the game touches and they were all already right.
**1. `DrawTexturePro` — done, and not by a hand-written line.** It was the one true blocker for `siam-farmer`
(see `PORTING.md`: every tile in both implementations goes through it, and neither `DrawTextureRec` nor
(see `docs/PORTING.md`: every tile in both implementations goes through it, and neither `DrawTextureRec` nor
`DrawTextureEx` substitutes). It was reachable only through the opt-in `FLAN_RAYLIB_H` import. The bindings are
committed now, so `rl/draw-texture-pro` is in `vendor/raylib/generated.flan` and a default build has it. **Nothing
should add it by hand** — a second `declare-c` for the same C symbol is refused for the whole program.
@ -892,7 +892,7 @@ still a hand edit: the importer generates functions and only functions, so no `d
the break loop, and tail into the old `flan_bounds_fail` message and status only if nothing answered. `Vec`'s two
checks are plumbed the same way, since `(at v i)` and `(at arr i)` are one form in the source.
**No restart is established at the failing index**, and `BUILT.md` has the argument: `retry` exists for allocation and
**No restart is established at the failing index**, and `docs/BUILT.md` has the argument: `retry` exists for allocation and
for files because those attempts are *repeatable*, and nothing a handler can do makes index 51 valid for a length-50
array. `use-value` for the index would cost every indexing operation a restart frame and buy a silently different
element. What answers a bad index is the restart the program already had — a frame loop's `continue`, `sand.flan`'s
@ -912,22 +912,22 @@ resumes with the session intact.
different kind of failure — the region the container lived in was released — and there is no frame to go back to that
would not read freed memory. The map path was left alone rather than converted half-way.
### What is left on `PORTING.md`'s list
### What is left on `docs/PORTING.md`'s list
Tier 0 is finished, and so is item 5. **The watch for a running program is done** — the `spy` half was already
built (a pushed table, the watch buffer, inline ghost text) and the `spy-num` half landed 2026-09-13: a hot-loop slot
keeps count/min/max/last/mean, the write path does no formatting, and the window is since the editor's last tick
rather than cumulative, which is a deliberate divergence from `watch.clj` argued in `BUILT.md`, "A hot loop keeps
rather than cumulative, which is a deliberate divergence from `watch.clj` argued in `docs/BUILT.md`, "A hot loop keeps
five numbers, and the window is the editor's". **Item 6 landed the same day**:
`test/programs/frame-rollback.flan` is the worked example — `snapshot` at the top of the frame, `restore` in the
`continue` clause — and `bounds-condition.flan`'s half-written abandoned frame is the thought it finishes. That was
the last Tier 1 item anyone was going to move. Items 7 (`drop`), 8 (generics) and 9 (`(read-edn T bytes)`) are still
on that list and still deferred with reasons written beside each; none is a blocker for this game.
**What `PORTING.md` says NOT to build, with evidence:** escaping closures (one capture site, fixed by one parameter),
**What `docs/PORTING.md` says NOT to build, with evidence:** escaping closures (one capture site, fixed by one parameter),
`Handle`/pools, `Result`/`try`, `handler-case`, `loop`/`recur` and tail calls, user allocators, structural typing —
**none has a customer in that code**. (`Handle` and the pool were built anyway, and on the other reason: they are the
gate on classes. The finding stands and is why they were built small — see [`BUILT.md`](BUILT.md). `loop`/`recur` was
gate on classes. The finding stands and is why they were built small — see [`docs/BUILT.md`](docs/BUILT.md). `loop`/`recur` was
built too, and the finding stands there as well: what it is not is **tail calls**, which are still not built and still
have no customer.) And **generics is not the blocker** there either: the element-changing maps are
five-line load-time loops. That last one hangs on a design decision the report states flatly — whether the game's
@ -940,9 +940,9 @@ byte-identical between 5.5 and the vendored 6.0.
Both lanes committed their main work and died on trailing polish; both are merged and the suite is green.
- ~~**`pause` marking from Emacs was not built.**~~ **Built**, both halves. See `BUILT.md`, "A breakpoint is a
- ~~**`pause` marking from Emacs was not built.**~~ **Built**, both halves. See `docs/BUILT.md`, "A breakpoint is a
function call, and the editor only says where".
- ~~**Ghost text** for the watch window — values shown inline at the code they belong to — is noted and not designed.~~ **Built.** See `BUILT.md`, "Ghost text finds its anchor in the buffer, not in the table".
- ~~**Ghost text** for the watch window — values shown inline at the code they belong to — is noted and not designed.~~ **Built.** See `docs/BUILT.md`, "Ghost text finds its anchor in the buffer, not in the table".
- `tools/unit-return.py` is re-runnable; run it over any `.flan` file a lane wrote before the conversion landed.
# Where this is
@ -951,11 +951,11 @@ Both lanes committed their main work and died on trailing polish; both are merge
**Branch `dev-loop`, 199 commits, working tree clean, `dune test` green.**
**[`DISCUSS.md`](DISCUSS.md) is what has been *asked* and not answered** — open questions with the repo context that
**[`docs/DISCUSS.md`](docs/DISCUSS.md) is what has been *asked* and not answered** — open questions with the repo context that
bears on each, so an investigation starts from what exists. Nothing in it is a decision or a task; when one becomes
either, it moves here.
**`NEXT.md` is what is left. [`BUILT.md`](BUILT.md) is why the existing parts are the shape they are** — the reload
**`NEXT.md` is what is left. [`docs/BUILT.md`](docs/BUILT.md) is why the existing parts are the shape they are** — the reload
primitive, cells, the agent, the session, the daemon, the Emacs client, conditions, the FFI shim, the layout, and the
order it all got built in. This file was half build log until it was split; do not let it become one again. When a
track here finishes, its explanation moves there and its entry here goes away.
@ -994,7 +994,7 @@ The expander is written and the exit criterion plan.org set for milestone 5 is m
unchanged. Running `test/programs/macro-unless.flan` means the compiler built a shared object, `dlopen`ed it into
itself and called a Flan function to find out what `(unless c a b)` means.
The full explanation is in [`BUILT.md`](BUILT.md), "Macros: the compiler dlopens the program". Four things worth
The full explanation is in [`docs/BUILT.md`](docs/BUILT.md), "Macros: the compiler dlopens the program". Four things worth
knowing before touching any of it, because each cost something to find:
- **A call inside a quasiquote is output, not a compile-order dependency.** A macro body that *calls* another macro
@ -1017,10 +1017,10 @@ difference being a cached `.so`; and a hello-world carries eight bytes of it, be
### Landed — a C header is read, so a binding is checked instead of trusted
`lib/cimport.ml`, `lib/cjson.ml`, a `headers` file beside `link`. Full reasoning in `BUILT.md`, "The header is read
now"; DISCUSS.md item 6 is rewritten down to the two decisions left, both the author's.
`lib/cimport.ml`, `lib/cjson.ml`, a `headers` file beside `link`. Full reasoning in `docs/BUILT.md`, "The header is read
now"; docs/DISCUSS.md item 6 is rewritten down to the two decisions left, both the author's.
The gap closed is the one `BUILT.md` recorded as *trusted*: `declare-c` generates the wrapper, the typedefs and the
The gap closed is the one `docs/BUILT.md` recorded as *trusted*: `declare-c` generates the wrapper, the typedefs and the
prototype from one declaration, so they agree with each other by construction and only the library could disagree —
and nothing had a second opinion to disagree with. Now clang is asked for a JSON AST dump of the header (shelled out,
never libclang — the dependency plan.org rejected; Zig has since left it too, for Aro) and both halves are compared
@ -1030,14 +1030,14 @@ against it.
all 172 hand-written `declare-c` agree exactly.** Against the 5.1-dev header also installed on this machine, ten real
differences — nine functions that version lacks and one that gained a parameter — so picking the wrong header is
loud. Both comparisons run at build time and stop the build; verified by permuting `Texture2D` and by putting `f64`
where raylib says `float`, which is the hazard `BUILT.md` names and says only a test can catch.
where raylib says `float`, which is the hazard `docs/BUILT.md` names and says only a test can catch.
**Costs, measured, because they decide the remaining question.** Release build +4ms warm — `Reach.link` already drops
a wrapper nothing reachable calls, confirmed on the wasm32 case it exists for with 256 extra declarations in play.
Redefinition 31.0ms → 46.5ms. Dev build +333ms cold, once per session, since `Build.shared` compiles no C. Reading the
header is cached (64ms → 17ms), keyed like the object cache; the cache was built against a measurement, not a guess.
**Re-measured, and the 15.5ms was misattributed** — see BUILT.md, "Where that 15.5ms actually is". A `C-c C-c` reads
**Re-measured, and the 15.5ms was misattributed** — see docs/BUILT.md, "Where that 15.5ms actually is". A `C-c C-c` reads
no header: `Session.eval` puts the forms through `Load`, and forms with no `(import …)` in them touch no package. The
15.5ms is `flan reload`'s, and `flan reload` is a fresh process — ~14.5ms of it is session startup and ~4ms of *that*
is the header. What a redefinition really pays for an imported package is **+3.6ms per eval** in `Check` and in
@ -1057,7 +1057,7 @@ Worth knowing before touching it:
172 are already bound by hand and left alone. Widening the binding is a `defstruct`, not a list edit.
- **No `defstruct` is generated, and that is load-bearing.** Generate them and the header becomes the authority on
layout, and checking the package's layouts against it would be comparing the header with itself — which is exactly
why `BUILT.md` rejected a `_Static_assert` as circular. Keeping them hand-written is what makes the check a second
why `docs/BUILT.md` rejected a `_Static_assert` as circular. Keeping them hand-written is what makes the check a second
source.
- **A refusal is a demotion, not a drop** — Zig's `failDecl`, which `Load.refuse_hidden` already implemented for
`main`. `rl/get-gamepad-name` is a name that exists, cannot be had, and says why at the use site.
@ -1066,7 +1066,7 @@ Worth knowing before touching it:
- **`test/headers/sample.h`** is the importer's table — one function per decision, committed, no raylib needed. The
raylib acceptance case skips without `FLAN_RAYLIB_H`; that one does not.
Two things that are *not* done, and are 6a and 6b in DISCUSS.md: whether the header stays a build-time read or becomes
Two things that are *not* done, and are 6a and 6b in docs/DISCUSS.md: whether the header stays a build-time read or becomes
a committed generator (`flan import-c` already prints the lines, so it costs nothing more to switch), and whether the
172 hand-written lines migrate. Neither is blocked on correctness. The argument for the first is weaker than it
looked — committing the generated lines would save ~4ms of session startup and none of the +3.6ms per redefinition,
@ -1081,7 +1081,7 @@ sibling of a call that worked did not — by excluding them and hand-writing the
### Landed 2026-09-12 — six tracks, one session
Six agents in parallel worktrees. Kept short on purpose; the reasoning that outlives the change is in `BUILT.md` or in
Six agents in parallel worktrees. Kept short on purpose; the reasoning that outlives the change is in `docs/BUILT.md` or in
the commit that made it.
1. **`nth` removed**, an alias of `at` that was asymmetric — `check.ml` aliased them but `parse.ml` and
@ -1140,7 +1140,7 @@ is written down rather than left out; see the unsettled list under the build ord
The critical path, and the thing NEXT.md said was the only one standing between this and writing a game. Steps 1, 2 and
3 of the build order below are struck; `Map` is step 4 and is untouched. `Allocator` is a builtin opaque type and
needed nothing from milestone 5, which was the whole bet. Three amendments to a **frozen** `spec-memory.md`, made
deliberately and stated as amendments in [`BUILT.md`](BUILT.md): `free-all` is retain-capacity with `arena-destroy`
deliberately and stated as amendments in [`docs/BUILT.md`](docs/BUILT.md): `free-all` is retain-capacity with `arena-destroy`
beside it; `context/allocator` is a dynamic variable rather than a literal calling-convention parameter; and the `Vec`
header is six words in every build rather than four in release. One addition the spec does not have: a budget on the
allocator, because `retry` needs a handler that can make the *same* request succeed.
@ -1150,7 +1150,7 @@ allocator, because `retry` needs a handler that can make the *same* request succ
`--sanitize` is a build flag beside `--debug`; `dune build --root . @sanitize` builds twenty-eight programs twice, plain
and sanitized, and compares output and exit status. Its own alias and not `dune test`, because the sweep is about nine
minutes. The checked sweep is **clean**. How ASan and UBSan reach a language whose IR is written by hand, and why the
flag does not force `-O0` when `--debug` does, is in [`BUILT.md`](BUILT.md).
flag does not force `-O0` when `--debug` does, is in [`docs/BUILT.md`](docs/BUILT.md).
Two defects came out of it, both found by reading rather than by the tools, both fixed with a regression case:
`flan_bytes_to_i64`/`flan_bytes_to_f64` clamped a slice length with `(size_t)n` and so read 63 or 511 bytes off the end
@ -1182,7 +1182,7 @@ it *would* have written.
control that must report: index 3 of a `Vec` with len 2 and cap 4, with `--track-origins` naming the
`aligned_alloc` in `flan_vec_push`. Two more controls pin a heap overrun and a padded `Map` key. `test/valgrind.supp`
holds **no suppressions** — nothing false came up to suppress. Details, and the measured fact that memcheck catches
0 of `bounds.flan`'s 6 cases where ASan catches 3, in [`BUILT.md`](BUILT.md).
0 of `bounds.flan`'s 6 cases where ASan catches 3, in [`docs/BUILT.md`](docs/BUILT.md).
**The arena half of the hole is closed; the interior half cannot be.** `free-all` is retain-capacity, so the pages
stay and `malloc` never learns the storage died — but memcheck can be told directly, and now is: `flan_arena_proc`
@ -1192,7 +1192,7 @@ it *would* have written.
control. Cost outside valgrind is five no-op instructions plus the request block — 23 on the `free-all` path only,
about 1ns per reset — and the macro is vendored rather than `#include`d because the machine that runs the sweep
has `valgrind` and not `valgrind-devel`, so a guarded include would silently compile to nothing exactly here. See
[`BUILT.md`](BUILT.md).
[`docs/BUILT.md`](docs/BUILT.md).
**Interior overruns remain invisible**, for a structural reason no client request fixes — an arena is one `malloc`,
and the `Map`'s `keys | values | hashes | scratch` is one allocation too, so "probe overrun at high load" is not
@ -1253,7 +1253,7 @@ Either way it is an amendment to a **frozen** `spec-conditions.md`, not a gap in
program, at that program's next frame boundary. Verified against sand: an unsaved buffer edit to `game-draw`, and 240
consecutive frames drew it.
Steps 1, 2 and 3 are done — see *The reload primitive* in `BUILT.md`. A list of top-level forms can be recompiled and installed
Steps 1, 2 and 3 are done — see *The reload primitive* in `docs/BUILT.md`. A list of top-level forms can be recompiled and installed
into a running process; call sites compiled before they existed follow them, and a `defn` or `defvar` the process was
never built with can be added and then redefined again. That is the whole of `C-c C-c`, minus an editor: sand.flan takes
a redefinition over a socket and installs it between frames.
@ -1263,7 +1263,7 @@ running process was built with, and speaks a protocol an editor can talk to.
Milestone 4 is done: **sand.flan builds, links raylib and runs**, and its simulation has a headless acceptance case that
runs on the `dune test` path at `-O0` and `-O2`. Milestones 2 and 3 are behind it (`calc-me.flan` compiles and runs; the
interpreter was dropped — open decision #7, settled — see "Why there is no interpreter" in `BUILT.md`).
interpreter was dropped — open decision #7, settled — see "Why there is no interpreter" in `docs/BUILT.md`).
```
reader ✅ → parse ✅ → load ✅ → check ✅ → emit ✅ → clang ✅
@ -1322,7 +1322,7 @@ $ flan run sand.flan # a window, 120 fps, hold space
Five questions were put and answered in one sitting. Each is a decision, not a preference — build against them, and
reopen one only with a reason rather than a taste.
~~**1. Assets are embedded at compile time, one file or one directory.**~~ **Built**`(embed "p")`, `(embed "p" string)`, `(embed-dir "d")`. See BUILT.md, "Assets are baked in". Odin's answer, and the reason it is the right
~~**1. Assets are embedded at compile time, one file or one directory.**~~ **Built**`(embed "p")`, `(embed "p" string)`, `(embed-dir "d")`. See docs/BUILT.md, "Assets are baked in". Odin's answer, and the reason it is the right
one here: it is a *compiler* feature, so it needs no build flags, no linker arguments and no per-target packaging, and
it works identically on desktop and web. That matters more here than it does for Odin, because `Load` gives link flags
only to directory packages — the single file doing `(rl/load-texture "brush.png")` is structurally the one file with
@ -1331,7 +1331,7 @@ no link channel, which is what stopped the web lane from inventing a flag. Embed
`--preload-file` stays available later for assets that should load lazily rather than be baked in; the `@web` link
line already carries it if wanted.
~~**2. Reading a file works everywhere; writing is desktop-only and signals on web.**~~ **Built**`barf` on the web signals `FileError` with reason `file-unsupported`, and `test/test_web.ml` runs it under node rather than asserting the artifact's shape. See BUILT.md, "slurp, barf, and the two ways they fail". Odin stubs its whole file API on
~~**2. Reading a file works everywhere; writing is desktop-only and signals on web.**~~ **Built**`barf` on the web signals `FileError` with reason `file-unsupported`, and `test/test_web.ml` runs it under node rather than asserting the artifact's shape. See docs/BUILT.md, "slurp, barf, and the two ways they fail". Odin stubs its whole file API on
js/wasm — every operation returns `.Unsupported`, and `core/os/file_js.odin`'s own comment says the stubs exist only
so importing `core:os` "panics cleanly". Take the restriction and not the mechanism. **Flan has no conditional
compilation** — nothing in `parse.ml` or `check.ml` reads the target — so "isolate this code to desktop" is not
@ -1340,7 +1340,7 @@ it is how a save file disappears with nothing said. So `barf` on web signals a c
program decides. This is the language having something Odin does not; use it. Per-package target isolation, if a
whole desktop-only package is ever wanted, is the `@native`/`@wasi`/`@web` link-line tagging the web lane built.
**3. Build the shadow stack.** ~~Not yet built.~~ **Built**, both halves — see BUILT.md. Kept here as the decision it
**3. Build the shadow stack.** ~~Not yet built.~~ **Built**, both halves — see docs/BUILT.md. Kept here as the decision it
was, with the measurement it asked for: +33% on call-heavy code over globals for the frames, +61% with the slot table,
and 0.06% of a 60fps frame.
@ -1371,7 +1371,7 @@ length is not known until the file is read and therefore cannot exist before an
refusal names the new one. `:keys` kept its colon — it names no field, so leaving it alone is what lets the dot mean
exactly one thing. 681 labels across 45 `.flan` files including `vendor/`, plus 94 more in the Flan embedded in
`lib/prelude.ml` and the tests. `Map` is now free to take `{:key value}` without colliding with struct literals. See
BUILT.md, "The colon belongs to keys".
docs/BUILT.md, "The colon belongs to keys".
**What it left for the Emacs lane, both verified.** `render.ml` still *prints* a struct with colons, deliberately:
`emacs/flan-inspect.el:165` parses that output and hard-codes the colon when it reads a field out, so the printer
@ -1408,7 +1408,7 @@ reason there is no collector.
**Globals in the break buffer — built.** One section under the stack, holding the union of the globals every frame on
the current stack references, each entry annotated with the frames that touch it and ordered by the innermost one. It
is `(:op "globals")` in `dev.ml` and `flan-cnr--insert-globals` in the break buffer. See BUILT.md, "Globals of a
is `(:op "globals")` in `dev.ml` and `flan-cnr--insert-globals` in the break buffer. See docs/BUILT.md, "Globals of a
stopped stack". ~~The one hole left open — the redefinition check is a fingerprint over a body's *slots*, so a new body
that names different globals while binding the same locals is not caught.~~ **Closed**: `Reach.ref_fingerprint` is a
second fingerprint over the set of globals a body names, carried beside the slot one in `%fninfo` and checked the same
@ -1428,7 +1428,7 @@ mid-edit in another buffer.
that nothing starts on managed classes "until ordinary `struct`, `Handle`, and reload semantics are working". All
three now hold: structs work fully; reload works with one known hole (a changed signature is refused rather than
versioned); and `(Handle T)` and `(Pool T)` exist, with the enumeration primitive `migrate-instances` was blocked on.
See [`BUILT.md`](BUILT.md), "`(Handle T)` and the pool, which is what a stale reference answers with".
See [`docs/BUILT.md`](docs/BUILT.md), "`(Handle T)` and the pool, which is what a stale reference answers with".
It was not an incidental precondition. `migrate-instances` has to *enumerate* live instances, and a pool behind a
generational `(Handle T)` gives that by construction while a world arena and an owned region do not. plan.org presents
@ -1482,11 +1482,11 @@ acquisitions against releases at that boundary and report what is still held at
annotation, nothing running at a distance — it does not change how code is written, it reports when something was
forgotten.
## ~~Before the batch below: read `DISCUSS.md`'s "NEXT SESSION STARTS HERE"~~ — **answered, and being unwound**
## ~~Before the batch below: read `docs/DISCUSS.md`'s "NEXT SESSION STARTS HERE"~~ — **answered, and being unwound**
The architectural question raised at the end of 2026-09-12 — **putting the compiler inside the running program's
process** — was researched (`DISCUSS.md` §14), then built: `flan dev` is one binary and one process, and the editor
socket did not move. See "One process" in [`BUILT.md`](BUILT.md).
process** — was researched (`docs/DISCUSS.md` §14), then built: `flan dev` is one binary and one process, and the editor
socket did not move. See "One process" in [`docs/BUILT.md`](docs/BUILT.md).
What it reopened is now being deleted one piece at a time, each with its own green run:
@ -1516,7 +1516,7 @@ whether an in-process JIT or a hand-written backend is needed at all.
## ~~Queued: a second tier of the standard library, after macros~~ — **landed**
See [`BUILT.md`](BUILT.md), "The prelude's second tier". The diagnosis here was right and the prelude had 44
See [`docs/BUILT.md`](docs/BUILT.md), "The prelude's second tier". The diagnosis here was right and the prelude had 44
allocation-free functions because there was nothing to allocate from; there are 24 more now, and the "Refused, by
name" block at the foot of `prelude.ml` is down from eight entries to four, each with a *different* reason rather
than the one shared sentence.
@ -1534,14 +1534,14 @@ Tests: `programs/strings.flan`, `programs/format.flan`, `programs/algorithms.fla
them wants a language decision.
- ~~**`Map` keys and values.**~~ **Iteration is built**`flan_map_next` and the `map-next!` builtin, exactly the
shape this described. See [`BUILT.md`](BUILT.md), "`map-next!`, the one thing a Map could not do".
shape this described. See [`docs/BUILT.md`](docs/BUILT.md), "`map-next!`, the one thing a Map could not do".
`map-keys`/`map-values` as *prelude functions* stay refused, and the reason is now generics rather than the
iterator: a `defn` has to name its types and `(defn map-keys [m {K V}] (Vec K))` has no `K`. The loop is three
lines at the call site, where `K` is known.
- ~~**`map`, `filter`, `reduce`, and a sort taking a comparator.**~~ **All four are in the prelude.** The diagnosis
was right and is now evidenced: **function values, not generics** — they arrived with no generics at all. See
[`BUILT.md`](BUILT.md), "Function values, with no capture". They are one copy per element type (i32 and f32), which
[`docs/BUILT.md`](docs/BUILT.md), "Function values, with no capture". They are one copy per element type (i32 and f32), which
is the half generics would remove, and a `map` that *changes* the element type is the one shape that did not come
with them — one copy per ordered pair of types rather than per type.
@ -1563,7 +1563,7 @@ them wants a language decision.
Two smaller findings, both written down beside the code that ran into them:
- ~~**The prelude is never macro-expanded.**~~ **Fixed, and the diagnosis above was wrong in both halves** — see
[`BUILT.md`](BUILT.md), "A prelude function may call a prelude macro". The prelude *does* reach the expander; the
[`docs/BUILT.md`](docs/BUILT.md), "A prelude function may call a prelude macro". The prelude *does* reach the expander; the
arity error came from the `Check.program` *inside* `Macro.compile`, where expansion is off. It is a cycle and not
an ordering — a macro module is compiled from the prelude — so moving the prepend would have changed nothing. What
fixed it is `Macro.reduce`, which makes the prelude smaller for that one build, plus dropping the prelude's own
@ -1642,7 +1642,7 @@ destination is always honest about what it is.
Drop Clojure's `:eduction` branch — that is the pass-around case, and the one part that would need runtime machinery.
**Done.** See *`into`, which fuses at compile time because it is a macro* in [`BUILT.md`](BUILT.md). It is a prelude
**Done.** See *`into`, which fuses at compile time because it is a macro* in [`docs/BUILT.md`](docs/BUILT.md). It is a prelude
`defmacro` over a plain `defn` that walks the transforms in reverse, and all four of the macro limits bit without
blocking anything: the three refusals are names nothing defines, `into-wrap` uses only special forms so
`Macro.reduce` does not drop it, the quasiquotes are all single-level, and `into` lives in the prelude because a
@ -1664,7 +1664,7 @@ this, and it does not exist.
## ~~Queued: `loop`/`recur` (the return type is done)~~ — **landed**
~~**1. A `defn` must always state its return type, and unit is written `()`.**~~ **Done.** See *The return type is
the slot, and unit is `()`* in [`BUILT.md`](BUILT.md).
the slot, and unit is `()`* in [`docs/BUILT.md`](docs/BUILT.md).
The slot after the parameters is unconditionally a type, the pre-pass that collected a file's type names is gone
along with `is_type_form`, `qualified_type`, `types_in`, `declared_types` and `prelude_types`, and `Parse.decl` no
@ -1690,10 +1690,10 @@ python3 tools/unit-return.py --in-html web/index.html
`-v` logs every `defn` it saw and what it decided; `--check` changes nothing. It is re-runnable, and on this tree it
reports exactly six sites, all in `test_flan.ml`, which spell the refused forms *on purpose* so the refusals can be
tested. Read the diff of every non-`.flan` file — BUILT.md lists what the script can and cannot see.
tested. Read the diff of every non-`.flan` file — docs/BUILT.md lists what the script can and cannot see.
~~**2. `loop` and `recur`.**~~ **Done.** See *`loop` and `recur`, and why `recur` is better than tail calls and not
only cheaper* in [`BUILT.md`](BUILT.md). `emit.ml` is untouched: a loop is a `let`, a `While` whose condition is
only cheaper* in [`docs/BUILT.md`](docs/BUILT.md). `emit.ml` is untouched: a loop is a `let`, a `While` whose condition is
`true`, and two jumps, and the barrier question `recur` asks is the one labelled `break` already answered.
Three things the plan did not anticipate, each written up there. **Tail position is a permission that is withdrawn**
@ -1718,7 +1718,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
backtrace line never carried it, and `Dev.locals` compared slot counts and nothing else — four of the five
hand-offs were never written, and printing both sides of the comparison could not have found it because there was
no comparison. The mechanism was sound and stayed: it hashes slot *names* as well as types, so it does see a
rename. See BUILT.md, "Locals of a stopped frame".
rename. See docs/BUILT.md, "Locals of a stopped frame".
2. ~~**The colon-to-dot change.**~~ **Done**, and `Map` is unblocked: `{:key value}` is free. The sweep is
`tools/colon-to-dot.py`, kept rather than thrown away, because the lanes that branched before it wrote Flan in the
@ -1726,7 +1726,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
`--in-strings` for a `test/*.ml` that embeds Flan.
3. ~~**`Map`, and the `defer` relaxation.**~~ **Both done.** See *`(Map K V)`, which is Odin's map* and *`defer` may be
written in a `let`* in [`BUILT.md`](BUILT.md). The `defer` relaxation **amends `spec-memory.md`**, which said
written in a `let`* in [`docs/BUILT.md`](docs/BUILT.md). The `defer` relaxation **amends `spec-memory.md`**, which said
`(defer (free v))` for a `let`-bound `v` was not expressible; it is now. `Map` restricts one thing the spec does not:
a fixed array is a key only when its elements compare bytewise, so an array of structs or of strings is refused by
name. The measured answer to "is this another Python dict" is six times quicker cache-resident and *slower* at a
@ -1743,7 +1743,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
5. ~~**Union values**~~, then the macro expander, then `Result`/`try`. Promoted above `Handle` on the author's call —
macros are the thing most worth wanting, and unions are the only thing between here and them.
**Union values are done.** See *Unions, and the tag they carry* in [`BUILT.md`](BUILT.md). The diagnosis was right:
**Union values are done.** See *Unions, and the tag they carry* in [`docs/BUILT.md`](docs/BUILT.md). The diagnosis was right:
`Option` is a two-case union wearing a special coat, so `Tast.arm`'s `acase` and `binds` already were union shape
and `check_match` grew a second subject rather than a second path. A union is `Types.Named` exactly as a struct is,
so every path that merely carries a type learned nothing.
@ -1761,7 +1761,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
needs no import and no `load.ml` change. Verified by declaring one there and matching it from a program.
**Macros landed on top of this** and needed no `load.ml` change for `Form`, exactly as this said. See
[`BUILT.md`](BUILT.md), "Macros: the compiler dlopens the program", and the short list of what is left of them
[`docs/BUILT.md`](docs/BUILT.md), "Macros: the compiler dlopens the program", and the short list of what is left of them
below.
Macros are what buy `with-drawing` and `with-mode-2d` over raylib's begin/end pairs, the hiccup DSL if a JS backend
@ -1778,7 +1778,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
a C-shaped callback and somewhere to put a `flan_allocator`, neither of which is a type parameter.
6. ~~**`Handle` and the pool.**~~ **Built.** A reference to something that can die, that reports that it died rather
than silently resolving to whatever reused the slot. See [`BUILT.md`](BUILT.md), "`(Handle T)` and the pool, which
than silently resolving to whatever reused the slot. See [`docs/BUILT.md`](docs/BUILT.md), "`(Handle T)` and the pool, which
is what a stale reference answers with". A handle is one `i64` — slot index low, generation high — so it copies,
zeroes and compares like an integer and owns nothing; a live slot's generation is odd, which makes a zeroed handle
resolve to nothing rather than to slot 0; and a generation that would wrap retires its slot instead, because "rare"
@ -1800,7 +1800,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
is consumed, because there is no borrowing parameter — a pre-existing `Vec` gap, not a pool one.
7. ~~**`break` and `continue`, with loop labels.**~~ **Built.** Labels are Odin's in the head position, both blockers
are answered, and the refusals name the construct they refuse for. See BUILT.md, "`break` and `continue`, and the
are answered, and the refusals name the construct they refuse for. See docs/BUILT.md, "`break` and `continue`, and the
rule that replaced a blanket refusal". What was settled in conversation before it was built, kept:
**Labels, Odin-style but in the head position.** A keyword names a loop and `break` takes it:
@ -1822,7 +1822,7 @@ run one lane at a time; item 4 is disjoint and runs alongside any of them.
hang.
8. ~~**Errors: a structured value with spans and notes, and more than one per compile.**~~ **Built.** See
*An error is a value, and there is more than one of them* in [`BUILT.md`](BUILT.md). `Loc.Error` carries a
*An error is a value, and there is more than one of them* in [`docs/BUILT.md`](docs/BUILT.md). `Loc.Error` carries a
`diag` — a stable `kind`, a *span*, `notes` that each have their own span and severity, and the macro expansion
the error came out of — and `flan check`/`flan build` print the source line with the offending span underlined,
in the GNU format `compilation-mode` already parses. No editor work was needed and none was done.
@ -1880,7 +1880,7 @@ debug tracking allocator, which is the leak safety net and a good candidate when
## Decided in discussion — the array constructor and the module system
**`(array 4 rl/Vector2)` makes a fixed array; `[4 T]` stays the type syntax. Built** — see BUILT.md, "`(array 4 rl/Vector2)`, and the one position with no type slot". The problem it solved: a `let`
**`(array 4 rl/Vector2)` makes a fixed array; `[4 T]` stays the type syntax. Built** — see docs/BUILT.md, "`(array 4 rl/Vector2)`, and the one position with no type slot". The problem it solved: a `let`
binding takes no type, so `(let [pts [4 rl/Vector2]] ...)` reads `[4 rl/Vector2]` as a two-element array *literal* and
fails with *unknown name rl/Vector2*. It cost 32 hand-written `Vector2`s in one raylib example.
@ -1938,7 +1938,7 @@ package to *mark* a name private, and adding one means a parser change.
## Decided in discussion — three more, two now built
~~**A watch window, ported from the author's Clojure one.**~~ **Built.** See `BUILT.md`, "The watch window, and why
~~**A watch window, ported from the author's Clojure one.**~~ **Built.** See `docs/BUILT.md`, "The watch window, and why
it is the only listing that is pushed", and `emacs/MANUAL.md` under "Looking at values". Three of the original's
decisions were kept unchanged — the program decides what is shown, the request is async, and the paint is
`replace-buffer-contents` so point and scroll survive every tick.
@ -1954,7 +1954,7 @@ could not have done at any price.
**What was not built, deliberately: the `(watch "hp" hp)` form.** Scalars work today through `declare-c` against four
runtime entry points, which needs no compiler change at all. A struct or a slice needs a compile-time walk over its
type — one arm in `check.ml` beside `print`, which `BUILT.md` writes out in full — and that file is held by another
type — one arm in `check.ml` beside `print`, which `docs/BUILT.md` writes out in full — and that file is held by another
lane, so it was left alone rather than reached into.
~~**Ghost text is gated on that same arm**, which is the finding worth keeping.~~ **Wrong, and ghost text is built.**
@ -1965,10 +1965,10 @@ conclusion did not follow: the call site is in the *buffer*, and the name in the
so the anchor is searched for rather than reported. Nothing new is asked of the daemon. The two open questions
`flan-watch.el` recorded are answered rather than solved — overlays are replaced wholesale every repaint, so an edit
has nothing to invalidate, and a watch in a loop shows the last value written exactly as the buffer does, because
every better answer is the query UI this design exists to avoid. See `BUILT.md`, "Ghost text finds its anchor in the
every better answer is the query UI this design exists to avoid. See `docs/BUILT.md`, "Ghost text finds its anchor in the
buffer, not in the table".
~~**The inspector gets a second way to start: an address and a type.**~~ **Built.** See `BUILT.md`, "Two ways to root
~~**The inspector gets a second way to start: an address and a type.**~~ **Built.** See `docs/BUILT.md`, "Two ways to root
a walk, and why neither subsumes the other". It went in as a frame and a slot *index* rather than an address and a
type — the daemon holds both and an index is the thing the listing can hand back, while an address is not something an
editor should be holding. The one prediction that did not survive contact: `l` crossing between the two modes was
@ -2042,11 +2042,11 @@ Ranked by how often they were hit, top two first because they are walls rather t
list head — because `(Key n)` is a *value* now, and putting `Key` in `types` would make a body starting with one
be eaten as a return type.
3. ~~**`break` is not implemented.**~~ **Built, with `continue` and loop labels.** Both blockers are answered: the
`in_frames` rule became a relative one rather than a blanket one, and `Tast.While` grew a latch. See BUILT.md,
`in_frames` rule became a relative one rather than a blanket one, and `Tast.While` grew a latch. See docs/BUILT.md,
"`break` and `continue`, and the rule that replaced a blanket refusal".
4. ~~**A `let` binding takes no type annotation**~~ — still true, and **no longer the blocker it was**: `(array 4
rl/Vector2)` is built and is the answer to the case that raised it. The reasoning below is kept because it is what
chose between the three surfaces, and the first of them is not what was taken — see BUILT.md, "`(array 4
chose between the three surfaces, and the first of them is not what was taken — see docs/BUILT.md, "`(array 4
rl/Vector2)`, and the one position with no type slot". The original entry:
A fixed array is either a top-level `defvar` or a literal with
@ -2089,11 +2089,11 @@ array with a struct element, a 2-D struct array, and `[N string]` as both `defco
### The web target: what it does not reach yet
`flan build --target=web` works, a raylib example builds unchanged and `test/test_web.ml` is green — see BUILT.md,
`flan build --target=web` works, a raylib example builds unchanged and `test/test_web.ml` is green — see docs/BUILT.md,
"The browser is the third target", for the mechanism and why asyncify rather than `emscripten_set_main_loop`. Four
things it does not cover.
~~**1. `sand.flan` has no web build, and the cause is one missing `#include`.**~~ **Built. It opens.** See BUILT.md,
~~**1. `sand.flan` has no web build, and the cause is one missing `#include`.**~~ **Built. It opens.** See docs/BUILT.md,
"sand.flan in a browser", for the whole of it. Three summary lines, because the diagnosis below was right about the
structure and wrong about the cause:
@ -2147,7 +2147,7 @@ The original text follows. `sand.flan` does `(rl/load-texture "brush.png")` agai
**3. Nothing has been opened in a browser.** *Still true, and now it is the only thing left between here and
"someone played with it".* `sand.flan` builds for the web, the module carries asyncify, raylib's GL imports and
brush.png's own bytes whole, and `node sand.js` gets as far as `glfwInit` before dying on `window is not defined`
which proves the module is live and proves nothing about the canvas. BUILT.md carries the exact commands to serve and
which proves the module is live and proves nothing about the canvas. docs/BUILT.md carries the exact commands to serve and
open it, and the list of what only a human will discover: whether it paints, whether the audio round trip through
MEMFS survives, and the canvas size. The `until` loop never exits on the web, so none of `main`'s `defer`s run —
expected, and worth knowing before reading anything into it.
@ -2167,7 +2167,7 @@ message.
### `break`, and why it was not built — **it is built now**
Kept as written, because everything in it held and the two blockers at the end are the two things the build had to
rule on. Both are ruled on in BUILT.md, "`break` and `continue`, and the rule that replaced a blanket refusal":
rule on. Both are ruled on in docs/BUILT.md, "`break` and `continue`, and the rule that replaced a blanket refusal":
the `in_frames` precedent was replaced by a barrier on the loop stack, which refuses a *crossing* rather than
everything, and `Tast.While` grew the latch. The original note:
@ -2197,7 +2197,7 @@ plan.org's single line on it (831) names a `for` the language does not have and
1. ~~**Allocators, then `Vec` and `Map`.**~~ **Steps 1, 2 and 3 are done** — the allocator, the arena, `(Vec T)`,
`StorageExhausted` and `retry`. `Map` is step 4 and is what is left of this item. See *Allocators, `(Vec T)` and
`StorageExhausted`* in [`BUILT.md`](BUILT.md) for the shape, the three amendments to a frozen `spec-memory.md` and
`StorageExhausted`* in [`docs/BUILT.md`](docs/BUILT.md) for the shape, the three amendments to a frozen `spec-memory.md` and
the one addition. The claim below held: **`Vec` does not need generics** — that was wrong and is worth un-learning: Odin's containers are compiler
builtins over a *type-erased* runtime (`base/runtime/dynamic_array_internal.odin`), where `$T` appears only in thin
wrappers producing `size_of`/`align_of` at the call site, and per-key hash and equality are compiler-emitted
@ -2237,7 +2237,7 @@ plan.org's single line on it (831) names a `for` the language does not have and
**Steps 1, 2 and 3 are built; 4 to 8 are what is left.** The reasoning is kept because it is what the remaining steps
rest on, and because the escape it describes was tested rather than assumed — see *Allocators, `(Vec T)` and
`StorageExhausted`* in [`BUILT.md`](BUILT.md).
`StorageExhausted`* in [`docs/BUILT.md`](docs/BUILT.md).
The dependency nobody had written down, and the reason it looked worse than it is. `spec-memory.md` defines an
allocator as "a procedure plus an opaque data pointer" — a function value. `check.ml` refuses function values four
@ -2280,7 +2280,7 @@ expander last, on 6's unions.
~~1. **`Allocator` and the arena.**~~ **Done.** The builtin opaque type, the four operations with `size` and `align`,
the capability set read off the allocator value, `with-allocator`, `context/allocator`, `context/temp` and the epoch
counter. `free-all` was decided as retain-capacity with `arena-destroy` beside it; the context is a dynamic variable
rather than a literal calling-convention parameter; both are stated as amendments in `BUILT.md`. A user-written
rather than a literal calling-convention parameter; both are stated as amendments in `docs/BUILT.md`. A user-written
allocator is refused by name with milestone 5 as the reason.
~~2. **`(Vec T)`**~~ **Done**, over the type-erased runtime, with `push`, `reserve`, `at`, `len`, `as-slice`, `free` and
@ -2340,7 +2340,7 @@ expander last, on 6's unions.
not the next thing to build.
- ~~**The operation table may be one operation short.**~~ **Decided.** `free-all` is retain-capacity and
`arena-destroy` hands the pages back — two names rather than the mode parameter, so the table the spec froze at four
operations did not grow. `BUILT.md` states it as the amendment it is.
operations did not grow. `docs/BUILT.md` states it as the amendment it is.
- **The `Vec` header is six words in release too, and should not stay that way.** The 32-byte layout the spec fixes is
blocked on one thing: a redefinition module is built by `llc` and `ld` against a host built separately, and nothing
makes the two agree on a struct size. Give the reload path a way to carry the build flags and this falls out.
@ -2372,7 +2372,7 @@ expander last, on 6's unions.
- ~~`web/examples/breakdemo.out` is stale and `check.sh` fails on it.~~ **Fixed.** Commit `4a6a8fa` made the break
banner number its restarts and the `.out` was never repinned. Nothing had to drive the socket in the end:
`check.sh` already builds this one `--dev` and runs it under `timeout 5`, keeping what it printed before it
stopped, so the repin was the `.out` plus the two prose copies of the banner — `web/index.html` and `BUILT.md` —
stopped, so the repin was the `.out` plus the two prose copies of the banner — `web/index.html` and `docs/BUILT.md` —
and a sentence on the page saying what the numbers are for, since a restart is taken by position.
- ~~A shadowed restart is offered and cannot be taken.~~ **Fixed.** A restart is taken by *position* now:
@ -2483,7 +2483,7 @@ memcheck sweep (`@valgrind`), whose alarm is looser at 5400s because memcheck is
now exists** — `Session.render_locals` is exactly that thunk, rooted at an address the program supplies — so what is
left is the first: keep the pointer, and give the agent a verb that hands it back. The type is already known: it is
the `condition_name` the break loop reports, which `layout` already resolves.
- **The type identity is settled, and it is the qualified name**`layout` is in, see BUILT.md. `Load` qualifies
- **The type identity is settled, and it is the qualified name**`layout` is in, see docs/BUILT.md. `Load` qualifies
every declaration at import, so the names in `Tast.structs` are a flat namespace where two packages' `Missing` are
`a/Missing` and `b/Missing`; a bare name is refused with the candidates rather than resolved. `condition` inherits
it for free: the string the break loop already reports *is* that name, because `Emit.struct_name_of` writes
@ -2491,7 +2491,7 @@ memcheck sweep (`@valgrind`), whose alarm is looser at 5400s because memcheck is
reads is not qualified by anything.
- ~~**`(:op "backtrace")` is blocked** on frame metadata.~~ **Built**, and not out of DWARF: decision 3's shadow
stack carries the name and the location on the frame itself, so a backtrace needs no debug information at all. See
BUILT.md, "The shadow stack, and `backtrace`", for what it costs. **Locals landed with it** — the pointer-rooted
docs/BUILT.md, "The shadow stack, and `backtrace`", for what it costs. **Locals landed with it** — the pointer-rooted
render thunk turned out to be `Render.render` over a `Deref` of a slot's address, and one new arm in the backend.
See "Locals of a stopped frame" for the four things it refuses. Restart source locations and arity are still blocked — `flan_restart` carries `prev`, `name_id`, `name` and `namelen`, so both need a new
field in the frame, which means the compiler emitting it.
@ -2614,7 +2614,7 @@ left of the item is visibility, which is listed above and needs a marker the par
## Macros — landed; what is left of them
**The expander works and `unless` is a prelude `defmacro`.** How all of it fits together is in
[`BUILT.md`](BUILT.md), "Macros: the compiler dlopens the program" — the image format, the thunk ABI, why quasiquote
[`docs/BUILT.md`](docs/BUILT.md), "Macros: the compiler dlopens the program" — the image format, the thunk ABI, why quasiquote
runs before the walk, the two different ways expansion fails to terminate, `-linkall`, and the three cost numbers.
What follows is only the part that is still missing.
@ -2684,7 +2684,7 @@ tests assert on the reason, not just on the failure.
## Handoff: the shadow stack lane, stopped mid-repair
Two commits landed and are green: the shadow stack with `(:op "backtrace")`, and `(:op "locals" :frame N)`. See
BUILT.md's two new sections for the design and the measurements. A third commit was **half-built and its own test
docs/BUILT.md's two new sections for the design and the measurements. A third commit was **half-built and its own test
left red** on purpose; **it is finished now** — see the struck item 1 above — and the rest of this section is kept
because the parts of it that were true are still worth having.
@ -2698,7 +2698,7 @@ wrote.
**Not obvious from the diff.** Two things cost a day between them. The linked-list frame beat an array-with-a-stack-
pointer on both benchmarks, which is the opposite of what the escaping-alloca argument predicts, and the measurement
that first said otherwise was comparing a 40-frame binary with a 600-frame one; every number in BUILT.md is now a
that first said otherwise was comparing a 40-frame binary with a 600-frame one; every number in docs/BUILT.md is now a
minimum of nine runs for that reason. And `redefinition`'s transient rule (`m.nstr = 0`) silently stops every module
carrying a string literal from ever being unloaded — the frame descriptors go through their own counter, `m.nfi`, for
that reason, and a locals thunk passes `~retains:false` because everything it emits is memcpy'd into the result buffer.

View File

@ -109,7 +109,7 @@ let sanitize_flag = "--sanitize"
let two_process_flag = "--two-process"
(* The hand-written x86-64 backend (lib/x86.ml) instead of LLVM. The dev
backend from DISCUSS.md item 15, off by default and named explicitly:
backend from docs/DISCUSS.md item 15, off by default and named explicitly:
LLVM stays the release path and the default one. It covers a subset of the
IR and refuses the rest by name, so a build that succeeds is one it really
compiled. *)

View File

@ -860,7 +860,7 @@ load a file out of `resources/` and neither file is in this tree.
- `DotGothic16-Regular.ttf` is SIL OFL 1.1 and therefore redistributable, but it is 2 MB,
and whether a compiler repository whose only binary asset is a 1 KB PNG should grow a 2 MB
font is a call about the repository rather than about this port. Left to the author;
`HANDOFF-raylib-ports.md` carries it as the open question. The example looks for it under
`handoffs/HANDOFF-raylib-ports.md` carries it as the open question. The example looks for it under
`examples/resources/`, says on screen when it is not there, and runs either way — raylib
answers a missing path with the default font, whose glyphs are ASCII, so the kana draw as
boxes and the program explains itself rather than looking broken.

View File

@ -2,7 +2,7 @@
Two examples, `text_codepoints_loading` and `textures_image_processing`, ported into
`examples/`. This file is the running record: what is done, what was decided without being
able to ask, and what is still open. `PORTING.md` has the findings that outlive the port —
able to ask, and what is still open. `../PORTING.md` has the findings that outlive the port —
its last section, "Porting the raylib examples — the last two" — and this file has the
state of the work.
@ -12,7 +12,7 @@ state of the work.
- [x] `examples/textures-image-processing.flan` — builds
- [x] `test/programs/raylib-codepoints.flan` and `test/programs/raylib-image-processing.flan`,
both in the acceptance table, both gated on libraylib, both run plain and at `-O0`
- [x] `PORTING.md` section for this round
- [x] `../PORTING.md` section for this round
- [x] `dune test --root .` green
Four commits, each one a working state:
@ -39,7 +39,7 @@ Four commits, each one a working state:
reported", *"a `(Ptr T)` where the header says `T *` and the hand-written line chose
something more specific"*. The function implements the enum-against-i32 arm and nothing
else. That missing arm is the only thing standing between this round's two refused
bindings and ordinary binding work — see `PORTING.md` §A.1 and §A.2, which have the
bindings and ordinary binding work — see `../PORTING.md` §A.1 and §A.2, which have the
exact declarations and the exact error. **Not built: it is a compiler change and the
freeze is on.** It is small and it is well specified, and it is the thing to do first if
these two ports are ever revisited.

View File

@ -6,9 +6,9 @@ newcomer at that. This is the plan and, below it, what actually happened.
## The plan
1. `git mv` the nine `HANDOFF-*.md` into `docs/handoffs/`, and `DISCUSS.md`,
`BUILT.md`, `PORTING.md`, `SPIKE-GENERICS.md`, `REFERENCES.md` and
`overview.md` into `docs/`. These are written once and read occasionally.
1. `git mv` the nine `HANDOFF-*.md` into `docs/handoffs/`, and `../DISCUSS.md`,
`../BUILT.md`, `../PORTING.md`, `../SPIKE-GENERICS.md`, `../REFERENCES.md` and
`../overview.md` into `docs/`. These are written once and read occasionally.
2. Leave `plan.org`, `spec-memory.md`, `spec-conditions.md`, `NEXT.md`, the
`.flan` scratch programs and the build files where they are: they are cited
by bare filename from source comments in dozens of places, and moving them

View File

@ -61,7 +61,7 @@ So a reference to a symbol the object does not define goes through the GOT. Meas
`call_flan`'s `` `Cell `` target loaded **one** level: `mov r11, cell@GOTPCREL(%rip)` then `call *%r11`. That
reads as "call through the cell" in a disassembly and in fact *calls the cell*. The GOT slot holds the cell's
**address**; reading what the cell holds is two loads, not one. Found by the segfault, not by reading — which is
DISCUSS.md item 15's claim, now with a sixth instance. `load_sym` is the fix and is the only helper that
../DISCUSS.md item 15's claim, now with a sixth instance. `load_sym` is the fix and is the only helper that
double-loads; `Lgot` gets it right for free because `lmem` already had the shape.
## Scope, deliberately narrower than `Emit.redefinition`

View File

@ -1,7 +1,7 @@
# Handoff — the x86 backend's last two correctness items
Branch: `dev-loop`, worktree `agent-af9064091c0602dc8`. Three commits plus this one; nothing is half-written and
nothing is reverted. The long-form report is **DISCUSS.md item 18**; this file is the operational version.
nothing is reverted. The long-form report is **../DISCUSS.md item 18**; this file is the operational version.
## 1. The `flan_vec_as_slice` answer — got it, and it is complete

View File

@ -3,11 +3,11 @@
This was the first brainstorm. It is kept for history and is **no longer
accurate**. Read instead:
- `plan.org` — the design, the build sequence, and the open decisions
- `spec-memory.md` — ownership, containers, places, generics, function values
- `spec-conditions.md` — conditions and restarts, operational semantics
- `sand.flan` — the first acceptance program
- `syntax-sketch.flan` — the syntax, annotated with the decisions above
- `../plan.org` — the design, the build sequence, and the open decisions
- `../spec-memory.md` — ownership, containers, places, generics, function values
- `../spec-conditions.md` — conditions and restarts, operational semantics
- `../sand.flan` — the first acceptance program
- `../syntax-sketch.flan` — the syntax, annotated with the decisions above
## What changed since

View File

@ -1,7 +1,7 @@
# Flan in Emacs
A manual for the editor side. It assumes you know Emacs and nothing about how
Flan's dev loop is built — if you want that, `BUILT.md` has it.
Flan's dev loop is built — if you want that, `docs/BUILT.md` has it.
The short version: you start a program, you keep it running, and you change it
while it runs. Everything below is a variation on that.

View File

@ -1409,7 +1409,7 @@ START and END, when given, are the region it came from, flashed on success.
PAUSE, when given, is (BEG . END): the bounds of the form inside CODE the
program should stop at. Only BEG goes on the wire the daemon matches it
against the location the reader attached to that form and END is what the
mark is drawn over here. Nothing is inserted in the buffer; see DISCUSS.md
mark is drawn over here. Nothing is inserted in the buffer; see docs/DISCUSS.md
§9."
(let ((reply
(flan-dev--request

View File

@ -55,7 +55,7 @@
;;
;; Scalars only, so far. A struct or a slice needs a compile-time walk over
;; its type — a `(watch "hp" hp)' form in the checker — and that is a file this
;; change does not own. See BUILT.md.
;; change does not own. See docs/BUILT.md.
;;; Code:
@ -533,7 +533,7 @@ Tears down both consumers. `flan-watch--drop' is the way to stop one of them."
;; Still genuinely blocked on the checker, and unchanged by any of this: a
;; **struct or a slice**. Rendering one is a compile-time walk over its type,
;; the runtime has scalar entry points only, and no amount of searching the
;; buffer produces a value the table does not hold. See BUILT.md.
;; buffer produces a value the table does not hold. See docs/BUILT.md.
(provide 'flan-watch)
;;; flan-watch.el ends here

View File

@ -811,7 +811,7 @@ is written instead — the real `message' call the real command makes."
;; ── Marking a form with (pause) ───────────────────────────────────────
;;
;; DISCUSS.md §9: `C-u' before an evaluation marks a form so the program
;; docs/DISCUSS.md §9: `C-u' before an evaluation marks a form so the program
;; stops when it runs, and the buffer is never edited — the position goes on
;; the wire beside the code and the daemon splices the call in after parsing.
;;

View File

@ -9,13 +9,13 @@
;;;; is the same struct going into the call that actually draws with it.
;;;;
;;;; The hundred buildings are a `defvar` of fixed arrays rather than a Vec:
;;;; a global cannot hold a Vec (PORTING.md §3) and does not need to here,
;;;; a global cannot hold a Vec (docs/PORTING.md §3) and does not need to here,
;;;; because the count is a constant in the C too. `(array n T)` is the zeroed
;;;; fixed array, and the C's `= { 0 }` is exactly that.
;;;;
;;;; get-random-value comes from the generated half of the bindings. It is on
;;;; no frame's path — it runs once, before the loop — and its Flan face is
;;;; the C one, so PORTING.md §1's rule does not reach it.
;;;; the C one, so docs/PORTING.md §1's rule does not reach it.
;;;;
;;;; One deliberate difference from the C: `(- camera.rotation 1.0)` and the
;;;; clamp after it are written with the prelude's `clamp` macro rather than

View File

@ -2,7 +2,7 @@
;;;;
;;;; examples/core/core_scissor_test.c. Needed begin-scissor-mode and
;;;; end-scissor-mode, now hand-written in vendor/raylib/raylib.flan: they are
;;;; a begin/end pair inside a frame, which is the class PORTING.md §1 says
;;;; a begin/end pair inside a frame, which is the class docs/PORTING.md §1 says
;;;; belongs in the hand-written half and not in the importer's. get-mouse-x
;;;; and get-mouse-y come from the generated half — their Flan face is the C
;;;; one, two plain i32s, and there is nothing for a hand-written line to say

View File

@ -15,7 +15,7 @@
;;;; window-state?, set-window-state, clear-window-state, toggle-fullscreen,
;;;; minimize-window, maximize-window and restore-window all come from the
;;;; generated half. That is the line drawn in vendor/raylib/bindings and it
;;;; is worth saying why it falls here: the rule in PORTING.md §1 exists so
;;;; is worth saying why it falls here: the rule in docs/PORTING.md §1 exists so
;;;; that a build with no header set can still draw, and generated.flan is
;;;; committed, so it can. What a hand-written line adds on top of that is a
;;;; Flan face the C signature does not have — a Key instead of an int, a

View File

@ -11,7 +11,7 @@
;;;; Hand-written in raylib.flan rather than generated, and why each:
;;;;
;;;; begin-mode-3d / end-mode-3d a begin/end pair inside a frame, the
;;;; class PORTING.md §1 names, and taken
;;;; class docs/PORTING.md §1 names, and taken
;;;; together the way begin-mode-2d is
;;;; draw-cube / draw-cube-wires / draw-grid drawn every frame
;;;; update-camera the Flan face differs: (Ptr Camera3D),

View File

@ -29,7 +29,7 @@
;;;; zeroed global standing in for the null pointer that means "the default
;;;; ASCII set" — written for exactly this call, before anything called it.
;;;;
;;;; **Two things Flan would not do, both written up in PORTING.md.**
;;;; **Two things Flan would not do, both written up in docs/PORTING.md.**
;;;;
;;;; 1. `GetCodepointPrevious` cannot be called at all. It reads BACKWARDS
;;;; from the pointer it is handed, and a Flan `string` crosses to C as a
@ -176,7 +176,7 @@
;; Whether the TTF was there, asked once. A `defvar` and not the call itself
;; in the draw loop: path-file? is a stat, and a syscall per frame to answer a
;; question whose answer cannot change while the program runs is exactly what
;; the per-frame rule in PORTING.md is about.
;; the per-frame rule in docs/PORTING.md is about.
(defvar font-present bool)
(defvar show-font-atlas bool)
(defvar cursor i32)

View File

@ -36,7 +36,7 @@
;;;; u8]` globals here. The dimensions are compile-time constants in the C
;;;; too (the #defines and the two assignments right after), so nothing is
;;;; lost by fixing them, and a global cannot hold a Vec in any case
;;;; (PORTING.md §3). 375 is 25*15, written out because Flan's array length
;;;; (docs/PORTING.md §3). 375 is 25*15, written out because Flan's array length
;;;; must be a literal.
(import rl "vendor:raylib")

View File

@ -51,7 +51,7 @@
;;;; and because the picture is a demonstration of the filter rather than the
;;;; point of the program.
;;;;
;;;; One gap, written up in PORTING.md and worked around in `reload-texture`
;;;; One gap, written up in docs/PORTING.md and worked around in `reload-texture`
;;;; below: LoadImageColors answers a (Ptr Color) and UpdateTexture takes a
;;;; (Ptr u8), because the header spells its parameter `const void *` and Flan
;;;; has no cast between pointer types. The address of the first field of the
@ -143,7 +143,7 @@
;; takes a (Ptr u8), the header having spelled that parameter `const void *`;
;; and there is no cast between pointer types in Flan. What there is, is the
;; address of the red channel of pixel zero, which is the address of the
;; buffer said the long way round. See PORTING.md.
;; buffer said the long way round. See docs/PORTING.md.
(defn reload-texture [] ()
(rl/unload-image im-copy)
(set im-copy (rl/image-copy im-origin))

View File

@ -189,7 +189,7 @@ let declared_name (d : decl) =
(* ── Instrumenting a form with (pause) ─────────────────────────────── *)
(* [C-u C-c C-c] marks a form so the program stops when it runs — DISCUSS.md
(* [C-u C-c C-c] marks a form so the program stops when it runs — docs/DISCUSS.md
§9. The mark travels beside the source as a position and is applied *here*,
to the AST, rather than being spliced into the text the editor sends: text
would shift every line and column after the insertion, and the error

View File

@ -388,7 +388,7 @@ let wasm_main_source =
(* ── The browser: the main loop, the shell, and the link ─────────────
The mechanism, and why it is not the one the old note predicted.
BUILT.md says a web build "drives the loop with [emscripten_set_main_loop]
docs/BUILT.md says a web build "drives the loop with [emscripten_set_main_loop]
instead of a [while]. That is a different [main], not a different program."
The first half is right about the browser and wrong about what it costs
here: [emscripten_set_main_loop] wants the loop body as a callback, so every
@ -916,7 +916,7 @@ let shared ?(opts = default) ~ir ~out () : timing =
host. [flan reload] is precisely that caller. The complete answer is a
marker symbol the host defines and a module references, so the loader
refuses the pair at dlopen rather than the processor refusing it at a
call. See HANDOFF-x86-aggregates.md. *)
call. See docs/handoffs/HANDOFF-x86-aggregates.md. *)
if opts.x86 then
failwith
"--x86: Build.shared is the LLVM redefinition path, and an --x86 host \

View File

@ -1742,7 +1742,7 @@ and var ctx loc ~want name =
(* spec-memory.md puts the allocator in the calling convention as
[context/allocator] and [context/temp]. They read as names rather than
calls because that is how the spec writes them, and they are dynamic
variables at run time rather than extra parameters see BUILT.md for why
variables at run time rather than extra parameters see docs/BUILT.md for why
the literal reading of "calling convention" is deferred. *)
| "context/allocator" ->
expect loc ~want
@ -3493,7 +3493,7 @@ and named_call ctx ~want loc name args =
expect loc ~want
(mk loc Types.Alloc (Tast.Prim (Tast.Rt "flan_arena_new", [ cap ])))
(* Hands the pages back, which [free-all] deliberately does not — see
BUILT.md, "free-all is retain-capacity". *)
docs/BUILT.md, "free-all is retain-capacity". *)
| "arena-destroy" ->
arity loc name 1 args;
let a = check ctx ~want:Types.Alloc (List.hd args) in

View File

@ -3,7 +3,7 @@
[declare-c] closed half the gap: the wrapper, the typedefs and the
prototype are generated, so they cannot disagree with each other. The half
it left open is the one BUILT.md records as *trusted* that the signature
it left open is the one docs/BUILT.md records as *trusted* that the signature
somebody typed is the function's real signature. Nothing checked it,
because no header was ever read. This reads one.
@ -41,7 +41,7 @@
package's [defstruct]s against the header's records would be comparing
the header with itself. Keeping the layouts hand-written is what makes
[check_structs] an independent second source, and that check is the
cheapest real closure of BUILT.md's trusted-not-guaranteed gap: a
cheapest real closure of docs/BUILT.md's trusted-not-guaranteed gap: a
[_Static_assert] was rejected there as circular for exactly this reason,
and this is not circular, because the two sides have different authors.
@ -810,7 +810,7 @@ let of_dump ~env ~taken ~bound_syms ~config (d : dump) : imported =
(* The point of reading a header that the generator does not otherwise need.
BUILT.md rejected a [_Static_assert] on [sizeof]/[offsetof] as circular:
docs/BUILT.md rejected a [_Static_assert] on [sizeof]/[offsetof] as circular:
both sides would have come from the same field list. This is not circular.
The [defstruct] was written by hand and the record comes from the library's
own header, so a disagreement is real information and it is the failure
@ -868,7 +868,7 @@ let check_structs ~env ~(structs : (string * Ast.field list) list) (d : dump) =
| None -> None)
in
(* Names and widths both. Order is what a permuted [defstruct] gets wrong and
what BUILT.md says only a test can catch; width is the other half of the
what docs/BUILT.md says only a test can catch; width is the other half of the
same hazard and the one it calls out by name [f64] where the library
says [float] lays out eight bytes where there are four, and every field
after it moves. Comparing the rendered Flan type rather than the C
@ -1268,7 +1268,7 @@ let header ~loc ~header:h ~flags ~known_structs ~known_enums ~taken ~bound_syms
(* ── Printing a declaration back as source ─────────────────────────── *)
(* Which makes the third option in DISCUSS.md item 6 available at no extra
(* Which makes the third option in docs/DISCUSS.md item 6 available at no extra
cost: generate the declarations from the header, *commit the result*, and
regenerate when the library moves. That trade explicit in the source,
checked against reality, no header read at build time is a real one, and
@ -1309,7 +1309,7 @@ let decl_source (d : Ast.decl) =
something more specific for a reason it recorded.
What is left after those is a real disagreement about a width, an arity or a
direction which is exactly the class of bug BUILT.md warns about, where
direction which is exactly the class of bug docs/BUILT.md warns about, where
[f64] against the library's [float] reads as garbage rather than as a link
error. *)
@ -1378,7 +1378,7 @@ let diff_bound ~env ~(bound : (Ast.fn * string) list) (d : dump) =
(* Generate once, commit the result, regenerate when the library moves.
What that buys is in DISCUSS.md item 6 and it is not caching the dump is
What that buys is in docs/DISCUSS.md item 6 and it is not caching the dump is
already cached on disk and in memory, so a build that reads the header pays
for it once either way. It is that no header is needed by *anybody*: the
declarations are in the repository, so they are greppable, they diff when
@ -1398,7 +1398,7 @@ let diff_bound ~env ~(bound : (Ast.fn * string) list) (d : dump) =
they come from, because the one function that writes the file is this one
and it refuses when the two disagree. A regeneration that quietly rewrote
the bindings against a header the library does not match would produce
exactly the failure BUILT.md warns about a permuted struct read as five
exactly the failure docs/BUILT.md warns about a permuted struct read as five
plausible numbers rather than as a link error and it would produce it in a
committed file that looks reviewed.

View File

@ -1017,7 +1017,7 @@ let locals t ~frame =
refused) ]))
(* [(:op "inspect" :frame N :slot I :path (...))] — the inspector's second
rooting mode. [BUILT.md]'s "Two ways to root a walk" says what each root
rooting mode. [docs/BUILT.md]'s "Two ways to root a walk" says what each root
can and cannot do; this is the half that names a frame.
[i] in the break buffer used to send a local's *name* to be evaluated as an
@ -1080,7 +1080,7 @@ let inspect t ~frame ~slot ~path =
(* ── The allocation registry, read from this end ───────────────────── *)
(* [BUILT.md]'s "An address answers with a type" is what the table is and why.
(* [docs/BUILT.md]'s "An address answers with a type" is what the table is and why.
What follows is the reader: three verbs that ask the agent for what is
recorded, and one of them turns a recorded *name* back into a type.
@ -2530,7 +2530,7 @@ let two_process ?(debug = false) ~file ~sock () =
(* Everything above this line works the same either way. What follows is the
merged build: one executable that is the compiled Flan program *and* holds
the whole OCaml compiler, with the editor's socket served from a thread
inside it. DISCUSS.md item 14 is the spike this is built from.
inside it. docs/DISCUSS.md item 14 is the spike this is built from.
The shape, and it is this way round for a reason:

View File

@ -164,7 +164,7 @@ CAMLprim value flan_peek_bytes(value p, value off, value n) {
*
* The runtime system is released across the call. A delivery does a [dlopen],
* which is milliseconds of relocation and the loader lock, and holding OCaml's
* lock through it stalls every other OCaml thread for no reason DISCUSS.md
* lock through it stalls every other OCaml thread for no reason docs/DISCUSS.md
* §14's third cost, in the one place this lane creates it. Nothing the agent
* does touches an OCaml value, so there is nothing to keep alive across it. */
extern char *flan_agent_request(const char *line, uint64_t *len)

View File

@ -1034,7 +1034,7 @@ and value_at f (e : Tast.expr) : string =
same name would do, at the point the *address* is taken rather than at the
call. What that does not give is a value taken before a redefinition and
called after it: that one is still the old body, because there is nothing
left to re-resolve once the address is in a slot. Named in BUILT.md rather
left to re-resolve once the address is in a slot. Named in docs/BUILT.md rather
than papered over with a trampoline. *)
| Tast.FnAddr (Tast.Fnval n) -> body_of f n
| Tast.Addr p -> fst (place f p)
@ -2121,7 +2121,7 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
only [Rt] symbols that can transfer, so they are the only ones that take
the channel and the only ones guarded everything else in this family
is arithmetic over a container header and cannot reach a handler. A Vec
is checked inside the runtime rather than in emitted IR (BUILT.md), so
is checked inside the runtime rather than in emitted IR (docs/BUILT.md), so
this is where (at v i) gets what (at arr i) gets from [check_at]. *)
let signals =
String.equal sym "flan_vec_at" || String.equal sym "flan_vec_as_slice"

View File

@ -1,6 +1,6 @@
(** Macro expansion: the pass between the reader and [Parse].
There is no interpreter and there is not going to be one (BUILT.md, "Why
There is no interpreter and there is not going to be one (docs/BUILT.md, "Why
there is no interpreter"), so running a macro at compile time means
compiling it and loading it into this process. Every piece of that is
already built and measured [Emit.macro_thunk], [Build.macro_module],

View File

@ -341,7 +341,7 @@ let qualify_decl owned alias (d : Ast.decl) : Ast.decl =
[(Form.Sym {.s "begin"})] whose name is a *string in an argument* rather
than a name anything would rename. That is not an accident to work around:
it is the property [Macro]'s walk depends on, the one that makes a
quasiquoted call output rather than a compile-order dependency (BUILT.md,
quasiquoted call output rather than a compile-order dependency (docs/BUILT.md,
"A call inside a quasiquote is output, not a dependency"). It is also
exactly what puts the name out of a rename's reach.
@ -421,7 +421,7 @@ let rec rename_form owned alias bound (f : Form.t) : Form.t =
Desugared on the way out, because [Macro.program] is handed forms that
[Parse.parse_forms] has already run [Expand.quasiquote] over and its rounds
read them with that assumed. An un-desugared one would make a quasiquoted
call look like a real one, which is the false ring BUILT.md records the
call look like a real one, which is the false ring docs/BUILT.md records the
first cycle test walking into. *)
let qualify_macro owned alias (f : Form.t) : Form.t option =
match f.Form.v with
@ -704,7 +704,7 @@ let words line =
nothing if it is not".
That marker is what lets a package offer the check without requiring it.
[vendor/raylib] builds today against a shared library alone BUILT.md's "no
[vendor/raylib] builds today against a shared library alone docs/BUILT.md's "no
raylib headers are needed", which is a real property: a build needs
libraylib linkable and not raylib-devel installed. A required header would
take that away from everyone in order to give the check to the people who
@ -969,7 +969,7 @@ let rec import ~seen ~open_ ~loc alias dir =
the header is the package's own claim about what it binds; a
[defstruct] that disagrees with it lays fields out in the
wrong order and reads as five plausible numbers rather than as
a link error, which is the failure BUILT.md says only a test
a link error, which is the failure docs/BUILT.md says only a test
can catch. Continuing past a known-wrong layout to produce a
program that will read garbage is the shape the house rule
against swallowing things exists to prevent.

View File

@ -1466,7 +1466,7 @@ let source = {flan|
;; site attached to what a macro produces", and it is what the queued
;; structured-error work will read.
;;
;; Case order is the tag order (BUILT.md, unions), so this list is a layout
;; Case order is the tag order (docs/BUILT.md, unions), so this list is a layout
;; contract with lib/expand.ml's marshaller and may not be reordered.
(defunion Form
[(Sym [s string])

View File

@ -87,7 +87,7 @@ let rec same_const (a : Tast.expr) (b : Tast.expr) =
desugars a package's macro on its way out for the same reason but
[Parse.imported_macros] is read by [Macro.program] directly, past that map.
An undesugared body still has its quasiquote in it, which makes a
quasiquoted call look like a real one: the false ring BUILT.md records the
quasiquoted call look like a real one: the false ring docs/BUILT.md records the
first cycle test walking into.
Unqualified, and that is the point: a buffer writes its own macro's bare
@ -345,7 +345,7 @@ type change = {
}
(* [pause] is [C-u C-c C-c]: the position, in the source just sent, of the form
the program should stop at DISCUSS.md §9. It arrives as a separate field
the program should stop at docs/DISCUSS.md §9. It arrives as a separate field
rather than spliced into [src], because splicing text would move every
location after it, and it is applied below to the *declarations*, once
parsing has attached those locations and [Load] has qualified the names.

View File

@ -7,7 +7,7 @@
{1 The internal calling convention}
The spike's report (DISCUSS.md item 15) called the internal convention the
The spike's report (docs/DISCUSS.md item 15) called the internal convention the
sharpest obstacle, because LLVM's answer for a first-class struct is an
implementation detail discoverable only by disassembly a 24-byte struct
comes back in [rax]:[rdx]:[rcx], and [rcx] is a register SysV never uses
@ -745,7 +745,7 @@ let addr_sym f ~dst s =
holds the cell's *address*. Collapsing them was this lane's one real bug,
and it looked exactly right in the disassembly: [mov r11, cell@GOTPCREL(%rip)]
beside [call *%r11] reads as "call through the cell" and in fact calls the
cell. DISCUSS.md item 15 said this is how hand-encoding fails. *)
cell. docs/DISCUSS.md item 15 said this is how hand-encoding fails. *)
let load_sym f ~dst s =
if f.ext s then begin
load_int f.b ~dst ~mm:(Got s) ~size:8 ~signed:false;
@ -1118,7 +1118,7 @@ and lower_at f (e : Tast.expr) (dst : loc) : unit =
| Tast.None_ -> zero_value f dst t
(* Reading an uninitialised value gives whatever the slot held: stable
garbage rather than LLVM's [poison]. The one construct where the two
backends are meant to differ DISCUSS.md item 15, question 4. *)
backends are meant to differ docs/DISCUSS.md item 15, question 4. *)
| Tast.Uninit _ -> ()
| Tast.Local _ | Tast.Global _ | Tast.Field _ | Tast.Deref _ ->
let src = lvalue f e in
@ -1877,9 +1877,9 @@ and check_slice f (base : loc) (ty : Types.t) (loc : Loc.t) (lo : Tast.expr)
bounds_call f "flan_slice_error" loc [ a; b; c ];
lbl f.b ok)
(* [emit.ml]'s [check_div] and [check_cast], item 3 of HANDOFF-x86-rt.md's
(* [emit.ml]'s [check_div] and [check_cast], item 3 of docs/handoffs/HANDOFF-x86-rt.md's
list, and the one item on it that was blocked on a language decision rather
than on code. That decision is in HANDOFF-arith.md: a divide or remainder by
than on code. That decision is in docs/handoffs/HANDOFF-arith.md: a divide or remainder by
zero, the one division that overflows, and a float to integer cast whose
value does not fit all signal ArithError, exactly as a bad index signals
BoundsError.
@ -2112,7 +2112,7 @@ and call_c f ~sym ~args ~rty dst =
[Rt] symbols that can transfer, so they are the only ones that take the
channel and the only ones guarded everything else in this family is
arithmetic over a container header and cannot reach a handler. A Vec is
checked inside the runtime rather than in emitted code (BUILT.md), so this
checked inside the runtime rather than in emitted code (docs/BUILT.md), so this
is where [(at v i)] gets what [(at arr i)] gets from [check_at]. *)
and rt_signals sym =
String.equal sym "flan_vec_at" || String.equal sym "flan_vec_as_slice"

View File

@ -608,7 +608,7 @@ void flan_dev_watch_end(void) {
* four [flan_dev_watch_emit_*] above are the emitter it would be pointed at,
* shaped exactly like the [print] arm's. What is missing is the
* [(watch "hp" hp)] arm in check.ml that joins the two, which is a file this
* change does not own. BUILT.md says what that arm is. */
* change does not own. docs/BUILT.md says what that arm is. */
int32_t flan_dev_watch_i64(const char *name, int64_t x) {
if (!flan_dev_watch_begin(name)) return 0;
flan_dev_watch_emit_i64(x);
@ -642,7 +642,7 @@ int32_t flan_dev_watch_str(const char *name, const char *s) {
/* The scalar watch above keeps one value per name, and from a hot inner loop
* that is nearly useless: you see whichever of the 91,200 cells happened to
* run last. This is the other half the port of [spy-num] from the author's
* watch.clj, which exists for exactly that reason and is the piece PORTING.md
* watch.clj, which exists for exactly that reason and is the piece docs/PORTING.md
* calls the least obvious and the most valuable.
*
* **What a slot keeps: count, min, max, last, mean.** Five numbers, and the
@ -1053,7 +1053,7 @@ static int flan_reg_full; /* something found no slot */
* flan_rt.c are called unconditionally and begin with this load and a
* not-taken branch, because flan_dev.c is linked into every build and a
* second version of the allocator gated on a build flag is worse than a
* branch. That is a real cost and not zero; BUILT.md says so rather than
* branch. That is a real cost and not zero; docs/BUILT.md says so rather than
* repeating the claim that a release build carries nothing. */
static void flan_reg_report(void); /* the exit report, at the bottom */
@ -1378,7 +1378,7 @@ int64_t flan_dev_reg_by_type(int32_t live_only, int64_t *counts,
* Registered by [flan_dev_reg_enable] and therefore only in a dev build,
* which is the point: a file-scope destructor would run in *every* build,
* because this file is compiled into every build, and that would be a third
* place a release build is not free. BUILT.md names two and only two.
* place a release build is not free. docs/BUILT.md names two and only two.
*
* Off unless FLAN_DEV_LEAKS is set, and that is not timidity. The acceptance
* table reads programs/registry.flan's output with stderr folded in, so a

View File

@ -774,7 +774,7 @@ static int flan_over_budget(flan_allocator *a, int64_t size) {
* an address, or a range of them. So this half is unconditional and calls into
* flan_dev.c, which is linked into every build and begins each of these with a
* load of a flag that only a dev build ever sets. A release build pays a load
* and a not-taken branch per free, which is not nothing, and BUILT.md says so.
* and a not-taken branch per free, which is not nothing, and docs/BUILT.md says so.
*/
void flan_dev_reg_note(void *base, int64_t bytes, int64_t elem,
const char *type, int64_t typelen);
@ -905,7 +905,7 @@ static flan_allocator flan_heap = {
/* -- The arena: one fixed backing buffer and a bump offset. ----------
*
* `free-all` is retain-capacity: offset = 0, the pages stay. That is an
* announced amendment to spec-memory.md's operation table (see BUILT.md) and
* announced amendment to spec-memory.md's operation table (see docs/BUILT.md) and
* it is what Odin's arena_free_all already does in effect. Handing the pages
* back is `arena-destroy`, a separate operation, because a frame arena reset
* every frame must not return memory only to ask for it again.
@ -998,7 +998,7 @@ static void *flan_arena_proc(flan_allocator *a, int32_t mode, void *p,
* signature. The spec calls it part of the calling convention; taking that
* literally would touch every function signature, the FFI shim, the dev
* trampolines and the reload ABI, for the same observable behaviour. The
* literal reading is deferred and BUILT.md says so.
* literal reading is deferred and docs/BUILT.md says so.
*
* There are no threads in Flan, so a plain global is the whole of it. */
@ -1133,7 +1133,7 @@ _Noreturn void flan_free_all_fail(const uint8_t *loc, int64_t loclen) {
*
* ptr len cap allocator the release layout spec-memory.md fixes
* gen bumped on every reallocation the stale-slice
* word. It has no reader yet; see BUILT.md.
* word. It has no reader yet; see docs/BUILT.md.
* epoch the allocator's epoch when this Vec last
* touched it. Any operation on a container whose
* recorded epoch has moved traps.
@ -1142,7 +1142,7 @@ _Noreturn void flan_free_all_fail(const uint8_t *loc, int64_t loclen) {
* is not laziness: a redefinition module is built by llc and ld against a host
* that was built separately, and nothing makes the two agree on a struct size.
* A layout that changes with a build flag is a layout that can disagree across
* that boundary silently. Dropping them in release is deferred and BUILT.md
* that boundary silently. Dropping them in release is deferred and docs/BUILT.md
* says what it is blocked on.
*
* Every entry point returns int8_t 1/0 for "did it fit", and never reports
@ -1243,7 +1243,7 @@ static int8_t flan_vec_grow(flan_vec *v, int64_t want, int64_t size,
v->ptr = p;
v->cap = cap;
/* Any slice taken before this points at storage that may have moved. The
* word is bumped here and read nowhere yet; see BUILT.md. */
* word is bumped here and read nowhere yet; see docs/BUILT.md. */
v->gen++;
return 1;
}

View File

@ -95,4 +95,4 @@ int64_t spike_probe_align(int64_t x) {
* omission: check.ml rejects an aggregate in a [declare] signature and the
* generated shim flattens every one, so no Flan-emitted call ever passes a
* struct to C. The aggregate problem is real but it is on the Flan-to-Flan
* side, which is measured in DISCUSS.md item 15 and not from here. */
* side, which is measured in docs/DISCUSS.md item 15 and not from here. */

View File

@ -1,7 +1,7 @@
# What the hand-written x86-64 backend costs
`survey.sh` has said for three handoffs that this backend agrees with LLVM on all 97 corpus programs it can build.
Item 7 of `HANDOFF-x86-rt.md` is the other half of that sentence — nobody had a number for what the agreement
Item 7 of `docs/handoffs/HANDOFF-x86-rt.md` is the other half of that sentence — nobody had a number for what the agreement
costs — and it came with a list of suspects: a guard after every call, three frame temporaries per bounds check,
every intermediate in memory, `rep movsb` block copies, and an extra load per call site in a dev build. This is
the measurement. It does not change anything; two of the five suspects turn out not to matter, and the one that

View File

@ -2,7 +2,7 @@
# What does the hand-written backend cost, against LLVM, on the same programs?
#
# survey.sh answers "does it agree". This answers "what does agreeing cost",
# which is item 7 of HANDOFF-x86-rt.md and the one thing about this backend
# which is item 7 of docs/handoffs/HANDOFF-x86-rt.md and the one thing about this backend
# nobody had a number for. It builds each program the same two ways the
# survey does, and for each records three sizes and a time:
#

View File

@ -2,7 +2,7 @@
# Does the hand-written backend agree with LLVM?
#
# The only honest test of a hand-encoded backend is what the program prints and
# what it exits with -- DISCUSS.md item 15 and item 16 both say so, and both
# what it exits with -- docs/DISCUSS.md item 15 and item 16 both say so, and both
# say it after a disassembly that read perfectly beside a wrong answer. So this
# builds every program in test/programs twice, runs both, and diffs stdout,
# stderr and the exit status. objdump is for after a program already has the

View File

@ -6,7 +6,7 @@
;;;; with it and the session is the thing the project is built around never
;;;; having to restart. And the route in is the most ordinary one there is: a
;;;; grid indexed from a mouse position is out of bounds the first time the
;;;; pointer leaves the window (PORTING.md, §3).
;;;; pointer leaves the window (docs/PORTING.md, §3).
;;;;
;;;; So a failed bounds check signals BoundsError with `error`, the same way a
;;;; failed allocation signals StorageExhausted, and dies with the old message
@ -28,7 +28,7 @@
;;;;
;;;; 1. The frame is abandoned and the program carries on. `frames` counts
;;;; the ones that finished and `skipped` the ones that did not.
;;;; 2. **Defers run.** A trap ran none, which BUILT.md recorded as following
;;;; 2. **Defers run.** A trap ran none, which docs/BUILT.md recorded as following
;;;; from the noreturn-then-unreachable shape rather than as a decision.
;;;; The shape changed, so the question had to be answered rather than
;;;; inherited: an *answered* bounds failure leaves through the same

View File

@ -7,7 +7,7 @@
;;;; editor picks up a stopped program with the stack, the locals and the
;;;; globals readable and the restarts on offer.
;;;;
;;;; That is the claim `PORTING.md` said was missing, and it is worth testing
;;;; That is the claim `docs/PORTING.md` said was missing, and it is worth testing
;;;; end to end rather than reasoning about: before this, a bad index called
;;;; exit(134), and with `flan dev` running as one process that took the
;;;; compiler and the session with it.

View File

@ -1,6 +1,6 @@
;;;; Frame rollback: the half of the frame loop that `continue` does not do.
;;;;
;;;; PORTING.md Tier 1 item 6. Not a language feature — restart-case, struct
;;;; docs/PORTING.md Tier 1 item 6. Not a language feature — restart-case, struct
;;;; assignment and fixed-arrays-as-values are all built. This is the worked
;;;; example, the way sand.flan is the worked example for the loop itself.
;;;;

View File

@ -10,7 +10,7 @@
;;;; It is all one function because a Pool is move-only exactly as a Vec is,
;;;; so passing one to a helper *consumes* it — there is no borrowing
;;;; parameter in the language yet. That is not a pool question and this
;;;; program does not work around it; see BUILT.md.
;;;; program does not work around it; see docs/BUILT.md.
(defstruct Enemy [hp i32 kind i32])

View File

@ -18,7 +18,7 @@
;;;;
;;;; Nothing here introduces a name the host was not built with. X86.redefinition
;;;; refuses those by name — that is the registry path, and it is item 1 of
;;;; HANDOFF-x86-redef.md's "what remains" rather than anything to do with
;;;; docs/handoffs/HANDOFF-x86-redef.md's "what remains" rather than anything to do with
;;;; aggregates.
(defstruct Pair [a i64 b i64])

View File

@ -1,4 +1,4 @@
;;;; The aggregate case across the reload boundary, v1 (HANDOFF-x86-redef.md,
;;;; The aggregate case across the reload boundary, v1 (docs/handoffs/HANDOFF-x86-redef.md,
;;;; item 4).
;;;;
;;;; `reload.flan' proves that a redefined body is reached; every signature in

View File

@ -1053,7 +1053,7 @@ let () =
out of 54 is a fact about the Iroha that a lost byte anywhere in that
chain would change. The two walk rows are what stands in for
GetCodepointPrevious, which cannot be called from Flan at all see
PORTING.md, and the file's own header for what each row fails on. *)
docs/PORTING.md, and the file's own header for what each row fails on. *)
let raylib_codepoints_out =
"codepoints 54\n\
unique 49\n\
@ -2799,7 +2799,7 @@ ERR@7 unexpected token: not the kind the caller was reading
offset its own alignment allows, so the offset of field 1 in
{ i8, T } *is* alignof(T). Reading [2 x i64] out of the emitted type and
concluding 8 would be asserting the layout against itself, which is the
circularity BUILT.md already rejected for _Static_assert. *)
circularity docs/BUILT.md already rejected for _Static_assert. *)
let llvm_align ir sname =
let sty = Printf.sprintf "%%\"%s\"" sname in
let b = Buffer.create 512 in

View File

@ -1768,7 +1768,7 @@ let () =
The redefinition binds a local, deliberately, because that is what
the detector can see. [Emit.slot_fingerprint] hashes a body's
*slots*, so a new body with the same slots and different global
references is not caught the hole is stated in BUILT.md, and a
references is not caught the hole is stated in docs/BUILT.md, and a
test that asserted otherwise would be asserting a mechanism that is
not there. *)
let r =
@ -2421,7 +2421,7 @@ let () =
(* ── Marking a form with (pause), from the editor's side ───────── *)
(* DISCUSS.md §9: C-u before an evaluation marks a form so the program
(* docs/DISCUSS.md §9: C-u before an evaluation marks a form so the program
stops when it runs. The mark travels as a *position* beside the code
rather than spliced into it splicing text would move every line and
column after the insertion, and the error overlays, the break loop's

View File

@ -1632,7 +1632,7 @@ let () =
(* The struct check, which is the point of reading a header the generator
does not otherwise need: the defstruct and the header's record have
different authors, so a disagreement is real information. A
_Static_assert was rejected in BUILT.md as circular for want of exactly
_Static_assert was rejected in docs/BUILT.md as circular for want of exactly
that. *)
let structs_of ds =
List.filter_map
@ -1642,7 +1642,7 @@ let () =
in
check "a defstruct that matches the header is not reported"
(Cimport.check_structs ~env ~structs:(structs_of fixture_ds) dump = []);
(* Permuted: the failure BUILT.md says only a test can catch, because every
(* Permuted: the failure docs/BUILT.md says only a test can catch, because every
field still reads as a plausible number. *)
check "a permuted defstruct is reported"
(match
@ -1651,7 +1651,7 @@ let () =
with
| [ ("Pair", why) ] -> contains why "field order"
| _ -> false);
(* Widened: the other half of the same hazard and the one BUILT.md names —
(* Widened: the other half of the same hazard and the one docs/BUILT.md names —
f64 where the library says float lays out eight bytes where there are
four, and every field after it moves. *)
check "a widened field is reported"
@ -1684,7 +1684,7 @@ let () =
~structs:(structs_of (program "(defstruct Feel [mood i32 n i32])\n")) dump
= []);
(* And the whole point of the check survives it. The tolerance is for a
32-bit integer and nothing else, so the width hazard BUILT.md names f64
32-bit integer and nothing else, so the width hazard docs/BUILT.md names f64
where the library says float still fails, in the very struct whose
other field is an enum. *)
check "a widened field beside an enum field is still reported"

View File

@ -201,7 +201,7 @@ let () =
fail "reload\n got: %S (exit %d)\n wanted: %S" text code want;
(* The same thing again, compiled by the dev backend end to end (x86.ml's
header, HANDOFF-x86-rt.md item 1). Both halves, host and module, because
header, docs/handoffs/HANDOFF-x86-rt.md item 1). Both halves, host and module, because
the two backends' conventions agree on every scalar and disagree on
every aggregate: an LLVM-built module dlopened into an --x86 host would
be correct until the first redefined function took or returned a struct.
@ -213,7 +213,7 @@ let () =
flan_dev_global lookups that needs are refused there by name.
Read by running, not by reading. A disassembly reads correctly beside a
wrong answer often enough (DISCUSS.md item 15) that only the printed
wrong answer often enough (docs/DISCUSS.md item 15) that only the printed
transcript settles it: [outer] is compiled once into the host and never
rebuilt, so "after2 1204" can only mean its call site followed a body
that this backend emitted, published through a cell it reached via the
@ -365,7 +365,7 @@ let () =
segfaults with this guard in place, re-measured after it landed. The
guard catches a caller holding one option record; it cannot catch a
caller holding two, and [flan reload] is exactly that caller. See
HANDOFF-x86-aggregates.md. *)
docs/handoffs/HANDOFF-x86-aggregates.md. *)
(match Build.shared ~opts:x86 ~ir:"" ~out:(tmp "never.so") () with
| _ -> fail "Build.shared accepted an --x86 option record"
| exception Failure m when has m "--x86" -> ()

View File

@ -13,7 +13,7 @@
* settles it. Memcheck answers both, and needs no instrumentation whatever:
* it works on the binary, so hand-written IR, clang-compiled C and libc all
* arrive on the same footing. Nothing in [Emit] has to cooperate, which is
* the exact opposite of the sanitize_address attribute story in BUILT.md.
* the exact opposite of the sanitize_address attribute story in docs/BUILT.md.
*
* The two tools are not ordered, they are complementary, and the measurement
* that shows it is bounds.flan: ASan catches three of its six deliberate

View File

@ -181,7 +181,7 @@ let () =
end;
(* ── raylib in the browser ────────────────────────────────────────
The claim BUILT.md left open. core-basic-window.flan is built for the
The claim docs/BUILT.md left open. core-basic-window.flan is built for the
web unchanged no edit to its `until` loop, which is the whole point
of choosing asyncify over emscripten_set_main_loop and the module is
then read for the two things that prove the claim rather than assert
@ -237,7 +237,7 @@ let () =
no DOM and no WebGL, so the module instantiates, main runs, and
InitWindow dies inside glfwInit on `window is not defined` which
says the module is live but says nothing about whether the canvas
paints. Only a human opening it can say that; BUILT.md carries the
paints. Only a human opening it can say that; docs/BUILT.md carries the
commands. *)
let out = Filename.concat scratch "flan-web-sand.html" in
(match web_build "../sand.flan" out with

View File

@ -16,7 +16,7 @@
# malloc(cap), so every byte in it — handed out, padding, or still past
# the offset — is one allocation to memcheck. It never learns that a
# sub-object ended, so it has nothing to complain about and equally
# nothing to catch. Recorded as a coverage ceiling in BUILT.md, not as a
# nothing to catch. Recorded as a coverage ceiling in docs/BUILT.md, not as a
# clean bill of health.
#
# What *is* caught is definedness rather than addressability, and that

View File

@ -67,7 +67,7 @@ name IsAudioStreamProcessed audio-stream-processed?
# Hand-written in raylib.flan, so excluded here: a second declare-c for one C
# symbol is refused for the whole program. These three are on a game's
# per-frame path (PORTING.md), and a hand-written line is what the signature
# per-frame path (docs/PORTING.md), and a hand-written line is what the signature
# check has to compare against -- generated output agrees with the header by
# construction, so it can only check the hand-written half.
exclude DrawTexturePro

View File

@ -12,7 +12,7 @@
#
# The output is a plain static archive plus nothing else: no headers are
# installed, because the generated FFI shim declares the prototypes it uses
# (see BUILT.md, "No raylib headers are needed").
# (see docs/BUILT.md, "No raylib headers are needed").
#
# sh vendor/raylib/build-web.sh
#
@ -54,7 +54,7 @@ else
# GRAPHICS_API_OPENGL_ES2 is what WebGL is. PLATFORM_WEB makes rcore.c
# include platforms/rcore_web.c, whose WindowShouldClose() is an
# emscripten_sleep that returns false — see BUILT.md on why that is the whole
# emscripten_sleep that returns false — see docs/BUILT.md on why that is the whole
# reason a Flan `until` loop needs no rewriting for the browser.
#
# rglfw.c is not in the list: the web platform uses emscripten's own GLFW

View File

@ -39,7 +39,7 @@
;;;;
;;;; The obvious fix is `defer`, and it is not available here. `defer` is a
;;;; compile-time construct — the forms are copied into the function's exit
;;;; paths — so BUILT.md refuses it inside a loop or a branch, and a
;;;; paths — so docs/BUILT.md refuses it inside a loop or a branch, and a
;;;; begin/end pair lives inside the game loop essentially always. Were it
;;;; permitted there it would be wrong in the worse direction: one EndDrawing
;;;; at function exit for N BeginDrawings. A macro expanding to a defer would