The command list is all eleven, the environment is written down, and one promise is withdrawn
README documented four subcommands of eleven. The seven missing ones are there now, with import-c and generate-c given a worked example each -- they are the most valuable thing here that nothing documented at all. An environment table, checked against the getenv sites rather than against a list: thirteen variables, each with where it is read, plus the llc/clang version coupling that breaks C-c C-c while flan build keeps working. The FLAN_DEV_* set that flan dev hands itself across its own exec is named as internal rather than left looking settable. DISCUSS.md's survey of what the x86 backend had no plan for still listed the whole condition family. x86.ml:1587-1615 lowers all of it and the survey is 104/104; the row is struck through and corrected in place, because other files cite that table by position. prelude.ml promised a core: package at milestone 3. Milestone 3 came and went and the package did not, so the docstring states the limit instead of promising a way out of it. The loader could carry one -- what is missing is the decision about what core: means for a program that imports nothing.
This commit is contained in:
parent
668268b6cc
commit
ccb100d74c
136
README.md
136
README.md
@ -118,17 +118,139 @@ be threaded through every caller. See
|
||||
|
||||
## Commands
|
||||
|
||||
Eleven of them, and the four anyone starts with:
|
||||
|
||||
```text
|
||||
flan check <file.flan> type-check a program
|
||||
flan run <file.flan> [args...] build and run it
|
||||
flan build <file.flan> [-o out] [options] build a native executable
|
||||
flan dev <file.flan> [-s socket] start a live development session
|
||||
flan run <file.flan> [flags] [-- args...] build and run it
|
||||
flan build <file.flan> [-o out] [flags] build a native executable
|
||||
flan dev <file.flan> [-s socket] start a live development session
|
||||
```
|
||||
|
||||
Useful build options include `--debug`, `--sanitize`, `--no-bounds-checks`,
|
||||
`--x86`, and `--target=wasm32-wasi|web`. `run` is native-only; cross-built
|
||||
output should be run with an appropriate WASI runtime or browser. A `.wasm`
|
||||
file is not a tiny native executable in a trench coat.
|
||||
Useful build options include `-O0`…`-O3`, `--debug`, `--sanitize`,
|
||||
`--no-bounds-checks`, `--x86`, and `--target=wasm32-wasi|web`. `run` takes the
|
||||
same build flags and keeps them: everything after `--` goes to the program, and
|
||||
a dash-argument before it that `run` does not offer is refused rather than
|
||||
guessed at. `run` is native-only; cross-built output should be run with an
|
||||
appropriate WASI runtime or browser. A `.wasm` file is not a tiny native
|
||||
executable in a trench coat.
|
||||
|
||||
The other seven. Four print a stage of the pipeline, which is how you find out
|
||||
what the compiler thinks it was given:
|
||||
|
||||
```text
|
||||
flan read <file.flan>... the forms the reader produced
|
||||
flan parse <file.flan>... one line per declaration
|
||||
flan shim <file.flan>... the C a (declare-c ...) generated
|
||||
flan emit <file.flan> [--x86] [--dev] [--debug] [--no-bounds-checks]
|
||||
LLVM IR, or x86-64 assembly under --x86
|
||||
```
|
||||
|
||||
One builds a redefinition module the way `flan dev` does, for scripting the
|
||||
loop without an editor:
|
||||
|
||||
```text
|
||||
flan reload <program.flan> <forms.flan> [-o out.so] [--debug] [--x86]
|
||||
```
|
||||
|
||||
And two are the C binding tools, which are the most useful thing here that
|
||||
nothing else documents.
|
||||
|
||||
### `flan import-c` — read a header, print the bindings it implies
|
||||
|
||||
It builds nothing and writes nothing. It reads a C header (through clang's own
|
||||
parser), prints the `declare-c` forms it would generate, and then prints every
|
||||
function it *refused* and the reason — which is the half that earns its keep,
|
||||
because "this binding is missing" and "this binding cannot exist" are different
|
||||
problems:
|
||||
|
||||
```text
|
||||
$ flan import-c test/headers/sample.h
|
||||
(declare-c set-seed [seed u32] "set_seed")
|
||||
(declare-c add-ints [a i32 b i32] i32 "add_ints")
|
||||
(declare-c name-length [text string] i32 "name_length")
|
||||
...
|
||||
;; 8 imported, 12 refused, of 21 functions in test/headers/sample.h
|
||||
;; refused name-of: name_of returns char *, and a string only crosses as a
|
||||
;; parameter — a C function that returns one returns something Flan has no
|
||||
;; owner for
|
||||
;; refused printf-like: printf_like is variadic, and a wrapper cannot forward
|
||||
;; an argument list it does not know the shape of
|
||||
;; refused file-time: file_time long has a width that differs between this
|
||||
;; project's own targets (64 bits on x86-64, 32 on wasm32), so no single
|
||||
;; Flan type is right for it
|
||||
```
|
||||
|
||||
Hand the package's `.flan` files along with the header and it diffs against
|
||||
them too: a `defstruct` whose layout no longer matches the header's record is
|
||||
named, which is the failure that otherwise shows up as a wrong pixel.
|
||||
|
||||
```text
|
||||
$ flan import-c vendor/raylib/raylib-5.5.h vendor/raylib/*.flan
|
||||
```
|
||||
|
||||
Anything after the header that is not a `.flan` file is passed to clang, so
|
||||
`-I` and `-D` work as they do anywhere else.
|
||||
|
||||
### `flan generate-c` — write those bindings into the package
|
||||
|
||||
The same machinery, committing its answer. It takes a package *directory*, not
|
||||
a header: the header comes from the package's own `headers` file, at the
|
||||
version its `link` file names, because which version may be read is a property
|
||||
of the package and not of the command line. It reads every `.flan` in the
|
||||
directory except the one it writes, so hand-written declarations keep winning,
|
||||
and it writes `generated.flan`.
|
||||
|
||||
```text
|
||||
$ flan generate-c vendor/raylib
|
||||
;; refused get-clipboard-text: GetClipboardText returns char *, and a string
|
||||
;; only crosses as a parameter — ...
|
||||
;; refused load-shader: LoadShader Shader is a struct the package does not
|
||||
;; describe — add a defstruct for it, or keep a hand-written declare-c
|
||||
```
|
||||
|
||||
It exits non-zero and writes nothing if the package and the header disagree.
|
||||
That is the point of it: the committed file is the one thing in the package
|
||||
with no second opinion, so the moment of writing it is the last moment at which
|
||||
the installed library can contradict it.
|
||||
|
||||
## Environment
|
||||
|
||||
Thirteen variables the toolchain reads, none of which has to be set on a
|
||||
machine with clang, LLVM and binutils on `PATH`. Each exists for a machine
|
||||
where the thing is somewhere else, or is the wrong one.
|
||||
|
||||
| Variable | What it replaces | Read at |
|
||||
|---|---|---|
|
||||
| `FLAN_CLANG` | `clang`, which compiles the IR and the runtime's C | `lib/build.ml:14` |
|
||||
| `FLAN_LLC` | `llc`, used only by the live loop | `lib/build.ml:886` |
|
||||
| `FLAN_LD` | `ld`, used only by the live loop | `lib/build.ml:887` |
|
||||
| `FLAN_AS` | `as`, used only by `--x86` | `lib/build.ml:978` |
|
||||
| `FLAN_OBJDUMP` | `objdump`, used only by the disassembly verb | `lib/dev.ml:2018` |
|
||||
| `FLAN_OCAMLFIND` | `ocamlfind`, which a merged `flan dev` needs **at run time** | `lib/dev.ml:2898` |
|
||||
| `FLAN_EMCC` | `emcc`, for `--target=web` | `lib/build.ml:23` |
|
||||
| `FLAN_LIBDIR` | where `flan.cmxa` and `flan.a` are, if not beside the binary | `lib/dev.ml:3187` |
|
||||
| `FLAN_CACHE_DIR` | the object cache, default `$XDG_CACHE_HOME/flan/objcache` | `lib/build.ml:84` |
|
||||
| `FLAN_WASM_SYSROOT` | the wasi-libc sysroot, default `/usr/wasm32-wasi` | `lib/build.ml:239` |
|
||||
| `FLAN_WASM_BUILTINS` | `libclang_rt.builtins-wasm32.a`, which is not in clang's resource directory on Fedora | `lib/build.ml:272` |
|
||||
| `FLAN_WEB_SHELL` | the HTML shell a `--target=web` build wraps the module in | `lib/build.ml:458` |
|
||||
| `FLAN_DEV_LEAKS` | set (to anything) to have a `--dev` build print what it still held at exit | `runtime/flan_dev.c:1084` |
|
||||
|
||||
`${FLAN_RAYLIB_WEB}` is not read by the compiler: it is expanded inside
|
||||
`vendor/raylib/link`, which is where a package writes a linker argument that
|
||||
has to differ per target. Any `${NAME}` in a `link` file expands from the
|
||||
environment and an unset one is refused by name.
|
||||
|
||||
**`llc` and `ld` have to match `clang`.** The live loop does not call the clang
|
||||
driver at all — it goes `llc` + `ld -shared` + `dlopen`, which is what makes
|
||||
`C-c C-c` cost milliseconds. So an `llc` from a different LLVM release than
|
||||
`clang` breaks the dev loop while `flan build` keeps working perfectly, which
|
||||
is a confusing shape of failure to meet without warning.
|
||||
|
||||
Variables beginning `FLAN_DEV_` other than `FLAN_DEV_LEAKS`, plus
|
||||
`FLAN_AGENT_SOCKET` and `FLAN_COMPILER_STAMP`, are internal: `flan dev` sets
|
||||
them across its own `exec` to hand the merged binary what it needs. Setting
|
||||
them by hand is not supported.
|
||||
|
||||
## Checking it
|
||||
|
||||
|
||||
@ -759,13 +759,22 @@ Against `tast.ml`'s `expr_kind`, in four buckets:
|
||||
| **Mechanical** | `While` `Break` `Continue` (the jump patching exists), `Global` `Str` `Zero` `Uninit`, the rest of `place`, `Field` `Deref` `Addr`, `Arr`, `Some_` `None_` `UnwrapSome`, `Match` on a tag |
|
||||
| **Bulky, not hard** | floats — a second register file, SSE encodings, `Cast`'s eight conversions, and the SSE half of the calling convention. Perhaps a third of the total instruction work for a small fraction of the programs |
|
||||
| **Fiddly** | aggregate copy on assignment (a struct `store` *is* the copy `spec-memory.md` requires), `Make` `MakeCase` `CaseField` over the payload blob, `CallPtr`, `FnAddr`'s three cases and the cell load behind `Fnval` |
|
||||
| **No plan** | `Handled` `Signal` `RestartCase` `InvokeRestart` `WithAlloc`, the transfer-channel guard after every call, the landing pad, and `fdefers` on the transfer exit path |
|
||||
| **~~No plan~~ Done** | `Handled` `Signal` `RestartCase` `InvokeRestart` `WithAlloc`, the transfer-channel guard after every call, the landing pad, and `fdefers` on the transfer exit path |
|
||||
|
||||
The last row is the one to take seriously. The spike never emitted a guard or a pad, and the guard is on *every call
|
||||
site* in the real thing — `emit.ml`'s `guard`, `current_pad`, `emit_restart_case` and `emit_with_alloc` are several
|
||||
hundred lines of control flow that a second backend reimplements from the spec rather than copies. Conditions are not
|
||||
an advanced feature to defer: `spec-conditions.md` is load-bearing in the prelude already.
|
||||
|
||||
**Correction, and it matters because this row argues the opposite of the truth.** The last row was written before the
|
||||
backend existed and it is no longer the state of anything. `x86.ml:1587-1615` lowers the whole condition family —
|
||||
`Handled`, `Signal`, `RestartCase`, `InvokeRestart`, `WithAlloc`, the per-call-site transfer guard, the landing pad and
|
||||
the defers on the transfer exit — and the parity survey is **104 / 104 MATCH, 0 DIFFER**. Item 16 below is where that
|
||||
was built and item 17 is where conditions and bounds checks were taken through the whole corpus rather than a sample;
|
||||
this row is kept struck through rather than deleted because other files cite this table by position. Anyone reading it
|
||||
for a production decision should read items 16, 17 and 18 instead: the second backend refuses nothing this one does,
|
||||
and "no plan" has not been true since.
|
||||
|
||||
### Question 3 — the SysV boundary, and the obstacle nobody named
|
||||
|
||||
**The C boundary is the easy half, and `BUILT.md` is why.** `check.ml` rejects an aggregate in a `declare` signature
|
||||
|
||||
@ -5,9 +5,17 @@
|
||||
above it is Flan. That is what keeps a second backend cheap — a primitive
|
||||
is the only thing implemented twice.
|
||||
|
||||
It lives here as a string rather than as a file because there is no package
|
||||
loader yet; at milestone 3 it becomes an ordinary [core:] package and this
|
||||
module goes away. The acceptance programs may call anything defined here.
|
||||
It lives here as a string rather than as a file, and every program gets it
|
||||
prepended by [Check.build_program] whether it asked for one or not. That is
|
||||
a real limit and it is stated as one: the prelude cannot be read as Flan,
|
||||
extended, or replaced without rebuilding the compiler. There *is* a package
|
||||
loader now — [lib/load.ml], and [vendor/raylib] is a package that uses it —
|
||||
so the obstacle is no longer the mechanism. What is missing is the decision
|
||||
about what a [core:] package would mean for a program that imports nothing,
|
||||
and until that is made this docstring does not promise one. (It used to say
|
||||
the prelude became a [core:] package "at milestone 3". Milestone 3 came and
|
||||
went; the package did not.) The acceptance programs may call anything
|
||||
defined here.
|
||||
|
||||
No printing function is here at all any more. [print] and [println] are
|
||||
the whole printing surface, and neither is a function: both are
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user