Skip to content

Commit d91ca2a

Browse files
nikswamyCopilot
andcommitted
Skip redundant check_slprop_with_core in purify_and_check_spec and bind_res_and_post_typing
In purify_and_check_spec: purify_spec already elaborates each atom via tc_term_phase1_with_type (which includes core_check_term). The star-composition of slprop-typed atoms is trivially slprop-typed, so the full-term core_check_term recheck was redundant. For InsertionSort's inner while invariant, this single call took 7.3s. In bind_res_and_post_typing: each bind in a sequential composition called check_slprop_with_core on the accumulated postcondition. With N statements, the same growing post was kernel-checked N times (O(n^2) behavior). The postcondition type was already verified when the sub-computation was checked. For InsertionSort, 18 expensive calls totaled ~4.1s. Combined savings: ~11.2s (core_check_term: 11.6s -> 0.44s, 96% reduction) Total InsertionSort verification time: 43.4s -> 19.5s (55% speedup) Also adds PULSE_PROFILE_CORE env var for optional per-call timing profiling of core_check_term in Pulse_RuntimeUtils.ml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 605f4cf commit d91ca2a

4 files changed

Lines changed: 62 additions & 14 deletions

File tree

pulse/src/checker/Pulse.Checker.ImpureSpec.fst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,8 @@ let purify_spec (g: env) (ctxt: ctxt) (t0: slprop) : T.Tac slprop =
423423
t
424424

425425
let purify_and_check_spec (g: env) (ctxt: ctxt) (t: slprop) =
426-
// purify_spec already elaborates the term via tc_term_phase1_with_type,
427-
// so we only need the core checker for validation (skip instantiate_term_implicits)
426+
// purify_spec already elaborates each atom via tc_term_phase1_with_type (which
427+
// includes a core_check_term). The star-composition of slprop-typed atoms is
428+
// trivially slprop-typed, so a redundant full-term core_check_term is skipped.
428429
let t = purify_spec g ctxt t in
429-
check_slprop_with_core g t;
430430
t

pulse/src/checker/Pulse.Typing.Combinators.fst

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -299,20 +299,16 @@ let bind_res_and_post_typing g c2 x post_hint
299299
= let s2 = st_comp_of_comp c2 in
300300
match post_hint with
301301
| NoHint | TypeHint _ ->
302-
(* We're inferring a post, so these checks are unavoidable *)
303-
(* since we need to type the result in a smaller env g *)
302+
(* We're inferring a post, so check that the result universe matches *)
303+
(* and the bound variable doesn't escape scope *)
304+
(* The postcondition's slprop typing was already checked when the *)
305+
(* sub-computation was checked, so skip redundant core_check_term *)
304306
let u = check_universe g s2.res in
305307
if not (eq_univ u s2.u)
306308
then fail g None "Unexpected universe for result type"
307309
else if x `Set.mem` freevars (RU.deep_compress_safe s2.post)
308310
then fail g None (Printf.sprintf "Bound variable %d escapes scope in postcondition %s" x (P.term_to_string s2.post))
309-
else (
310-
let y = x in //fresh g in
311-
let s2_post_opened = open_term_nv s2.post (v_as_nv y) in
312-
let _ =
313-
check_slprop_with_core (push_binding g y ppname_default s2.res) s2_post_opened in
314-
()
315-
)
311+
else ()
316312
| PostHint post ->
317313
CU.debug g "pulse.main" (fun _ -> "bind_res_and_post_typing (with post_hint)\n");
318314
()

pulse/src/ml/Pulse_RuntimeUtils.ml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,58 @@ let record_stats (key:string) (f: unit -> 'a utac)
272272
= fun ps ->
273273
FStarC_Stats.record key (fun () -> f () ps)
274274

275+
(* Per-call timing distribution for core_check_term profiling *)
276+
let core_check_term_timings : (int * float * string * string) list ref = ref []
277+
let core_check_term_call_count = ref 0
278+
let core_check_caller_label = ref ""
279+
let profiling_enabled = try ignore (Sys.getenv "PULSE_PROFILE_CORE"); true with Not_found -> false
280+
281+
let record_core_check_term_call (label:string) (f: unit -> 'a) : 'a =
282+
if not profiling_enabled then f ()
283+
else begin
284+
let idx = !core_check_term_call_count in
285+
incr core_check_term_call_count;
286+
let caller = !core_check_caller_label in
287+
core_check_caller_label := "";
288+
let t0 = Unix.gettimeofday () in
289+
let result = f () in
290+
let elapsed = Unix.gettimeofday () -. t0 in
291+
core_check_term_timings := (idx, elapsed, label, caller) :: !core_check_term_timings;
292+
result
293+
end
294+
295+
let () = Stdlib.at_exit (fun () ->
296+
if not profiling_enabled then ()
297+
else
298+
let timings = List.rev !core_check_term_timings in
299+
if timings <> [] then begin
300+
let total = List.fold_left (fun acc (_, t, _, _) -> acc +. t) 0.0 timings in
301+
Printf.eprintf "\n=== core_check_term per-call distribution (%d calls, %.1fms total) ===\n"
302+
(List.length timings) (total *. 1000.0);
303+
(* Print histogram buckets *)
304+
let buckets = [0.0; 0.001; 0.01; 0.05; 0.1; 0.5; 1.0; 5.0] in
305+
let counts = List.map (fun lo ->
306+
let hi = match List.find_opt (fun x -> x > lo) buckets with Some x -> x | None -> infinity in
307+
let n = List.length (List.filter (fun (_, t, _, _) -> t >= lo && t < hi) timings) in
308+
let sum = List.fold_left (fun acc (_, t, _, _) -> if t >= lo && t < hi then acc +. t else acc) 0.0 timings in
309+
(lo, hi, n, sum)
310+
) buckets in
311+
List.iter (fun (lo, hi, n, sum) ->
312+
if n > 0 then
313+
Printf.eprintf " [%.0fms-%.0fms): %d calls, %.1fms total\n"
314+
(lo *. 1000.0) (hi *. 1000.0) n (sum *. 1000.0)
315+
) counts;
316+
(* Print the top 20 slowest calls *)
317+
let sorted = List.sort (fun (_, t1, _, _) (_, t2, _, _) -> compare t2 t1) timings in
318+
Printf.eprintf "\nTop 20 slowest core_check_term calls:\n";
319+
List.iteri (fun i (idx, t, label, caller) ->
320+
if i < 20 then
321+
Printf.eprintf " #%d: %.1fms [%s] caller=%s\n" idx (t *. 1000.0) label caller
322+
) sorted;
323+
Printf.eprintf "===\n%!"
324+
end
325+
)
326+
275327
let stack_dump () = FStarC_Util.stack_dump()
276328

277329
let push_options () : unit = FStarC_Options.push ()

pulse/test/bug-reports/ExistsErasedAndPureEqualities.fst.output.expected

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
- Current context:
44
some_pred x v
55
- In typing environment:
6-
__#403 : squash (_v_5 == v)
7-
_v_5#402 : erased int
6+
__#402 : squash (_v_5 == v)
7+
_v_5#401 : erased int
88
__#281 : squash (v == v)
99
v#156 : erased int
1010
x#150 : R.ref int

0 commit comments

Comments
 (0)