flan/spike/embed/run.sh
Joseph Ferano d272a5b1e5 Six probes for whether the OCaml compiler can live in the game's process
Item 12 asks five questions and says to answer them with a spike rather than a
rewrite. spike/embed/ is that spike: one script, six binaries, each one built to
fail loudly at the thing it is asking about. It is deliberately not a dune
target -- the root dune only excludes old-ocaml/, so a dune file here would land
in @default and make the spike part of the build. It drives ocamlfind and clang
by hand against the flan.cmxa dune already produces.

The probes, in the order they would kill the idea: the smallest possible link, a
C main() reaching one OCaml function; the whole compiler linked in and doing
real work; the same again with lib/dynload_stubs.c from the unmerged dlopen
branch, because that is the only C the compiler itself is built from; the game
keeping the main thread while caml_startup happens on a pthread beside it; the
SIGSEGV disposition read on both sides of caml_startup; and an 8 MiB arena
checked byte for byte across a compaction.

No result is written down yet. This is the apparatus.
2026-09-12 20:27:45 +07:00

77 lines
3.5 KiB
Bash

#!/usr/bin/env bash
# Spike: what it costs to link the OCaml compiler into a native Flan dev build.
#
# Deliberately NOT a dune target. The root `dune` only excludes old-ocaml/, so a
# dune file here would land in @default and make the spike part of the build.
# Instead this drives ocamlfind and clang by hand, against the flan.cmxa that
# dune already produces. Run it from anywhere: bash spike/embed/run.sh
set -u
here=$(cd "$(dirname "$0")" && pwd)
root=$(cd "$here/../.." && pwd)
cd "$here" || exit 1
OCAMLLIB=$(ocamlopt -where)
CAMLINC="-I$OCAMLLIB"
# OCaml 5.2's marshaller is compressed, so -output-complete-obj pulls in zstd.
SYSLIBS="-lm -lpthread -ldl -lzstd"
step() { printf '\n=== %s ===\n' "$1"; }
# ---------------------------------------------------------------- 1. smallest
step "1. smallest link: C main() -> caml_startup -> OCaml callback"
ocamlfind ocamlopt -package unix -linkpkg -output-complete-obj \
-o embed1.o hello_ml.ml || exit 1
clang $CAMLINC harness1.c embed1.o -o spike1 $SYSLIBS || exit 1
./spike1 || echo "spike1 FAILED"
ls -l spike1 | awk '{print "spike1 size: " $5 " bytes"}'
# ------------------------------------------------------- 2. the real compiler
step "2. link the whole flan compiler (flan.cmxa) into a C binary"
CMXA="$root/_build/default/lib/flan.cmxa"
if [ ! -f "$CMXA" ]; then
echo "no $CMXA -- run 'dune build --root .' first"; exit 1
fi
ocamlfind ocamlopt -package unix -linkpkg -output-complete-obj \
-I "$root/_build/default/lib/.flan.objs/byte" \
-I "$root/_build/default/lib/.flan.objs/native" \
-o embed2.o "$CMXA" whole_ml.ml || exit 1
clang $CAMLINC harness2.c embed2.o -o spike2 $SYSLIBS || exit 1
(cd "$root" && "$here/spike2" test/programs/edn.flan) || echo "spike2 FAILED"
ls -l spike2 | awk '{print "spike2 size: " $5 " bytes"}'
ls -l "$root/_build/default/bin/main.exe" | awk '{print "main.exe size: " $5 " bytes"}'
clang $CAMLINC baseline.c -o baseline
ls -l baseline | awk '{print "bare C baseline: " $5 " bytes"}'
# ------------------------------------------------------------- 3. with stubs
step "3. the same, with the project's own C stubs compiled in"
ocamlfind ocamlopt -package unix -linkpkg -output-complete-obj \
-I "$root/_build/default/lib/.flan.objs/byte" \
-I "$root/_build/default/lib/.flan.objs/native" \
-o embed3.o "$CMXA" stubs_ml.ml dynload_stubs.c || exit 1
clang $CAMLINC harness3.c embed3.o -o spike3 $SYSLIBS || exit 1
./spike3 || echo "spike3 FAILED"
# ------------------------------------------ 4. threads: game owns main thread
step "4. threads: C main runs the 'game loop', OCaml starts on another thread"
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 embed4.o "$CMXA" thread_ml.ml || exit 1
clang $CAMLINC harness4.c embed4.o -o spike4 $SYSLIBS || exit 1
(cd "$root" && "$here/spike4" test/programs/edn.flan) || echo "spike4 FAILED"
# ------------------------------------------------------------ 5. signals
step "5. signals: who owns SIGSEGV across caml_startup"
clang $CAMLINC harness5.c embed2.o -o spike5 $SYSLIBS || exit 1
./spike5 || echo "spike5 exited nonzero"
# ------------------------------------------------------- 6. GC vs raw memory
step "6. GC: does a compaction move or touch a C-owned arena"
ocamlfind ocamlopt -package unix -linkpkg -output-complete-obj \
-o embed6.o gc_ml.ml || exit 1
clang $CAMLINC harness6.c embed6.o -o spike6 $SYSLIBS || exit 1
./spike6 || echo "spike6 FAILED"
printf '\nspike: done\n'