The leak sweeps have switches, a C source that includes the dyn header is recompiled when the header changes, and the map-remove and map-keys programs free what they allocate

This commit is contained in:
Joseph Ferano 2026-09-25 15:38:02 +07:00
parent 6e29c19097
commit a615e4ecfc
7 changed files with 45 additions and 18 deletions

View File

@ -1431,12 +1431,14 @@ through a pointer into it is answerable; memcheck is told the same fact, so the
same read is reported. The two stay two claims — different tools reaching
different people.
** NEXT The leak question across the corpus
** WAIT The leak question across the corpus
Decided 2026-09-25: one pass over the whole corpus with LeakSanitizer and memcheck's leak check on. Memory an allocator holds by design is set aside; memory nothing owns is a leak and is fixed. The sweeps' default stays leak-checking off.
Both sweeps run with leak checking off, because allocate-once-never-free is this
runtime's design and a leak check produces a suppression list. A green sweep
therefore says nothing about who frees the newly allocating =(bytes s)=. Worth
asking on purpose one day, across the whole corpus and not one program.
WAIT on the between-batches sweep slot: the switches are
=ASAN_OPTIONS=detect_leaks=1 dune build @sanitize= and =FLAN_LEAKS=1 dune build @valgrind=.
A 22-program LSan sample found no runtime leak; program leaks in map-remove and
map-keys are fixed. Needs a decision: =(bytes s)= and =(clone slice)= with no
allocator answer a =[T]= over a heap block nothing can free (bytes-copy.flan).
Temp-allocator by default, as =i64->bytes= is, or a =(Vec T)= the caller frees?
** DONE trap_oom has no site
CLOSED: [2026-09-25]

View File

@ -690,11 +690,23 @@ let compile_c ~opts ?tflags ?(warn = []) ~src ~name () =
built against, so repointing either must not serve a stale .o. *)
let tflags = match tflags with Some f -> f | None -> target_flags opts in
let cc = compiler opts in
(* A source that includes the dyn header was compiled against it, so the
header is part of what the object depends on. Without it a change to a
struct the header declares served an object built against the old layout
— test/dyn_ops.c read a descriptor's new fields past the end of its own. *)
let header =
let needle = "flan_dyn.h" in
let n = String.length needle and m = String.length src in
let rec has i =
i + n <= m && (String.sub src i n = needle || has (i + 1))
in
if has 0 then Runtime_src.dyn_header else ""
in
let key =
Digest.to_hex
(Digest.string
(String.concat "\000"
[ name; src; stamp_of cc; opts.opt;
[ name; src; header; stamp_of cc; opts.opt;
String.concat " " (cflags opts);
String.concat " " tflags;
String.concat " " warn ]))

View File

@ -118,6 +118,8 @@
(deps
(alias corpus)
test_sanitize.exe
; ASAN_OPTIONS=detect_leaks=1 asks the leak question; see test_sanitize.ml.
(env_var ASAN_OPTIONS)
; The dyn runtime's C main, which is the one thing in this sweep that is not
; a Flan program: flan_dyn.c has no Flan spelling yet. It is also the one
; translation unit here that frees the most, which is what makes it worth a
@ -149,7 +151,9 @@
test_valgrind.exe
; The suppression file, which is all reasons and no suppressions; its own
; header says why that is the finding rather than an oversight.
(file valgrind.supp))
(file valgrind.supp)
; FLAN_LEAKS=1 turns memcheck's leak check on; see test_valgrind.ml.
(env_var FLAN_LEAKS))
(action (run ./test_valgrind.exe)))
; The corpus a fourth time, through the hand-written x86-64 backend, compared

View File

@ -22,7 +22,8 @@
(dotimes [i (length vs)] (print (at vs i)) (print " "))
(println "")
(free ks)
(free vs)))
(free vs))
(free m))
;; A string key, and a map that never allocated.
(let [names (map-new string i32)
none (map-new string i32)]

View File

@ -123,7 +123,8 @@
(print (length t)) (println "") ; 150
(match (get t 299) (Some v) (do (print v) (println "")) None (println "?")) ; 598
(print (has-key? t 298)) (println ""))) ; false
(free-all ar))
(free-all ar)
(arena-destroy ar))
;; (7) Churn at a steady size, checked against a plain array. Keys are drawn
;; from 512 and the map holds about two thirds of them, so removals leave

View File

@ -5659,7 +5659,7 @@ level "1"
(List.filter
(fun line ->
contains line prefix
&& contains line "constant { i64, i64, ptr, i64, ptr, i64, ptr }")
&& contains line "constant { i64, i64, ptr, i64, ptr, i64, ptr, i64, ptr }")
(String.split_on_char '\n' ir))
in
if n <> want then begin

View File

@ -41,15 +41,22 @@ let fail fmt = Test_support.fail fmt
let scratch = Test_support.scratch
let supp = "valgrind.supp"
(* Leak checking is off, and the reason is [test_sanitize.ml]'s reason for
detect_leaks=0 unchanged: allocate-once-never-free is this runtime's design,
not an accident — rt_args says so in its own comment, an arena hands back
nothing before arena-destroy, and the context temp arena is made on first
use and never released. LeakSanitizer produced a suppression list and no
information; memcheck would produce the same list. The question is worth
asking on purpose one day, and this is not that run. *)
(* Leak checking is off by default, and the reason is [test_sanitize.ml]'s
reason for detect_leaks=0 unchanged: allocate-once-never-free is this
runtime's design — rt_args says so in its own comment, and the context
temp arena is made on first use and never released. FLAN_LEAKS=1 asks the
leak question on purpose: definite leaks only, which is memory nothing
points at any more, counted as errors. What an allocator holds by design is
still reachable and is not reported. The sanitize sweep's switch is
ASAN_OPTIONS=detect_leaks=1. *)
let leaks = Sys.getenv_opt "FLAN_LEAKS" = Some "1"
let vg_flags =
[ "--leak-check=no"; "--error-exitcode=0"; "--track-origins=yes";
(if leaks then
[ "--leak-check=full"; "--show-leak-kinds=definite";
"--errors-for-leak-kinds=definite" ]
else [ "--leak-check=no" ])
@ [ "--error-exitcode=0"; "--track-origins=yes";
(* Origins are what turn "uninitialised value" into a line naming the
allocation it came from. They cost roughly 2x on top of memcheck and
are worth every bit of it: without them an uninitialised-read report