The page catches up: the backend serves the editor, and emit --x86 is annotated

This commit is contained in:
Joseph Ferano 2026-09-14 13:12:53 +07:00
parent 45c5b43021
commit a4dce138c0

View File

@ -328,17 +328,30 @@ $ ./_build/default/bin/main.exe run calc-me.flan "1 + 2 * (3 - 0.5) / 2"
<pre><code class="sh">$ flan
usage: flan (read|parse|check|emit|shim) &lt;file.flan&gt;...
flan emit &lt;file.flan&gt; [--x86] [--dev] [--debug] [--no-bounds-checks]
flan import-c &lt;header.h&gt; [package.flan...] [clang flags...]
flan generate-c &lt;package-dir&gt;
flan build &lt;file.flan&gt; [-o out] [--no-bounds-checks] [--dev] [--debug] [--sanitize] [--x86] [--target=wasm32-wasi|web]
flan run &lt;file.flan&gt; [args...]
flan reload &lt;program.flan&gt; &lt;forms.flan&gt; [-o out.so]
flan dev &lt;program.flan&gt; [-s socket]</code></pre>
flan reload &lt;program.flan&gt; &lt;forms.flan&gt; [-o out.so] [--x86]
flan dev &lt;program.flan&gt; [-s socket] [--x86]</code></pre>
<p><code>read</code>, <code>parse</code>, <code>check</code>, <code>emit</code> and
<code>shim</code> each stop the pipeline one stage further along and print what it
produced. <code>run</code> builds to a temporary file and execs it.</p>
<p><code>flan emit --x86</code> prints the hand-written backend's assembly where plain
<code>emit</code> prints LLVM IR, and it is <em>annotated</em>: each run of bytes is
headed by the Flan form that produced it and its source position, and each function by a
map of its frame saying which displacement is which parameter, which is a named local,
and below which offset everything is a reusable temporary. Reading a listing of this
backend without that map is reading <code>-0x20(%rbp)</code> with no key. The comments
cost nothing in the object &mdash; the assembler discards them &mdash; but they are
emitted only by <code>emit</code>, never by a build, so the assembly a build hands to
<code>clang</code> is the same text it always was. <code>spike/x86/dump.sh</code> puts all
four lowerings of one function side by side, which is what <kbd>C-c C-l</kbd> shows in
the editor.</p>
<p>The smallest program:</p>
<pre><code>(defn main [] ()
@ -1734,6 +1747,7 @@ something surprising.</p>
<tr><td><kbd>C-c C-i</kbd></td><td>inspect a value, navigating into its fields</td></tr>
<tr><td><kbd>C-c C-m</kbd></td><td>what the macro call at point expands to, one step; <kbd>C-u</kbd> first for all the way</td></tr>
<tr><td><kbd>C-c C-a</kbd></td><td>disassemble a function; <kbd>C-u</kbd> first for its LLVM IR</td></tr>
<tr><td><kbd>C-c C-l</kbd></td><td>every lowering of a function at once: IR, <code>-O0</code>, <code>-O2</code>, and the hand-written backend</td></tr>
<tr><td><kbd>C-c C-g</kbd></td><td>debug under lldb, through dape &mdash; bound only once <code>flan-dape.el</code> is loaded, so <code>flan-mode</code> works without dape installed</td></tr>
<tr><td><kbd>C-c C-d</kbd></td><td>what the running program currently defines</td></tr>
<tr><td><kbd>C-c C-v</kbd></td><td>help on the name at point</td></tr>
@ -1902,6 +1916,35 @@ name</strong>, with 38 programs skipped because they do not compile on either si
no <code>main</code>, or run forever. <code>dune build @x86</code> runs it as part of the
build, so a refusal cannot sit unnoticed.</p>
<p><strong>It serves the editor now, which is what it was written for.</strong>
<code>flan dev --x86</code> and <code>flan reload --x86</code> compile a redefinition
through the hand-written backend, and the round trip is about half what LLVM's is —
<code>llc</code> was nearly all of it, and an assembler is not:</p>
<table>
<tr><th></th><th>LLVM</th><th><code>--x86</code></th></tr>
<tr><td>code generation</td><td><code>llc</code> 4248ms</td><td><code>as</code> 8.08.4ms</td></tr>
<tr><td>link</td><td><code>ld</code> 9.711.4ms</td><td><code>ld</code> 8.99.3ms</td></tr>
<tr><td><code>C-c C-c</code>, at the socket</td><td>6266ms</td><td><strong>2730ms</strong></td></tr>
<tr><td><code>C-x C-e</code></td><td>6061ms</td><td><strong>2425ms</strong></td></tr>
</table>
<p>Both columns were measured on one machine, whose <code>llc</code> is slower than the
one the 1517ms above came from; the comparison is the point and not the absolute
numbers. What the headline hides is where the time went: <code>ld</code> did not move and
is now half the x86 build, and about 9ms of the round trip is the frontend checking the
program again — a seventh of the LLVM loop but a third of this one. The next millisecond
is in the linker rather than the code generator.</p>
<p><strong>It requires <code>--two-process</code>, and refuses the merged daemon by
name.</strong> A merged host is linked <code>-rdynamic</code>, so it exports every Flan
body it contains, and those interpose the prelude of the LLVM-built macro module the
compiler loads into <em>itself</em> to expand macros. An LLVM caller lands in an x86 body
and the process dies during the first expansion, before the program has started. The
marker symbol that refuses a crossed redefinition module does not catch this, and was
never meant to: a macro module deliberately carries none. Hidden visibility on a macro
module's Flan bodies is the fix and is not built.</p>
<p><code>--debug</code> is a third flag beside <code>--dev</code> and the optimisation
level. <code>--dev</code> asks whether you can redefine the program while it runs;
<code>--debug</code> asks whether you can stop it and read it. It emits DWARF, sets