flan/spike/embed/merged.sh
Joseph Ferano 2e9b29e549 One binary that is both a Flan program and the compiler that built it
The first six probes each proved a piece. merged.sh puts them together: the
program's @main is renamed out of the way, a C main takes the main thread and
runs it there, caml_startup happens on a thread beside it, and clang links the
lot -- the emitted program object, flan_rt.c, flan_dev.c, flan_agent.c and the
whole compiler as one -output-complete-obj. It runs, and the compiler inside it
compiles the very source the program was built from.

Nothing is wired up. The two halves share an address space and do not speak.
That is the point: the question was whether they can, not what they would say.

sig.sh and symbols.sh answer the two questions the first pass got wrong or
skipped. The SIGSEGV reading in harness5.c was taken at the wrong moment --
OCaml 5 starts domains after caml_startup returns, so the disposition had to be
read from inside the runtime, and against a plain ocamlopt executable as a
control. symbols.sh is the hazard nobody looks for until the link fails: four
.c files that are compiled into two different processes today, and the OCaml
runtime, all landing in one link.
2026-09-12 20:32:51 +07:00

67 lines
2.6 KiB
Bash

#!/usr/bin/env bash
# Step 8: the thing the whole spike is really asking about -- ONE binary that
# is both a compiled Flan program and the OCaml compiler, with clang doing the
# final link.
#
# Everything before this proved a piece. This proves the shape: the Flan
# program's own main() is renamed out of the way, a C main() takes the main
# thread and runs the program there, and caml_startup happens on a side thread
# beside it. That is exactly item 11's inversion, built for real.
#
# It is NOT the merged architecture -- nothing is wired up, the compiler and the
# program do not talk. It is a link and a size and a startup number.
set -u
here=$(cd "$(dirname "$0")" && pwd)
root=$(cd "$here/../.." && pwd)
src=${1:-test/programs/edn.flan}
cd "$root" || exit 1
out=$(mktemp -d); trap 'rm -rf "$out"' EXIT
FLAN=_build/default/bin/main.exe
OCAMLLIB=$(ocamlopt -where)
SYSLIBS="-lm -lpthread -ldl -lzstd"
echo "program: $src"
# 1. The Flan program, as it is built today, for the baseline sizes.
"$FLAN" build "$src" -o "$out/rel" || exit 1
"$FLAN" build "$src" --dev -o "$out/dev" || exit 1
# 2. The same program as an object, with its main renamed so a C main can own
# the process. Emit writes @main literally; sed is enough to move it.
"$FLAN" emit "$src" --dev > "$out/prog.ll" || exit 1
sed -i 's/define i32 @main(/define i32 @flan_program_main(/' "$out/prog.ll"
grep -q 'define i32 @flan_program_main(' "$out/prog.ll" || {
echo "could not find @main in the emitted IR -- adjust the rename"; exit 1; }
clang -c -x ir "$out/prog.ll" -o "$out/prog.o" || exit 1
# 3. The runtime the program needs, and the agent beside it.
clang -c -O2 runtime/flan_rt.c -o "$out/rt.o" || exit 1
clang -c -O2 runtime/flan_dev.c -o "$out/dev.o" || exit 1
clang -c -O2 vendor/agent/flan_agent.c -o "$out/ag.o" || exit 1
# 4. The whole OCaml compiler as one object.
ocamlfind ocamlopt -thread -package unix,threads.posix -linkpkg \
-output-complete-obj \
-I "$root/_build/default/lib/.flan.objs/byte" \
-I "$root/_build/default/lib/.flan.objs/native" \
-o "$out/compiler.o" "$root/_build/default/lib/flan.cmxa" \
"$here/thread_ml.ml" || exit 1
# 5. One link. clang, as the project already does it.
clang -I"$OCAMLLIB" "$here/merged_main.c" "$out/prog.o" "$out/rt.o" "$out/dev.o" \
"$out/ag.o" "$out/compiler.o" -o "$out/merged" $SYSLIBS || exit 1
echo
echo "sizes:"
for f in rel dev merged; do
printf ' %-30s %9d bytes\n' "$f" "$(stat -c%s "$out/$f")"
done
printf ' %-30s %9d bytes\n' "what the compiler adds to a dev build" \
"$(( $(stat -c%s "$out/merged") - $(stat -c%s "$out/dev") ))"
echo
echo "running the merged binary:"
"$out/merged" "$src"
echo "exit: $?"