659 Commits

Author SHA1 Message Date
fa83056708 A backend mismatch is refused by the loader, and says so 2026-09-14 07:15:13 +07:00
b47f137b05 A plan for the ABI marker, written before the code 2026-09-14 07:10:54 +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
fdf7531c37 Nine filters that rewrite the buffer they are handed, and the half of them a headless run can pin 2026-09-13 23:22:37 +07:00
fbaf376281 The linker error in dune test is deliberate, and a plan to stop it looking otherwise 2026-09-13 23:21:42 +07:00
eacf7c489f The arithmetic condition landed without restarts, and why that is a question 2026-09-13 23:20:11 +07:00
c9394a6bcd The mismatch is reachable from flan reload, and the guard does not close it 2026-09-13 23:18:25 +07:00
ed32915501 The two redefinition builders refuse to be crossed 2026-09-13 23:15:40 +07:00
11474ecbda The benchmark table, filled in 2026-09-13 23:15:17 +07:00
f9f476dc41 COST.md, with the benchmarks measured and the corpus table still to come 2026-09-13 23:14:57 +07:00
1933295a70 A pixel format is twenty-four names, not an int 2026-09-13 23:13:49 +07:00
8ae77db92c A redefined function that takes and returns a struct, on both backends 2026-09-13 23:12:43 +07:00
2a7fcb6001 The plan for the last two ports, and the one question nobody is awake to answer 2026-09-13 23:10:14 +07:00
385445faa7 Merge branch 'worktree-agent-a1df35fb41dc83276' into dev-loop 2026-09-13 23:10:00 +07:00
9ee398e817 The two cases that would have passed while being wrong
An i32 min / -1 and an f32 cast out of range. The first is where the two
backends disagreed silently rather than both dying -- x86 divided in 64 bits
and truncated on the store, answering -2147483648, where LLVM emitted poison --
and it is the only case that exercises the widening on the way into the
condition, so a bug there would have left every other row passing. The second
is the one place the two backends reach the same answer by deliberately
different routes, f32 bounds here and widened doubles there, and the survey is
what says the routes agree.
2026-09-13 23:09:08 +07:00
188e6bc72f A plan for measuring the aggregate case across the reload boundary 2026-09-13 23:07:26 +07:00
b0f4fb73e1 The x86 backend stops leaving a divide by zero to the hardware
Item 3 of HANDOFF-x86-rt.md, which was blocked on the language decision rather
than on code. check_div and check_cast sit beside check_at and check_slice and
reuse bounds_call unchanged; flan_arith_error takes three extras, so the
channel lands in r9 and the argument registers are exactly full.

Three things differ from the LLVM side because the instruction set does: two
branches rather than one branch and a select, since there is no select and a
second compare on the cold path is free; the cast bounds compared in the
source's own precision rather than widened to a double, which is exact because
every bound is a power of two; and NaN excluded by choosing the direction of
each compare, because ucomis sets CF, ZF and PF together when either operand is
unordered.

Both programs now print byte-identical stdout, stderr and exit status through
either backend.
2026-09-13 23:07:23 +07:00
82c8b38dda The alias is verified in both directions, and the handoff says so 2026-09-13 23:07:22 +07:00
368866c63d A scratch directory per run, after two of them shared one 2026-09-13 23:05:12 +07:00
6c15eef5e5 Do not time a program that only waits for the timeout 2026-09-13 23:02:04 +07:00
957ba076bc Merge branch 'worktree-agent-ab6daf83988bee428' into dev-loop 2026-09-13 22:59:11 +07:00
921155408e The clean survey: ninety-nine match, and the edit described as it stands 2026-09-13 22:56:41 +07:00
a431cddd3b A divide by zero names the file and the line, and is answerable
Three arithmetic situations had no defined behaviour and the two backends
disagreed about all three: a divide or remainder by zero, which was a raw
SIGFPE with no message and no location; (/ min -1), whose quotient is one past
the top of the type; and a float to integer cast whose value does not fit,
which LLVM called undefined and would fold to anything.

They now signal ArithError with `error`, exactly as a bad index signals
BoundsError, and die with a sentence naming the file, the line and the operands
only if nothing answered. The guards ride the same --checks flag as the bounds
check and are elided with it.

No restart is established at the failing operation. The sketch this started
from asked for use-value, and the implementation ruled it out: a restart frame
is allocated by the restart-case that offers it, on its own stack, so the
runtime cannot hold one on a program's behalf and use-value here would mean an
alloca and a restart frame at every division in every checked build. That is
the cost already refused for indexing, buying a silently different answer.

The x86 backend is unchanged and is the next commit.
2026-09-13 22:55:26 +07:00
69d10bec4e Arithmetic with no answer is a condition: the plan 2026-09-13 22:55:26 +07:00
60d4c762e1 Merge branch 'worktree-agent-ab6daf83988bee428' into dev-loop 2026-09-13 22:50:40 +07:00
54da06d111 An @x86 alias, and the two scripts that ask what the backend costs 2026-09-13 22:49:38 +07:00