fence (25.1c): the census reads the heap outside Ludic's blocks; blocks counted at malloc's own size

The census's native line is malloc's live bytes over every zone since judging began less what
Ludic's tracked blocks kept - the libraries' and drivers' growth, read before the census file is
opened. A tracked block counts malloc_size(), not the size asked for, so kept is what the heap pays
and the residual carries no rounding. @malloc_zone_statistics is declared once, by the fence or by
Os.heap_bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 15:38:13 +03:00
parent a8d54e9878
commit 691964877b
7 changed files with 47519 additions and 47279 deletions

View file

@ -1,6 +1,6 @@
# alloc_fence_leak.ludic - frame 50 keeps one record in a list that outlives it. The fence fails # alloc_fence_leak.ludic - frame 50 keeps one record in a list that outlives it. The fence fails
# the run there (exit 86) and names this file's `new Box` line; under R3D_ALLOC_FENCE=count it # the run there (exit 86) and names this file's `new Box` line; under R3D_ALLOC_FENCE=count it
# runs on and prints `kept 72 bad 1` (the record and the list's first growth, one frame). # runs on and prints `kept 80 bad 1` (the record and the list's growth, at malloc's own sizes, in one frame).
program AllocFenceLeak { program AllocFenceLeak {
property Box { a: int = 0, b: int = 0 } property Box { a: int = 0, b: int = 0 }
entry { entry {

View file

@ -19,6 +19,7 @@ var g_site_kinds: []pointer = new []pointer # kinds seen, and their constants
var g_site_kindc: []pointer = new []pointer var g_site_kindc: []pointer = new []pointer
var g_site_next: pointer = null # a kind the next allocation takes (`new Foo`) var g_site_next: pointer = null # a kind the next allocation takes (`new Foo`)
var g_atexit_declared: bool = false # @atexit is declared once: by the fence or by --coverage var g_atexit_declared: bool = false # @atexit is declared once: by the fence or by --coverage
var g_mzs_declared: bool = false # @malloc_zone_statistics: by the fence or by Os.heap_bytes
# the build's defaults (--fence=, --fence-warm=, --fence-census=, else a `fence` line in the nearest # the build's defaults (--fence=, --fence-warm=, --fence-census=, else a `fence` line in the nearest
# package.ludic); the environment overrides each at run time (R3D_ALLOC_FENCE, _WARM, _CENSUS) # package.ludic); the environment overrides each at run time (R3D_ALLOC_FENCE, _WARM, _CENSUS)
var g_fence_mode: int = 0 # 0: headless fails, windowed fails only under R3D_DEV var g_fence_mode: int = 0 # 0: headless fails, windowed fails only under R3D_DEV
@ -30,6 +31,7 @@ function fence_reset() -> void {
g_site_fn = new []pointer; g_site_file = new []pointer; g_site_line = new []int; g_site_kind = new []pointer g_site_fn = new []pointer; g_site_file = new []pointer; g_site_line = new []int; g_site_kind = new []pointer
g_site_kinds = new []pointer; g_site_kindc = new []pointer g_site_kinds = new []pointer; g_site_kindc = new []pointer
g_atexit_declared = false g_atexit_declared = false
g_mzs_declared = false
g_cur_fn_name = "?"; g_site_fnc = null; g_site_filen = null; g_site_filec = null; g_site_next = null g_cur_fn_name = "?"; g_site_fnc = null; g_site_filen = null; g_site_filec = null; g_site_next = null
push(g_site_fn, null); push(g_site_file, null); push(g_site_line, 0); push(g_site_kind, null) # site 0, named at the end push(g_site_fn, null); push(g_site_file, null); push(g_site_line, 0); push(g_site_kind, null) # site 0, named at the end
} }
@ -168,6 +170,7 @@ function emit_fence_runtime() -> void {
if g_target_win { emit_fence_win(); return } if g_target_win { emit_fence_win(); return }
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true } 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 } if not g_atexit_declared { emith("declare i32 @atexit(ptr)\n"); g_atexit_declared = true }
if not g_mzs_declared { emith("declare extern_weak void @malloc_zone_statistics(ptr, ptr)\n"); g_mzs_declared = true }
emit_fence_globals() emit_fence_globals()
emit_fence_entry() emit_fence_entry()
emit_fence_table() emit_fence_table()

View file

@ -33,6 +33,7 @@ function emit_fence_globals() -> void {
emith("@lp_fkept = global i64 0\n") emith("@lp_fkept = global i64 0\n")
emith("@lp_fbad = global i64 0\n") emith("@lp_fbad = global i64 0\n")
emith("@lp_fjudged = global i64 0\n") emith("@lp_fjudged = global i64 0\n")
emith("@lp_zset = global i64 0\n")
emith("@lp_census = global ptr null\n") emith("@lp_census = global ptr null\n")
emith("declare extern_weak i64 @malloc_size(ptr)\n") emith("declare extern_weak i64 @malloc_size(ptr)\n")
emith("declare i32 @dprintf(i32, ptr, ...)\n") emith("declare i32 @dprintf(i32, ptr, ...)\n")
@ -58,6 +59,7 @@ function emit_fence_globals() -> void {
emith("@.lpf_auto = private unnamed_addr constant [15 x i8] c\"R3D_ALLOC_AUTO\\00\"\n") emith("@.lpf_auto = private unnamed_addr constant [15 x i8] c\"R3D_ALLOC_AUTO\\00\"\n")
emith("@.lpf_w = private unnamed_addr constant [2 x i8] c\"w\\00\"\n") emith("@.lpf_w = private unnamed_addr constant [2 x i8] c\"w\\00\"\n")
emith("@.lpf_chdr = private unnamed_addr constant [76 x i8] c\"alloc-census frames %lld bad %lld kept %lld made %lld freed %lld over %lld\\0A\\00\"\n") emith("@.lpf_chdr = private unnamed_addr constant [76 x i8] c\"alloc-census frames %lld bad %lld kept %lld made %lld freed %lld over %lld\\0A\\00\"\n")
emith("@.lpf_cnat = private unnamed_addr constant [91 x i8] c\"native %lld (the heap outside Ludic's blocks, since judging began: libraries and drivers)\\0A\\00\"\n")
emith("@.lpf_crow = private unnamed_addr constant [23 x i8] c\"site %lld %s %s:%d %s\\0A\\00\"\n") emith("@.lpf_crow = private unnamed_addr constant [23 x i8] c\"site %lld %s %s:%d %s\\0A\\00\"\n")
emith("@.lpf_from = private unnamed_addr constant [15 x i8] c\" from %s\\0A\\00\"\n") emith("@.lpf_from = private unnamed_addr constant [15 x i8] c\" from %s\\0A\\00\"\n")
emith("@.lpf_cfrom = private unnamed_addr constant [11 x i8] c\" from %s\\0A\\00\"\n") emith("@.lpf_cfrom = private unnamed_addr constant [11 x i8] c\" from %s\\0A\\00\"\n")
@ -277,11 +279,18 @@ function emit_fence_table() -> void {
emith("out:\n") emith("out:\n")
emith(" ret void\n") emith(" ret void\n")
emith("}\n") emith("}\n")
emith("define internal void @lp_fadd(ptr %p, i64 %n) {\n") emith("define internal void @lp_fadd(ptr %p, i64 %asked) {\n")
emith("entry:\n") emith("entry:\n")
emith(" %isnull = icmp eq ptr %p, null\n") emith(" %isnull = icmp eq ptr %p, null\n")
emith(" br i1 %isnull, label %out, label %go\n") emith(" br i1 %isnull, label %out, label %size\n")
emith("size:\n")
emith(" %hasms = icmp ne ptr @malloc_size, null\n")
emith(" br i1 %hasms, label %real, label %go\n")
emith("real:\n")
emith(" %ms = call i64 @malloc_size(ptr %p)\n")
emith(" br label %go\n")
emith("go:\n") emith("go:\n")
emith(" %n = phi i64 [ %asked, %size ], [ %ms, %real ]\n")
emith(" call void @lp_flock()\n") emith(" call void @lp_flock()\n")
emith(" %cnt = load i64, ptr @lp_tabn\n") emith(" %cnt = load i64, ptr @lp_tabn\n")
emith(" %max = load i64, ptr @lp_tabmax\n") emith(" %max = load i64, ptr @lp_tabmax\n")
@ -749,6 +758,7 @@ function emit_fence_report() -> void {
emith(" %none = icmp eq ptr %path, null\n") emith(" %none = icmp eq ptr %path, null\n")
emith(" br i1 %none, label %out, label %open\n") emith(" br i1 %none, label %out, label %open\n")
emith("open:\n") emith("open:\n")
emith(" %zn = call i64 @lp_fzone()\n")
emith(" %fp = call ptr @fopen(ptr %path, ptr @.lpf_w)\n") emith(" %fp = call ptr @fopen(ptr %path, ptr @.lpf_w)\n")
emith(" %bad = icmp eq ptr %fp, null\n") emith(" %bad = icmp eq ptr %fp, null\n")
emith(" br i1 %bad, label %out, label %write\n") emith(" br i1 %bad, label %out, label %write\n")
@ -761,6 +771,12 @@ function emit_fence_report() -> void {
emith(" %fr = load i64, ptr @lp_freed\n") emith(" %fr = load i64, ptr @lp_freed\n")
emith(" %ov = load i64, ptr @lp_over\n") emith(" %ov = load i64, ptr @lp_over\n")
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_chdr, i64 %j, i64 %b, i64 %k, i64 %md, i64 %fr, i64 %ov)\n") emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_chdr, i64 %j, i64 %b, i64 %k, i64 %md, i64 %fr, i64 %ov)\n")
emith(" %jq = icmp ne i64 %j, 0\n")
emith(" %zs = load i64, ptr @lp_zset\n")
emith(" %zd = sub i64 %zn, %zs\n")
emith(" %nat = sub i64 %zd, %k\n")
emith(" %nat1 = select i1 %jq, i64 %nat, i64 0\n")
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_cnat, i64 %nat1)\n")
emith(" %sn = load i32, ptr @lp_site_n\n") emith(" %sn = load i32, ptr @lp_site_n\n")
emith(" call void @lp_franked(ptr %fp, ptr @lp_site_base, i1 1, i32 %sn, i32 40)\n") emith(" call void @lp_franked(ptr %fp, ptr @lp_site_base, i1 1, i32 %sn, i32 40)\n")
emith(" call i32 @fclose(ptr %fp)\n") emith(" call i32 @fclose(ptr %fp)\n")
@ -797,6 +813,20 @@ function emit_fence_frame() -> void {
emith(" call void @lp_fsnap()\n") emith(" call void @lp_fsnap()\n")
emith(" ret void\n") emith(" ret void\n")
emith("}\n") emith("}\n")
emith("; malloc's live bytes over every zone (0 where there is no malloc_zone_statistics)\n")
emith("define internal i64 @lp_fzone() {\n")
emith("entry:\n")
emith(" %has = icmp ne ptr @malloc_zone_statistics, null\n")
emith(" br i1 %has, label %read, label %none\n")
emith("none:\n")
emith(" ret i64 0\n")
emith("read:\n")
emith(" %st = alloca [4 x i64], align 8\n")
emith(" call void @malloc_zone_statistics(ptr null, ptr %st)\n")
emith(" %p = getelementptr [4 x i64], ptr %st, i32 0, i32 1\n")
emith(" %n = load i64, ptr %p\n")
emith(" ret i64 %n\n")
emith("}\n")
emith("define internal void @lp_fsettle_locked() {\n") emith("define internal void @lp_fsettle_locked() {\n")
emith("entry:\n") emith("entry:\n")
emith(" %n = load i32, ptr @lp_site_n\n") emith(" %n = load i32, ptr @lp_site_n\n")
@ -807,6 +837,8 @@ function emit_fence_frame() -> void {
emith(" store i64 %lv, ptr @lp_slive\n") emith(" store i64 %lv, ptr @lp_slive\n")
emith(" %pr = load i64, ptr @lp_pre\n") emith(" %pr = load i64, ptr @lp_pre\n")
emith(" store i64 %pr, ptr @lp_spre\n") emith(" store i64 %pr, ptr @lp_spre\n")
emith(" %z = call i64 @lp_fzone()\n")
emith(" store i64 %z, ptr @lp_zset\n")
emith(" store i8 1, ptr @lp_fj\n") emith(" store i8 1, ptr @lp_fj\n")
emith(" ret void\n") emith(" ret void\n")
emith("}\n") emith("}\n")

View file

@ -264,7 +264,7 @@ function emit_os_prelude() -> void {
# Os.heap_bytes: malloc_zone_statistics' in-use bytes, all zones. The symbol is weak, so a libc # Os.heap_bytes: malloc_zone_statistics' in-use bytes, all zones. The symbol is weak, so a libc
# without it links and reads 0. # without it links and reads 0.
function emit_os_heap() -> void { function emit_os_heap() -> void {
emith("declare extern_weak void @malloc_zone_statistics(ptr, ptr)\n") if not g_mzs_declared { emith("declare extern_weak void @malloc_zone_statistics(ptr, ptr)\n"); g_mzs_declared = true }
emith("define i64 @lp_os_heap_bytes() {\n") emith("define i64 @lp_os_heap_bytes() {\n")
emith("entry:\n %has = icmp ne ptr @malloc_zone_statistics, null\n br i1 %has, label %read, label %none\n") emith("entry:\n %has = icmp ne ptr @malloc_zone_statistics, null\n br i1 %has, label %read, label %none\n")
emith("none:\n ret i64 0\n") emith("none:\n ret i64 0\n")

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -797,7 +797,7 @@ function alloc_fence_leak_case() -> void {
let head = capture_line(`grep 'alloc-fence: frame' {tmp_dir()}/afl.err | head -1`) let head = capture_line(`grep 'alloc-fence: frame' {tmp_dir()}/afl.err | head -1`)
let named = shq(`grep -q 'alloc_fence_leak.ludic:11 new Box' {tmp_dir()}/afl.err`) let named = shq(`grep -q 'alloc_fence_leak.ludic:11 new Box' {tmp_dir()}/afl.err`)
let counted = capture_line(`R3D_ALLOC_FENCE=count {b} < /dev/null`) let counted = capture_line(`R3D_ALLOC_FENCE=count {b} < /dev/null`)
if (rc == 86) and (head == "alloc-fence: frame 50 kept +72 B (2 made, 0 freed)") and named and (counted == "kept 72 bad 1") { ok(lbl) } if (rc == 86) and (head == "alloc-fence: frame 50 kept +80 B (2 made, 0 freed)") and named and (counted == "kept 80 bad 1") { ok(lbl) }
else { bad2(lbl, `rc={string(rc)} head=[{head}] named={string(named)} count=[{counted}]`) } else { bad2(lbl, `rc={string(rc)} head=[{head}] named={string(named)} count=[{counted}]`) }
} }
@ -813,7 +813,7 @@ function alloc_fence_auto_case() -> void {
let bc = `{tmp_dir()}/e_alloc_fence_auto_c` let bc = `{tmp_dir()}/e_alloc_fence_auto_c`
var through = "" var through = ""
if shq(`bin/ludicc --fence=count examples/lang/alloc_fence_auto.ludic -o {bc} 2>/dev/null`) { through = capture_line(`{bc} < /dev/null`) } if shq(`bin/ludicc --fence=count examples/lang/alloc_fence_auto.ludic -o {bc} 2>/dev/null`) { through = capture_line(`{bc} < /dev/null`) }
if (rc == 86) and (head == "alloc-fence: frame 1500 kept +4 B (1 made, 0 freed)") and (through == "through") { ok(lbl) } if (rc == 86) and (head == "alloc-fence: frame 1500 kept +16 B (1 made, 0 freed)") and (through == "through") { ok(lbl) }
else { bad2(lbl, `rc={string(rc)} head=[{head}] count=[{through}]`) } else { bad2(lbl, `rc={string(rc)} head=[{head}] count=[{through}]`) }
} }