flan/lib/dune
Joseph Ferano 7d4bec521e A value carries its own type, and the heap under it collects
Milestone 1 of dynamic-by-default, the runtime half: NaN-boxed values in one
machine word, a mark-sweep heap, and the operations over them.

A double is itself, which is what a language with a physics loop and a float
calculator in its corpus wants; everything else hides in the quiet-NaN space,
three tag bits and a 48-bit payload that is exactly an x86-64 user pointer.
The negative-NaN collision is answered by canonicalising every NaN on the way
in, which flan_rt.c had already decided was the right thing to print. An i64
past the payload goes on the heap rather than becoming a 48-bit integer with a
64-bit name.

The collector is mark-sweep and nothing else -- no generation, no barrier, no
free list -- because the answer to wanting it faster is to type the program.
Roots are pushed, not scanned: NaN-boxing makes a conservative guess wrong in
both directions, and flan_dev.c's frame chain is the precedent. A fixed ring
of the last sixty-four allocations is marked unconditionally, which closes the
window where an expression with two constructors in it can collect its own
first result before the compiler has rooted either.

A type mismatch traps rather than aborting, through a flan_trap exported from
flan_rt.c so it takes the same path the six existing traps take: parked for
inspection in a dev session, dead where it stands otherwise. The sentence
names the operation, both tags as words, and both values.

flan_dyn.c is its own translation unit and nothing in the release runtime
names a symbol in it, so a program with no dyn operation links no collector
and --no-gc can be file-level selection rather than an argument with the
linker.

docs/SPIKE-DYNAMIC.md carries the argument. test/dyn_ops.c drives every
operation and all twenty-four refusals from C, the way dev_limits.c does,
including a million allocations against a hundred live and the control that
says an unrooted object really is reclaimed.
2026-09-19 05:52:47 +07:00

62 lines
2.9 KiB
Plaintext

(library
(name flan)
(libraries unix)
; -linkall because lib/macro.ml installs itself into Parse.expander at module
; initialisation and nothing references it. Without it the linker drops the
; module from every executable that does not name it -- bin/main.exe among
; them -- and a program calling a macro would fail with an unknown name
; instead of expanding. The alternative was an install call at every entry
; point, including ones in files this cannot reach.
(library_flags (-linkall))
; Running a macro means dlopening it into the compiler, and OCaml has no
; dlopen for ELF -- Dynlink loads OCaml. These are the stubs for it, and the
; only C the compiler itself is built from. See lib/dynload_stubs.c.
(foreign_stubs
(language c)
(names dynload_stubs))
; No (c_library_flags (-ldl)): since glibc 2.34 dlopen lives in libc itself
; and libdl is a stub, and naming it breaks the merged build -- the partial
; link -output-complete-obj performs cannot resolve -ldl, so `flan dev` in
; one process fails at the link with "cannot find -ldl" while the ordinary
; build is unaffected. Add it back only with a platform guard.
)
; The host shim is Flan's, not the user's, so the compiler carries it rather
; than looking for it in an install directory. Generated from the real .c files
; so there is only ever one copy to edit. flan_dev.c goes into a dev build
; only — it is the run-time name lookup a REPL needs and a release build has
; no use for.
;
; flan_dyn.c is the third: the dynamic-value runtime, tagged values and the
; mark-sweep heap under them. It is carried the same way and for the same
; reason, and it is a *separate* string rather than being appended to
; [source] because it has to stay a separate translation unit — a program that
; calls no dyn operation references no symbol in it, which is what lets a
; build refuse to link it at all. See docs/SPIKE-DYNAMIC.md.
(rule
(target runtime_src.ml)
(deps
%{workspace_root}/runtime/flan_rt.c
%{workspace_root}/runtime/flan_dev.c
%{workspace_root}/runtime/flan_dyn.c
%{workspace_root}/runtime/flan_dyn.h)
(action
(with-stdout-to
runtime_src.ml
(progn
(echo "let source = {c|\n")
(cat %{workspace_root}/runtime/flan_rt.c)
(echo "|c}\n\nlet dev_source = {c|\n")
(cat %{workspace_root}/runtime/flan_dev.c)
(echo "|c}\n\nlet dyn_source = {c|\n")
(cat %{workspace_root}/runtime/flan_dyn.c)
; And the header, which is *not* compiled: it is written into the
; directory each translation unit is compiled in, so that a package's C —
; and test/dyn_ops.c — can include it. flan_dyn.c does not include it and
; declares its own prototypes instead, because the object cache is keyed
; on the source text and a header change would not invalidate an object
; compiled against the old one.
(echo "|c}\n\nlet dyn_header = {c|\n")
(cat %{workspace_root}/runtime/flan_dyn.h)
(echo "|c}\n")))))