The defer counter costs something on the warm path, and the valgrind row rests on one half
Three notes, all of them about a comment that was true as far as it went. The cost sentence on defer_slot said one i64 and one compare on a path that is already unwinding, which is the unwind's share and not the whole bill: every function with a defer also pays a store of zero at entry and a store of an ordinal at each defer, on the ordinary path, whether anything transfers or not. Small, correct, and now written down as what it is. init-conditions.flan is in test_valgrind.ml, and only the game-data half earns that row -- it is the one that reaches a real (free src) over a slot slurp transferred out of. The note half is output-only: revert the fix and its trace changes, but a wrong integer in a global is not a memory error and memcheck stays quiet. A later edit trimming the edn dependency would leave the row green and blind, so the header says so. And register_defer saves in_defer rather than clearing it. Unreachable today, because defer_ok is false inside a defer and nothing can nest one; written so that the flag comes back rather than being dropped on the day that changes.
This commit is contained in:
parent
4c667cf062
commit
7c1a818001
20
lib/check.ml
20
lib/check.ml
@ -249,9 +249,15 @@ type ctx = {
|
|||||||
not a leak the other way round; it is cleanup over a binding nothing has
|
not a leak the other way round; it is cleanup over a binding nothing has
|
||||||
written, which is [(free v)] on whatever the stack held.
|
written, which is [(free v)] on whatever the stack held.
|
||||||
|
|
||||||
So the count is kept at run time, one store per defer, and the transfer
|
So the count is kept at run time and the transfer path's copy of each
|
||||||
path's copy of each is guarded on it. One i64 and one compare per defer
|
defer is guarded on it. The cost is not all on the unwinding path, and
|
||||||
on a path that is already unwinding. *)
|
the half that is not is the half worth naming: every function with a
|
||||||
|
defer pays one i64 of frame, one store of zero at entry, and one store
|
||||||
|
of an ordinal where each defer is written — on the ordinary path,
|
||||||
|
whether anything ever transfers or not. What the unwind adds on top is
|
||||||
|
one compare per defer. A store of a constant into a frame slot nothing
|
||||||
|
else reads is about as little as a fact can cost, but it is not nothing
|
||||||
|
and it is not only on the cold path. *)
|
||||||
mutable defer_slot : int option;
|
mutable defer_slot : int option;
|
||||||
(* Where a [defer] may be written, which is exactly: a form whose extent is
|
(* Where a [defer] may be written, which is exactly: a form whose extent is
|
||||||
the whole function body. Two things have that extent and only two — a
|
the whole function body. Two things have that extent and only two — a
|
||||||
@ -2732,6 +2738,12 @@ and check_handler_case ctx ?want loc body clauses =
|
|||||||
has registered from one the text has not reached yet. The form's type is
|
has registered from one the text has not reached yet. The form's type is
|
||||||
still [unit], which is all a reader of the value can see. *)
|
still [unit], which is all a reader of the value can see. *)
|
||||||
and register_defer ctx loc forms =
|
and register_defer ctx loc forms =
|
||||||
|
(* Saved and put back rather than cleared, which is the same thing today and
|
||||||
|
will not be the day a defer may hold one. Nothing reaches here from
|
||||||
|
inside a defer now — [defer_ok] is false in there — so the saved value is
|
||||||
|
always false; written this way so that if it ever is not, the flag comes
|
||||||
|
back rather than being dropped. *)
|
||||||
|
let was_in_defer = ctx.in_defer in
|
||||||
ctx.in_defer <- true;
|
ctx.in_defer <- true;
|
||||||
(* A barrier, for the reason [defer] itself exists: these forms are *copied*
|
(* A barrier, for the reason [defer] itself exists: these forms are *copied*
|
||||||
into every exit path of the function, where the loop they were written
|
into every exit path of the function, where the loop they were written
|
||||||
@ -2740,7 +2752,7 @@ and register_defer ctx loc forms =
|
|||||||
let forms =
|
let forms =
|
||||||
barrier ctx "a defer" (fun () -> map_lr (fun d -> check ctx d) forms)
|
barrier ctx "a defer" (fun () -> map_lr (fun d -> check ctx d) forms)
|
||||||
in
|
in
|
||||||
ctx.in_defer <- false;
|
ctx.in_defer <- was_in_defer;
|
||||||
ctx.defers <- mk loc Types.Unit (Tast.Do forms) :: ctx.defers;
|
ctx.defers <- mk loc Types.Unit (Tast.Do forms) :: ctx.defers;
|
||||||
let slot =
|
let slot =
|
||||||
match ctx.defer_slot with
|
match ctx.defer_slot with
|
||||||
|
|||||||
@ -28,6 +28,17 @@
|
|||||||
;;;;
|
;;;;
|
||||||
;;;; [log] is a digit trace and not a running sum, for handler-case.flan's
|
;;;; [log] is a digit trace and not a running sum, for handler-case.flan's
|
||||||
;;;; reason: a sum commutes and would score a wrong order right.
|
;;;; reason: a sum commutes and would score a wrong order right.
|
||||||
|
;;;;
|
||||||
|
;;;; DO NOT DROP THE edn IMPORT. This program is in test_valgrind.ml, and only
|
||||||
|
;;;; one half of it earns that row: [game-data] below, whose initialiser goes
|
||||||
|
;;;; through edn/read-file and so through the real (defer (free src)) over a
|
||||||
|
;;;; binding slurp transferred out of. That is the free of an uninitialised
|
||||||
|
;;;; slot, and memcheck names it. The [note] half is output-only — revert the
|
||||||
|
;;;; fix and its printed trace changes, but no memcheck error is produced,
|
||||||
|
;;;; because a wrong integer written to a global is not a memory error. So a
|
||||||
|
;;;; later edit that trims the edn dependency out of here leaves the valgrind
|
||||||
|
;;;; row green and no longer looking at anything. Move it to a program that
|
||||||
|
;;;; still frees, or take the row out honestly.
|
||||||
|
|
||||||
(import edn "vendor:edn")
|
(import edn "vendor:edn")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user