682 Commits

Author SHA1 Message Date
f47f9ffe59 Two paragraphs in BUILT.md superseded in place, and the spike report says it is one
BUILT.md appends and never dates, so a paragraph that was true when it was
written reads as fact forever. Two are not: the map type spelling is (Map K V)
and braces in type position are refused by name, and sand.flan has not called
load-texture since it was cut back to port parity. Both get the parenthetical
the FLAN_RAYLIB_H paragraph already got rather than an edit, since that is the
convention the file has.

SPIKE-GENERICS.md gets the treatment overview.md has: a header saying what it is
and when it stopped being current. Its body stays. The two things in it that
would now mislead are named there -- its account of what plan.org says, which
plan.org has since overtaken, and the bare-t-at-every-use rule, which is narrower
than what shipped, since (Option t) does not compile.
2026-09-14 07:36:24 +07:00
dcba88ffd6 The 09-13 handoff says it is a snapshot, and two settled questions stop reading as open
NEXT.md prepends, so its second section still opened with "Read this first" while
pinning a tree six lanes behind and listing as live a queue most of which has
landed. It keeps its contents; what it gains is the sentence saying when it
stopped being where the tree is, and the survey count it quotes measured again on
this tree -- 103 MATCH, 0 DIFFER, 38 skipped.

The two generics entries get the file's own strikethrough treatment, which their
siblings already had: the {K V} catch resolved exactly as it predicted it would,
and there are five predicates rather than four.

Also here: conditions.org gains ArithError beside BoundsError, with the split
between the two runtime conditions that offer retry and the two that deliberately
offer nothing; and tast.ml's header stops naming a tree-walking interpreter among
the backends that consume the typed IR.
2026-09-14 07:36:24 +07:00
0aebef62f3 The sketch's condition syntax, checked against the parser rather than recalled
handler-bind takes (Type [name] body ...) and has since it was written; the
sketch paired a type with an fn, which is the shape parse.ml names in its own
refusal message. load-level had two return types. And there is no defcondition
anywhere in the tree -- a condition type is an ordinary struct, which is what
both spec-conditions.md and conditions.org say, so the one form in this file that
introduced one was inventing it.

The header's rules went with them: lowercase-is-a-type-variable and "no sigils"
are both the pre-$t spelling, and let never took an annotation.
2026-09-14 07:36:24 +07:00
d94e864dba The syntax sketch shows syntax that compiles, and says where it does not
Every generic example in syntax-sketch.flan was written in the lowercase-variable
spelling that $t replaced, so the file whose job is to be copied from was the one
place the wrong spelling cost the most. Checking the replacements against the
compiler rather than against the sketch turned up three more, and each is now
either fixed or named:

- reduce is (reduce s init f) and its accumulator is the element type. The old
  argument order was Clojure's and the fold in centroid is not a shape it has.
- an fn cannot be written inline into a (Fn [$t $t] bool) parameter, because a
  generic body is checked with nothing substituted and the fn has no types to
  take. Inside a generic the callback is a named defn.
- (largest hps >) does not work and never did: an operator is not a name. Both
  this file and spec-memory.md asserted it.

centroid is left as it was, because component-wise arithmetic over a fixed array
is planned rather than built and an aspirational example is not a defect -- but
it now says so instead of leaving it to be found.
2026-09-14 07:36:24 +07:00
daee402026 The two specs catch up with generics and with the conditions the runtime signals
spec-memory.md's generics section still said there were no constraints and wrote
its type variable as a bare lowercase name, which is the spelling the $t sigil
replaced -- the largest example in it would not have compiled. The where clause
and the five predicates are plan.org's account and this now agrees with it rather
than contradicting it. The empty-map example named no types and used defvar,
neither of which is how map-new is called.

spec-conditions.md named BoundsError once and nothing else. The four conditions
the runtime signals, and the split between the two that establish a retry and the
two that deliberately establish nothing, belong in \xc2\xa75 because that is the section
whose account of restarts the split could have falsified. It does not: the rule is
that the restart a bad index wants is the one the program already had.
2026-09-14 07:36:24 +07:00
00163bcf34 There is no interpreter, and the plan stops promising one
The Compilation section was written around a permanent tree-walking backend for
expression eval. Open decision #7 closed the other way and BUILT.md records it:
compiling is the only way a form is ever run. The diagram, the milestone-2 exit
criterion, milestone 7's "free in the interpreter", the dev/release table and
the decision itself all said otherwise, and lib/expand.ml states the settled
answer at the top of the file.

Also here, because the same section was the place they were missing: the
hand-written x86-64 code generator, which is a second route from the typed IR to
the same observable behaviour rather than a second semantics; DWARF from both
code generators rather than from LLVM alone; OCaml as a settled host language;
and the map-new example, which has named its key and value types since braces
stopped being a type spelling.
2026-09-14 07:36:24 +07:00
be39f32cb6 Update README 2026-09-14 07:34:26 +07:00
6ac11be5b1 The README points at the docs index too 2026-09-14 07:31:15 +07:00
5b54e1b7da Merge the tidy: the crossed-pair case is the marker's, and the citations follow the move 2026-09-14 07:30:14 +07:00
9735bcc2d8 The survey numbers, measured 2026-09-14 07:29:17 +07:00
cc6c046ed1 Merge branch 'worktree-agent-a4bfc3fb14f6fdc0c' into dev-loop 2026-09-14 07:25:20 +07:00
38d402e326 The marker's name is a literal in four places, and the note says so 2026-09-14 07:24:54 +07:00
e62b3bfe04 The survey did not move, which is what the dev gate is for 2026-09-14 07:24:31 +07:00
82557df51b The agent's half of the refusal is checked where it actually ships 2026-09-14 07:22:19 +07:00
1f30d9a067 The marker, written up: what it refuses and what it deliberately does not 2026-09-14 07:19:27 +07:00
1795441140 The index names the handoffs from where it sits, not from the root 2026-09-14 07:17:28 +07:00
9dddcac821 MIT, and a note that vendor keeps its own terms 2026-09-14 07:17:02 +07:00
0afa530295 An index for docs/, and a .gitignore that explains itself 2026-09-14 07:16:46 +07:00
96b1bba265 A README that says what this is, with a flan on it 2026-09-14 07:15:31 +07:00
fa83056708 A backend mismatch is refused by the loader, and says so 2026-09-14 07:15:13 +07:00
9d5689ffa2 Every citation of a moved document now resolves from where it is written 2026-09-14 07:12:27 +07:00
eb641cf8eb Move the reports and the history into docs/ 2026-09-14 07:10:57 +07:00
b47f137b05 A plan for the ABI marker, written before the code 2026-09-14 07:10:54 +07:00
c1c1f8ed76 A plan for tidying the root 2026-09-14 07:10:46 +07:00
71495a7fc8 A flan held between two parentheses, which is the whole joke 2026-09-14 07:10:26 +07:00
682cb745ac Merge branch 'worktree-agent-a4f73ab1c6726fdc2' into dev-loop 2026-09-14 06:55:39 +07:00
4c46a56827 Merge branch 'worktree-agent-a123a91f32f4c9779' into dev-loop 2026-09-14 06:55:39 +07:00
ddaebf2516 Merge branch 'worktree-agent-affdbb9fef702b34d' into dev-loop 2026-09-14 06:55:39 +07:00
2b68262249 Merge branch 'worktree-agent-ab7f6288f142b05df' into dev-loop 2026-09-14 06:55:39 +07:00
70e95119e9 Merge branch 'worktree-agent-a03eb07b90913c874' into dev-loop 2026-09-14 06:55:39 +07:00
9486bc0ac0 The reprint header names a signature, not a count, so it is true on every path 2026-09-13 23:44:42 +07:00
a9c5cf0c26 A frame with no line does not get the wrong line
flan..init-globals and the C main shim sit inside the compile unit's range
and have no line-table sequence. The worry is that a debugger picks a row
out of a neighbouring function's sequence and reports a confident wrong
Flan line; it does not. Checked by breaking inside dev-globals.flan's
initialiser: the frame is named from the ELF symbol, the line is honestly
absent, and the unwind out to libc is the .cfi working.

So that handoff item is a gap and not a wrong answer, which is worth the
distinction because the two deserve different urgency.
2026-09-13 23:43:41 +07:00
ae1b113ad4 Re-measured after the rebase onto the arithmetic-condition lane
101 MATCH rather than 99, and neither number is this lane's doing -- that
lane added two probes to spike/x86. Zero DIFFER, zero REFUSED, zero NOX86
on both sides, the skip breakdown unmoved, cells 4/4, dune test exit 0 with
232 checks and no failures.
2026-09-13 23:42:37 +07:00
3eb53d0f85 The measured baselines, and why a survey that says 143 is not a regression 2026-09-13 23:41:52 +07:00
ce72f03a0d The offset the walk never reaches, and a stat that was running sixty times a second 2026-09-13 23:36:03 +07:00
c8464abc18 The handoff, with every number in it measured on this tree
The baseline rows, the twelve-program DWARF sweep re-run against the code
that was actually committed rather than a superseded version of it, the
lldb transcript, the unwind out of flan_bounds_error, and the compile
unit's range checked against nm rather than asserted.
2026-09-13 23:32:35 +07:00
78368811c0 The x86 backend's DWARF is checked by a debugger, not by a transcript
test_acceptance.ml's lldb case is the only part of the suite that says a
person can debug a Flan program: a breakpoint on a Flan name, a backtrace
with .flan files and lines, and locals with their own types. It ran against
the LLVM backend only.

An --x86 arm, with a narrower claim: the breakpoint and the backtrace, and
that the program still prints what it printed. No frame variable, because
x86.ml emits no DW_TAG_variable -- a slot there is a bump-allocated frame
temporary whose lifetime the backend does not model. That gap between the
two backends is now recorded in the place it will be read.

Worth pinning rather than leaving to a handoff, because everything it
exercises is bytes x86.ml wrote by hand -- a line program, a compile unit,
an abbreviation table -- and a wrong byte in any of them is silent.

debug_compile grows an ~x86 flag beside the ~dev one it already had.
2026-09-13 23:32:35 +07:00
c9c23c5079 Call frame information, which is the thing the assembler gets right
The debug-information section added in the last commit is about what GAS
gets wrong against a file with no instructions in it: .loc is flushed when
an instruction is assembled, and this backend assembles none. CFI is the
opposite case and worth recording beside it. Its advances come from frag
positions, so .cfi_def_cfa_offset interleaved with .byte comes out exact --
measured, readelf --debug-dump=frames on a .byte-only function gives the
right advances.

The content is a constant, and that is the header's claim about the frame
model paying for itself. rsp is written exactly twice, so: on entry the CFA
is rsp+8; push rbp makes it rsp+16 with the saved rbp at cfa-16; mov rsp,rbp
moves the rule onto rbp and it stays there for the whole body; after leave
rsp is rbp+8 and the CFA is rsp+8 again. Five directives, three sites --
emit_fn, emit_main and emit_globals_init, which have the same prologue.

emit_main has no closing rule because it has no epilogue: it leaves through
flan_exit and the ud2 after that is unreachable, so the rbp rule holds to
the last byte, which is what a backtrace out of anything main called wants.

gdb did not need this -- its prologue analyser already unwound out of
flan_bounds_error into flan.main with a line number, because push rbp;
mov rbp,rsp; sub rsp,N is the pattern it recognises. It is here because the
description is now stated rather than guessed, and because a break at the
very first byte of a function -- before the push -- now unwinds from a rule
rather than from a heuristic.

Gated on --debug so a release build's assembly stays byte-for-byte what it
was. That is conservative rather than principled: the description is correct
in every build and a release build is where a crash would most want it. What
stops it being unconditional is only that nothing measures the .eh_frame it
would add, and another lane is measuring backend cost right now.
2026-09-13 23:32:35 +07:00
201cd87bc9 Debug information for --x86, written out as bytes
--x86 --debug was refused in build.ml for want of DWARF. It emits it now:
a compile unit, a subprogram per function, and a line table, so gdb breaks
by Flan file and line and a backtrace names Flan source.

The reason it is written as data rather than as .loc directives is the
finding worth carrying forward, and HANDOFF-x86-debug.md leads with it.
GAS builds its line table out of dwarf2_emit_insn, which runs only when an
instruction is assembled, and this backend assembles none -- everything is
a .byte blob. A pending .loc therefore sits until the next .loc and is
flushed at whatever the location counter has reached by then: every row
comes out one statement late and the last statement of every function gets
no row at all. Measured on GAS 2.44, and interposing labels does not help.

So .debug_line is emitted here the way the instructions are. Every row is
a full DW_LNE_set_address on a label rather than an advance_pc with a
computed delta, because a delta would be a difference of two labels inside
a .uleb128 -- a value whose size changes the offsets after it, and whose
failure would look exactly like a DWARF bug.

The .file 1 directive at the top of the assembly is not about our table at
all: without it clang's integrated assembler generates a compile unit of
its own over the .s, whose rows land at the call mnemonics, and those
addresses are inside the functions we already describe. -gdwarf-4 goes
with it so the empty stub it leaves behind parses.

Rows are deduplicated on the byte counter as well as on the position.
lower recurses, so an outer form and the inner one that emits its first
byte both ask for a row at the same address; keeping the first is smaller
and is the better answer, since a debugger takes the last of a run. buf.n
already existed and was written but never read. This is its first reader.

No locals and no types, deliberately. A slot here is a bump-allocated
frame temporary whose offset is known but whose lifetime is not modelled
-- scoped reclaims temporaries and a later expression reuses the bytes --
so a DW_TAG_variable would be right at some addresses and confidently
wrong at others. That is the call build.ml already makes about wasm32's
member offsets.

Verified under gdb against test/programs/debug.flan, and against an LLVM
--debug build of the same program. Identical bar the parameter values,
which is the locals work. Transcript in the handoff.
2026-09-13 23:32:35 +07:00
853bc35be7 A release build stops calling into a registry that is switched off
emit.ml:1918 drops the allocation registry's notes in a release build --
the checker builds a Tast.Rt it cannot know is unwanted, because it does
not know whether this is a dev build. The x86 backend had no counterpart
and emitted the calls for real: correct, since the registry answers
nothing when it is disabled, but one call per container operation into a
function that returns immediately.

The guard is emit.ml's byte for byte, strict > 17 included: bare
flan_dev_reg_note is the runtime's own C entry point and is never a
Tast.Rt, and what check.ml builds is the _vec, _map and _pool wrappers.
emit.ml drops the note before the arguments are walked so that taking the
address of the container does not leave an escaped alloca behind; here the
arguments are not touched until call_rt, so answering () is already early
enough.

Measured, call sites of flan_dev_reg_note in the disassembly:

  vec.flan       46 -> 1     (--dev: 46)
  maps.flan      55 -> 1     (--dev: 55)
  registry.flan  36 -> 1     (--dev: 36)

The one left in each is not emitted code -- it is inside the runtime's own
flan_dev_reg_note_vec. An LLVM release build of vec.flan has the same one,
so the two backends now agree.

HANDOFF-x86-debug.md is the stub for item 6, which is next, and it leads
with the finding that changes that item's plan: .loc does not work against
a backend that emits .byte blobs, so the line table has to be written out
by hand.
2026-09-13 23:32:35 +07:00
69e5a777e7 The two ports' findings, and the one arm in the header check that would close them 2026-09-13 23:32:04 +07:00
4c184e68ce A poem of forty-seven kana, and the one raylib call a Flan string cannot make 2026-09-13 23:28:40 +07:00
acc928a203 The b3 note belongs under the table, not inside it 2026-09-13 23:28:39 +07:00
35c33c7b8b The idiv in b3 is most of the denominator, and the two b2 numbers now agree 2026-09-13 23:28:27 +07:00
219a42ed5b The handoff carries the headline and what was left undone 2026-09-13 23:27:29 +07:00
f9f37e524a A note that the old handoff's environment note is now stale 2026-09-13 23:27:20 +07:00
69129fc29f p2's -O0 build is smaller because nothing unrolled the loop 2026-09-13 23:27:07 +07:00
65eeb09378 The corpus numbers, and the rows they came from 2026-09-13 23:26:48 +07:00
1195e90476 The noise is gone and the reporting half is verified by breaking the test on purpose 2026-09-13 23:25:48 +07:00
3c4ca20fea The robustness fixture says its failure is deliberate, and stops printing a linker's words 2026-09-13 23:24:21 +07:00