ludic/selfhost/backend/emit_decl.ludic
Orkuncakilkaya a8d54e9878 fence (25.1): every allocation goes through the fence - sites, frame judging, census, callers
Every allocation the compiler emits goes through @lp_malloc/@lp_calloc/@lp_realloc/@lp_free, and a
Ludic-level one first stores its site (function, file, line, kind) in @lp_site. Off, that is one load
and a predictable branch (30 M allocations: 0.87-0.91 s against 0.87-0.90 s on leaks2).

On (the default in a headless build, and windowed under R3D_DEV), tracking starts at the first frame
on its own and judging once R3D_ALLOC_WARM frames in a row kept nothing (600) or R3D_ALLOC_WARM_MAX
after (re)start; Mem.play()/Mem.rewarm() sends a load back to its warm-up. A judged frame that ends
holding more than it began with is reported by site with its callers (the unwinder, taken only once
judging) and fails the run with exit 86 (R3D_ALLOC_FENCE=off|count|warn|fail). R3D_ALLOC_CENSUS
writes the totals and top sites at exit. The build's defaults are --fence=, --fence-warm=,
--fence-census= or a fence line in the program's package.ludic; the environment overrides them.

The runtime is IR (emit_fence_ir.ludic, generated from a template); tracking is a side table in one
calloc'd region, so no block carries a header and pointers crossing to natives stay safe. Examples
alloc_fence, alloc_fence_leak and alloc_fence_auto with cases in ludic-dev test; reseeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:35:29 +03:00

463 lines
21 KiB
Text

# emit_decl.ludic — functions, main, and the whole-program driver. A function's
# body is built into a scratch buffer so entry-block allocas can be spliced in
# ahead of it (see emit_alloca). Returns go through a %retval slot + `ret` label.
function emit_params_sig(d: Node) -> void {
var i = 0
while i < len(d.kids) {
if i > 0 { emit(", ") }
emit(llty(d.kids[i].ty)); emit(" %arg_"); emit(d.kids[i].s)
i += 1
}
}
function emit_fn(d: Node) -> void {
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
det_enter(d.s)
fence_enter(d.s)
g_cur_scene = null # a function belongs to no scene: `become` leaves the live one
ret_ty = d.ty
let fbody = buf_new()
falloc = buf_new()
let saved = code
code = fbody
let rl = llty(ret_ty)
if not (rl == "void") { buf_puts(falloc, " %retval = alloca "); buf_puts(falloc, rl); buf_puts(falloc, "\n") }
# params: store each incoming argument into a stack slot
var i = 0
while i < len(d.kids) {
let p = d.kids[i]
let slot = emit_alloca(llty(p.ty))
emit(" store "); emit(llty(p.ty)); emit(" %arg_"); emit(p.s); emit(", ptr "); emit(slot); emit("\n")
loc_push(p.s, slot, p.ty)
i += 1
}
emit_block(d.a)
if not (rl == "void") and not block_ends(d.a) { perr(`function '{d.s}' can reach its end without returning a {ret_ty}`) }
if not g_term { emit(" br label %ret\n") }
emit("ret:\n")
if (rl == "void") { emit(" ret void\n") }
else { let r = emit_bind(`load {rl}, ptr %retval`); emit(" ret "); emit(rl); emit(" "); emit(r); emit("\n") }
code = saved
emit("define "); emit(rl); emit(" @fn_"); emit(d.s); emit("("); emit_params_sig(d); emit(") {\nentry:\n")
emit(buf_str(falloc))
emit(buf_str(fbody))
emit("}\n\n")
g_det_ctx = ""
}
function emit_main(d: Node) -> void {
fence_enter("entry")
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
ret_ty = "int"
let fbody = buf_new()
falloc = buf_new()
let saved = code
code = fbody
buf_puts(falloc, " %retval = alloca i32\n")
emit(" store i32 %argc, ptr @L_argc\n")
emit(" store ptr %argv, ptr @L_argv\n")
emit(" store i32 0, ptr %retval\n")
# boot the runtime like the auto-loop / test runner do, so an `entry`-driven
# game that renders (drives tick_render, draws, uses the engine light pass) has
# its framebuffer allocated. Headless it only allocates — no window, no output —
# so a non-rendering entry game is unchanged.
emit(" call void @L_init_runtime()\n")
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") }
emit(" call void @L_init_globals()\n")
emit_block(d.a)
if not g_term { emit(" br label %ret\n") }
emit("ret:\n")
let r = emit_bind("load i32, ptr %retval")
emit(" ret i32 "); emit(r); emit("\n")
code = saved
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
emit(buf_str(falloc))
emit(buf_str(fbody))
emit("}\n")
}
# ---- the testing framework -------------------------------------------------
# A `test "name" { ... }` block lowers to a void function; `expect*` assertions
# inside it flip @L_test_fail. A synthetic runner @main runs every test, prints
# `ok - name` / `FAIL - name`, a summary, and exits non-zero if any failed.
# `./tests "name"` runs only the test with exactly that name (what an editor's
# per-test run button passes); naming a test that does not exist is a failure.
# one test block -> a void function @fn__test_<idx> (mirrors emit_fn's shape).
function emit_test_fn(t: Node, idx: int) -> void {
fence_enter(`test {t.s}`)
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0
ret_ty = "void"
let fbody = buf_new()
falloc = buf_new()
let saved = code
code = fbody
emit_block(t.a)
if not g_term { emit(" br label %ret\n") }
emit("ret:\n ret void\n")
code = saved
emit("define void @fn__test_"); emit(itoa(idx)); emit("() {\nentry:\n")
emit(buf_str(falloc))
emit(buf_str(fbody))
emit("}\n\n")
}
# emit every test body plus the runner @main that drives them.
function emit_test_runner() -> void {
emith("@L_test_fail = internal global i32 0\n")
emith("@.fmt_test_sum = private unnamed_addr constant [28 x i8] c\"== %d passed, %d failed ==\\0A\\00\"\n")
emith("@.fmt_test_none = private unnamed_addr constant [20 x i8] c\"no test named \\22%s\\22\\0A\\00\"\n")
var i = 0
while i < len(g_tests) { emit_test_fn(g_tests[i], i); i += 1 }
if g_uses_expect { emith("@.fmt_expect = private unnamed_addr constant [22 x i8] c\"%s (got %d, want %d)\\0A\\00\"\n") }
if g_uses_expect_str { emith("@.fmt_expect_str = private unnamed_addr constant [26 x i8] c\"%s (got \\22%s\\22, want \\22%s\\22)\\0A\\00\"\n") }
if g_uses_expect_fp { emith("@.fmt_expect_fp = private unnamed_addr constant [22 x i8] c\"%s (got %g, want %g)\\0A\\00\"\n") }
ll_t = 0; ll_lbl = 0
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
emit(" store i32 %argc, ptr @L_argc\n")
emit(" store ptr %argv, ptr @L_argv\n")
emit(" %failed = alloca i32\n store i32 0, ptr %failed\n")
emit(" %ran = alloca i32\n store i32 0, ptr %ran\n")
# argv[1], when given, names the one test to run
emit(" %filter = alloca ptr\n store ptr null, ptr %filter\n")
let lhas = lbl("tfilt"); let lstart = lbl("tbegin")
let hasf = emit_bind("icmp sgt i32 %argc, 1")
emit(` br i1 {hasf}, label %{lhas}, label %{lstart}\n`)
emit(`{lhas}:\n`)
let fslot = emit_bind("getelementptr ptr, ptr %argv, i64 1")
let fval = emit_bind(`load ptr, ptr {fslot}`)
emit(` store ptr {fval}, ptr %filter\n`)
emit(` br label %{lstart}\n`)
emit(`{lstart}:\n`)
# `--list` names every test, one a line: `ludic test` runs each in a process of its own
let llist = lbl("tlist"); let lrun0 = lbl("trun0")
let fl0 = emit_bind("load ptr, ptr %filter")
let hasf0 = emit_bind(`icmp ne ptr {fl0}, null`)
let lcmp0 = lbl("tlcmp")
emit(` br i1 {hasf0}, label %{lcmp0}, label %{lrun0}\n`)
emit(`{lcmp0}:\n`)
let listflag = emit_str_const("--list")
let lc = emit_bind(`call i32 @strcmp(ptr {fl0}, ptr {listflag})`)
let islist = emit_bind(`icmp eq i32 {lc}, 0`)
emit(` br i1 {islist}, label %{llist}, label %{lrun0}\n`)
emit(`{llist}:\n`)
var li = 0
while li < len(g_tests) {
let nm = emit_str_const(g_tests[li].s)
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr {nm})\n`)
li += 1
}
emit(" ret i32 0\n")
emit(`{lrun0}:\n`)
emit(" call void @L_init_runtime()\n")
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
if has_ecs() { emit(" call void @rt_init()\n") }
emit(" call void @L_init_globals()\n")
i = 0
while i < len(g_tests) {
let okmsg = emit_str_const(`ok - {g_tests[i].s}`)
let failmsg = emit_str_const(`FAIL - {g_tests[i].s}`)
let tname = emit_str_const(g_tests[i].s)
let lcmp = lbl("tcmp"); let lrun = lbl("trun"); let lskip = lbl("tskip")
let fl = emit_bind("load ptr, ptr %filter")
let nof = emit_bind(`icmp eq ptr {fl}, null`)
emit(` br i1 {nof}, label %{lrun}, label %{lcmp}\n`)
emit(`{lcmp}:\n`)
let cmp = emit_bind(`call i32 @strcmp(ptr {fl}, ptr {tname})`)
let same = emit_bind(`icmp eq i32 {cmp}, 0`)
emit(` br i1 {same}, label %{lrun}, label %{lskip}\n`)
emit(`{lrun}:\n`)
let rn = emit_bind("load i32, ptr %ran")
let rn1 = emit_bind(`add i32 {rn}, 1`)
emit(` store i32 {rn1}, ptr %ran\n`)
emit(" store i32 0, ptr @L_test_fail\n")
# 0.S: every state fresh for each test - the states are what changes, and they are made here
if i > 0 { emit(" call void @L_init_globals()\n") }
emit(` call void @fn__test_{itoa(i)}()\n`)
let f = emit_bind("load i32, ptr @L_test_fail")
let isbad = emit_bind(`icmp ne i32 {f}, 0`)
let lpass = lbl("tpass"); let lfail = lbl("tfail"); let ldone = lbl("tdone")
emit(` br i1 {isbad}, label %{lfail}, label %{lpass}\n`)
emit(`{lpass}:\n`)
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr {okmsg})\n`)
emit(` br label %{ldone}\n`)
emit(`{lfail}:\n`)
let cf = emit_bind("load i32, ptr %failed")
let cf1 = emit_bind(`add i32 {cf}, 1`)
emit(` store i32 {cf1}, ptr %failed\n`)
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr {failmsg})\n`)
emit(` br label %{ldone}\n`)
emit(`{ldone}:\n`)
emit(` br label %{lskip}\n`)
emit(`{lskip}:\n`)
i += 1
}
# a filter that matched nothing is a typo, not a pass
let lnone = lbl("tnone"); let lsum = lbl("tsum")
let ran = emit_bind("load i32, ptr %ran")
let fl = emit_bind("load ptr, ptr %filter")
let hasfilter = emit_bind(`icmp ne ptr {fl}, null`)
let noran = emit_bind(`icmp eq i32 {ran}, 0`)
let missed = emit_bind(`and i1 {hasfilter}, {noran}`)
emit(` br i1 {missed}, label %{lnone}, label %{lsum}\n`)
emit(`{lnone}:\n`)
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_test_none, ptr {fl})\n`)
emit(" ret i32 1\n")
emit(`{lsum}:\n`)
let totf = emit_bind("load i32, ptr %failed")
let passed = emit_bind(`sub i32 {ran}, {totf}`)
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_test_sum, i32 {passed}, i32 {totf})\n`)
let allok = emit_bind(`icmp eq i32 {totf}, 0`)
let rc = emit_bind(`select i1 {allok}, i32 0, i32 1`)
emit(` ret i32 {rc}\n}\n`)
}
# two `function`s with one name would collide in the object file; say so in
# source terms (and name both files) instead of leaving it to the IR assembler.
function check_duplicate_fns() -> void {
let k = new []pointer # by name, in a table: a pair of loops was quadratic
let v = new []Node
ck_tab_init(k, v)
var i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_FN {
let first = ck_tab_get(k, v, d.s)
if first != null {
g_err_file = d.file; g_err_line = d.line
perr(`function '{d.s}' is defined twice (first in {first.file}:{itoa(first.line)})`)
}
ck_tab_put(k, v, d.s, d)
}
i += 1
}
}
# the program's own globals and types, each name once: the first definition used to win
# silently, and a game read an arrow key as a binding slot's number for months because two files
# both said KEY_LEFT. Declarations spliced in from the runtime are the runtime's business.
function decl_group(k: int) -> int {
if k == N_VAR or k == N_CONST or k == N_ENUM { return 1 }
if k == N_STRUCT or k == N_COMP or k == N_EVENT { return 2 }
return 0
}
# when one of the two is a package's export: say so, and what to do - exported names are one
# namespace, and a module's private one of the same spelling would not clash
function dup_whose(first: Node, d: Node) -> pointer {
var pk = pkg_of_file(first.file)
if pk == "" { pk = pkg_of_file(d.file) }
if pk == "" { return "" }
if not (pkg_of_file(first.file) == "") and not (pkg_of_file(d.file) == "") { return "" }
return `: {pk} exports it, and exported names are one namespace - rename this one, or declare it without export inside a module of your own`
}
function check_duplicate_decls() -> void {
let k = new []pointer
let v = new []Node
ck_tab_init(k, v)
let n = g_prog_user_end
var i = 0
while i < n and i < len(prog) {
let d = prog[i]
let g = decl_group(d.kind)
if g > 0 {
let key = `{itoa(g)}:{d.s}`
let first = ck_tab_get(k, v, key)
if first != null {
g_err_file = d.file
g_err_line = d.line
perr(`'{d.s}' is defined twice (first in {first.file}:{itoa(first.line)}){dup_whose(first, d)}`)
}
ck_tab_put(k, v, key, d)
}
i += 1
}
}
function emit_program() -> void {
check_duplicate_fns()
check_duplicate_decls()
ix_start() # the lookups by name, as tables from here on
head = buf_new()
code = buf_new()
g_uses_str = false
g_uses_intstr = false
g_uses_fpstr = false
g_fp_decls = new []pointer
g_prevals = new []Val
g_uses_strslice = false
g_uses_loopback = false
g_uses_expect = false
g_uses_panic = false
g_fprintf_declared = false
g_uses_result = false
g_uses_option = false
g_uses_quit = false
g_uses_world_despawn = false # #84: set when a world_despawn call is emitted (below)
g_cov_lines = new []int
g_cov_active = true
fence_reset()
loc_name = new []pointer; loc_reg = new []pointer; loc_ty = new []pointer; loc_mut = new []int
brk_lbl = new []pointer; cnt_lbl = new []pointer
self_stk = new []pointer
mach_stk = new []Node
emit_header()
emit_extern_decls()
if has_ecs() { emit_ecs_storage() }
var i = 0
while i < len(prog) {
if prog[i].kind == N_FN {
g_cov_active = (i < g_prog_user_end) # don't instrument spliced runtime functions
emit_fn(prog[i])
g_cov_active = true
}
i += 1
}
emit_global_init_fn() # @L_init_globals: the non-constant `var` initializers
if len(g_events) > 0 { emit_event_fns() } # EV0: @ev_<E> event-dispatch functions
if has_ecs() { emit_world_table() } # EV2/EV8: the mod reflection ABI (powers Query.* / Reflect.* / engine systems, and lets a binary module (#64) link against a shared world). Unused defs dead-strip at -O2, so a game that touches none is output-identical.
if has_ecs() { emit_ecs_allocator(); emit_snapshot() }
if has_ecs() { emit_net() } # N2/N3: @Sync serializers + @Owned storage (gated internally)
if has_ui() { emit_ui_build() }
if len(g_tests) > 0 { # a test file: synth a runner @main
if has_systems() { emit_game_defs() }
emit_test_runner()
}
else { if has_systems() and has_entry() { # N5: game owns its loop via `entry`
emit_game_defs() # system fns, hooks, tick helpers
i = 0
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i += 1 }
}
else { if has_systems() { emit_game_main() } # the auto frame loop
else {
i = 0
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i += 1 }
} } }
# quit() in a program with no frame loop (a package's code in a test program): the flag it sets
if g_uses_quit and not has_ecs() { emith("@L_running = internal global i32 1\n") }
if g_uses_world_despawn { emit_world_despawn_fn() } # #84: @fn_world_despawn, after all World.despawn / esys_bounds-kill uses are seen
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
if g_uses_str { emit_str_prelude() } # @lp_str_eq / @lp_str_concat, after all uses are seen
if g_uses_intstr { emit_int_str() }
if g_uses_fpstr { emit_fp_str_fn() } # @lp_int_str, for string(int) in interpolation
if g_uses_longstr { emit_long_str() } # @lp_long_str, for string(long) / long interpolation
if g_uses_strslice { emit_str_slice() } # @lp_str_slice, for s[a..b]
if g_uses_mathrt { emit_math_prelude() } # @lp_fx_sqrt / @lp_fx_sin + the sine table
if g_uses_textrt { emit_text_prelude() } # @lp_str_upper/lower/trim/repeat/pad builders
if g_uses_textrt2 { emit_text2_prelude() } # @lp_str_replace/join/split builders
if g_uses_hashrt { emit_hash_prelude() } # @lp_hash_fnv1a / @lp_hash_crc32 byte hashers
if g_uses_cryptort { emit_crypto_prelude() } # @lp_sha256_hex / @lp_hmac_sha256_hex + constant-time compare + CSPRNG
if g_uses_uuidrt { emit_uuid_prelude() } # @lp_uuid_v4 / @lp_uuid_v7 / parse / equals (over the crypto CSPRNG)
if g_uses_noisert { emit_noise_prelude() } # @lp_noise_value2/perlin2/simplex2/fbm2/cellular2 (Q16.16)
if g_uses_logrt { emit_log_prelude() } # @L_log_level + @lp_log_emit (levelled stderr sink)
if g_uses_osrt { emit_os_prelude() } # @lp_os_args/platform/arch/save_dir/... (libc env + uname)
if g_uses_pid and (not g_target_win) { emith("declare i32 @getpid()\n") }
if g_uses_heap { emit_os_heap() }
if g_uses_unicodert { emit_unicode_prelude() } # @lp_uni_len/valid/decode/case/truncate/grapheme (UTF-8)
if g_uses_fsrt { emit_fs_prelude() } # @fn_fs_*/fn_path_*/fn_mime_* (libc + string ops)
if g_uses_pak { emit_pak_prelude() } # @lp_pak_* asset packs + the pre-main mount ctor
if g_uses_datert { emit_datetime_prelude() } # @lp_days_from_civil / @lp_civil_from_days conversions
if g_uses_panic { # panic/assert: located abort to stderr
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true }
emith("@.fmt_panic = private unnamed_addr constant [6 x i8] c\"%s%s\\0A\\00\"\n")
}
if g_uses_bounds { # a slice index out of range: located abort
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true }
emith("@.fmt_bounds = private unnamed_addr constant [14 x i8] c\"%s%d, len %d\\0A\\00\"\n")
}
if g_uses_result { emith("%Result = type { i32, i32, ptr }\n") } # issue #46: ok/err/try value
if g_uses_option { emith("%Option = type { i32, i32 }\n") } # issue #53: some/none value
emit_fence_runtime() # 25.1: the allocation fence and its site tables
emit_cov_runtime() # issue #45: --coverage tables + exit dump
if g_emit_module { emit_module_glue() } # issue #64: binary-module host-ABI declares + load-time registration ctor
}
# issue #45: the line-coverage runtime. Emits the static line table, a parallel
# hit-counter array, and @cov_dump — a function registered with atexit (via an
# LLVM global constructor) that writes `<line> <hits>` rows to the file named by
# $LUDIC_COVERAGE (default "ludic.cov"). All gated behind --coverage, so a normal
# build emits none of this and stays byte-identical.
function emit_cov_runtime() -> void {
if not g_coverage { return }
let n = len(g_cov_lines)
if n == 0 { return }
let sn = itoa(n)
# the line table + zeroed hit counters (module globals)
emith("@L_cov_lines = internal global ["); emith(sn); emith(" x i32] [")
var i = 0
while i < n {
if i > 0 { emith(", ") }
emith("i32 "); emith(itoa(g_cov_lines[i]))
i += 1
}
emith("]\n")
emith("@L_cov_hits = internal global ["); emith(sn); emith(" x i32] zeroinitializer\n")
emith(`@L_cov_n = internal global i32 {sn}\n`)
# the source name, the env-var name, the default path, fopen mode, and row format
let fnc = emit_str_const(g_src_name)
let envc = emit_str_const("LUDIC_COVERAGE")
let defc = emit_str_const("ludic.cov")
let modec = emit_str_const("w")
emith("@.cov_filefmt = private unnamed_addr constant [9 x i8] c\"FILE %s\\0A\\00\"\n")
emith("@.cov_rowfmt = private unnamed_addr constant [7 x i8] c\"%d %d\\0A\\00\"\n")
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true }
if not g_atexit_declared { emith("declare i32 @atexit(ptr)\n"); g_atexit_declared = true }
# @cov_dump: open $LUDIC_COVERAGE (or "ludic.cov"), write a FILE header then one
# `<line> <hits>` row per instrumented line, and close.
emit("define void @cov_dump() {\nentry:\n")
emit(` %env = call ptr @getenv(ptr {envc})\n`)
emit(" %noenv = icmp eq ptr %env, null\n")
emit(` %path = select i1 %noenv, ptr {defc}, ptr %env\n`)
emit(` %f = call ptr @fopen(ptr %path, ptr {modec})\n`)
emit(" %bad = icmp eq ptr %f, null\n")
emit(" br i1 %bad, label %done, label %write\n")
emit("write:\n")
emit(` call i32 (ptr, ptr, ...) @fprintf(ptr %f, ptr @.cov_filefmt, ptr {fnc})\n`)
emit(" br label %loop\n")
emit("loop:\n")
emit(" %i = phi i32 [ 0, %write ], [ %i1, %body ]\n")
emit(` %go = icmp slt i32 %i, {sn}\n`)
emit(" br i1 %go, label %body, label %close\n")
emit("body:\n")
emit(" %lp = getelementptr inbounds ["); emit(sn); emit(" x i32], ptr @L_cov_lines, i32 0, i32 %i\n")
emit(" %lv = load i32, ptr %lp\n")
emit(" %hp = getelementptr inbounds ["); emit(sn); emit(" x i32], ptr @L_cov_hits, i32 0, i32 %i\n")
emit(" %hv = load i32, ptr %hp\n")
emit(" call i32 (ptr, ptr, ...) @fprintf(ptr %f, ptr @.cov_rowfmt, i32 %lv, i32 %hv)\n")
emit(" %i1 = add i32 %i, 1\n")
emit(" br label %loop\n")
emit("close:\n")
emit(" %rc = call i32 @fclose(ptr %f)\n")
emit(" br label %done\n")
emit("done:\n ret void\n}\n\n")
# register @cov_dump with atexit before main runs (an LLVM global constructor).
emit("define void @cov_init() {\nentry:\n")
emit(" %r = call i32 @atexit(ptr @cov_dump)\n")
emit(" ret void\n}\n\n")
emith("@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @cov_init, ptr null }]\n")
}
# Flush the emitted IR. With a null path it goes to stdout (the pipe the shell
# drivers read); with a path it is written to that file so ludicc can hand it to
# clang itself.
function ir_flush(path: pointer) -> bool {
let h = native_ir_lines() + buf_str(head)
let c = buf_str(code)
if (path == null) { # raw IR to stdout (no trailing newline)
let out = file_stdout()
file_write(out, h, len(h))
file_write(out, c, len(c))
return true
}
let f = file_open(path, "wb")
if (f == null) { return false }
file_write(f, h, len(h))
file_write(f, c, len(c))
file_close(f)
return true
}