Skip to content

Restate quantifier-free argument refinements at lemma calls (fixes #4591) - #4597

Open
gebner wants to merge 1 commit into
masterfrom
fix-4591-lemma-arg-sign-facts
Open

gebner wants to merge 1 commit into
masterfrom
fix-4591-lemma-arg-sign-facts

Conversation

@gebner

@gebner gebner commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #4591.

Root cause

The regression window (2026.09.20 → nightly-2026-09-23) contains only #4556 and #4515; the release already includes the prop encoding (#4519). The cause is in #4515: check_application_args binds non-constructor arguments with bind_no_capture, assuming the solver recovers an argument's result refinement from the callee's typing axiom. For nonlinear arithmetic that fails. In the reproducer, distributivity_add_right base (eval …) (pow2 … * eval …) lost the sign facts eval _ >= 0 and pow2 _ > 0, which the release VC had next to the lemma's postcondition.

Evidence: in a faithful replay of the incremental Z3 session, adding just those three facts before the failing goal takes it from >40M rlimit (canceled) to 3.9M. eval_empty contributes nothing to the query; it only reorders prelude declarations, so its effect (like the module-name sensitivity) is Z3 luck on a goal that had become hard.

Fix

When the callee's result type is a refined unit (a lemma call), bind the arguments with a new TcUtil.bind_capture_quantifier_free. It restates only the quantifier-free conjuncts of each argument's refinement. The lemma's result type becomes a hypothesis once the call is sequenced, so no value's type is polluted. Quantified conjuncts (e.g. Seq.init's forall i. index s i == f i) are dropped: an earlier version that kept them destabilized FStar.Matrix.

Also:

  • FStar.UInt128.eq_mask: calls v_inj explicitly. The query text is unchanged, but the goal was brittle (1.08 of 5 rlimit) and flipped under context changes from dependency checked files. With the hint it uses ≤0.36 both before and after this change.
  • tests/tactics/Postprocess.fst.output.expected: gensym renumbering only.
  • tests/bug-reports/closed/Bug4591.fst: regression test (max 8.7 of 60 rlimit over 8 seeds).

Results

Reproducer, max rlimit over --z3seed 0..7 (rlimit 60):

2026.09.20 this PR
Spec.Bignum4 3.0–8.1 3.8–14.6
module renamed Bug 1.3–12.2 2.0–12.4

Before this PR the nightly exhausts 60 on the default seed.

Testing

  • ulib re-verified from scratch without ADMIT (stage2/ulib.checked deleted), plus Pulse core/lib
  • make test with all _cache/_output dirs cleared, plus examples, doc/book/code and Pulse tests
  • The only failure, tests/custard RelocApp ("did not say where it found the source" / Error 369), fails identically on unmodified master.

I have not re-run hacl-pulse.

Since #4515, application arguments are bound without restating their
result-type refinement, on the premise that the solver recovers it from
the callee's typing axiom. For nonlinear arithmetic that premise fails:
`distributivity_add_right base (eval s) (pow2 n * eval t)` lost the sign
facts `eval _ >= 0` and `pow2 _ > 0`, pushing a goal from ~5 rlimit to
exhausting 60 (and making it sensitive to unrelated declarations above).

When the callee's result is a refined unit (a lemma call), bind the
arguments with the new `TcUtil.bind_capture_quantifier_free`, which
restates only the quantifier-free conjuncts of the argument's refinement.
The lemma's result type is dissolved into a hypothesis, so this does not
pollute any value's type; quantified conjuncts (e.g. `Seq.init`'s
`forall i. index s i == f i`) are dropped since they add instantiation
work to every later goal (they destabilized FStar.Matrix).

Also:
- FStar.UInt128.eq_mask: call v_inj explicitly; the proof was brittle
  (1.08/5 rlimit) and flipped under the context perturbation.
- tests/tactics/Postprocess: accept gensym renumbering.
- tests/bug-reports/closed/Bug4591.fst: regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gebner

gebner commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

!bench

@github-actions

Copy link
Copy Markdown
Contributor

🔬 F* Performance Comparison

Baseline: Base (master @ c0c03b9883e25b19807230d17af06558d1bf0052) | Patched: PR #4597 (fix-4591-lemma-arg-sign-facts @ 7112135412f427f04033f4b0e0c530557de25638) | Matched: 2909 tests

Summary

Metric Median Mean Geo Mean Std Dev P5 → P95 Min → Max
Memory +0.1% +0.2% 1.002× 0.5% -0.2% → +1.0% -2.6% → +7.2%
Time -0.1% -0.2% 0.998× 4.1% -5.9% → +5.6% -44.8% → +91.7%

Totals: Memory: 787.3 MiB | Time: +53.1s

Change Distribution

Metric 🟢 Improved (>5%) ⚪ Unchanged (±5%) 🔴 Regressed (>5%)
Memory 0 (0%) 2907 (100%) 2 (0%)
Time 225 (8%) 2425 (85%) 195 (7%)

Heavy Tests (baseline > 100 MiB, n=776)

Metric Median Mean Geo Mean Std Dev P5 → P95
Memory +0.1% +0.3% 1.003× 0.6% -0.2% → +1.3%
Time -0.0% +0.1% 1.000× 3.4% -4.9% → +5.2%
📉 Top 20 Memory Improvements
File Mem (base) Mem (patch) Mem Δ Time (base) Time (patch) Time Δ
tests/bug-reports/closed/_cache/Bug3207c.fst.checked 158.4 MiB 155.0 MiB -2.2% 2.03s 1.89s -6.9%
…lse/share/pulse/examples/dice/_cache/CBOR.Pulse.fst.checked 785.0 MiB 782.0 MiB -0.4% 37.57s 37.12s -1.2%
pulse/test/bug-reports/.depend 82.9 MiB 80.7 MiB -2.6% 2.10s 2.03s -3.2%
pulse/share/pulse/examples/_cache/Quicksort.Base.fst.checked 357.0 MiB 355.0 MiB -0.6% 9.91s 10.25s +3.4%
pulse/test/_cache/BorrowsExample.fst.checked 275.0 MiB 273.0 MiB -0.7% 3.66s 3.56s -2.5%
pulse/test/_cache/Example.Unreachable.fst.checked 191.0 MiB 189.0 MiB -1.0% 0.72s 0.74s +2.4%
pulse/test/bug-reports/_cache/Bug13.fst.checked 179.0 MiB 177.0 MiB -1.1% 0.68s 0.71s +4.1%
pulse/test/bug-reports/_output/Bug29.fst.output 155.0 MiB 153.0 MiB -1.3% 0.78s 0.82s +5.2%
pulse/test/nolib/_cache/CharConstants.fst.checked 137.0 MiB 135.0 MiB -1.5% 0.53s 0.51s -2.9%
tests/custard/pulse/_cache/PulseSlice.fst.checked 237.0 MiB 235.0 MiB -0.8% 2.46s 2.58s +4.8%
examples/tactics/_cache/Tautology.fst.checked 135.4 MiB 133.9 MiB -1.1% 1.98s 2.00s +1.1%
…examples/_cache/Demo.MultiplyByRepeatedAddition.fst.checked 217.0 MiB 216.0 MiB -0.5% 2.18s 2.28s +4.4%
…e/pulse/examples/_cache/PulseExample.BubbleSort.fst.checked 300.0 MiB 299.0 MiB -0.3% 5.38s 5.49s +2.1%
…es/by-example/_cache/PulseTutorial.Existentials.fst.checked 188.0 MiB 187.0 MiB -0.5% 1.10s 1.09s -1.4%
…se/examples/by-example/_cache/PulseTutorial.Ref.fst.checked 211.0 MiB 210.0 MiB -0.5% 1.89s 1.85s -2.2%
…e/pulse/examples/dice/_cache/CBOR.Pulse.Extern.fsti.checked 270.0 MiB 269.0 MiB -0.4% 2.10s 2.12s +0.9%
pulse/share/pulse/examples/dice/_cache/DPE.fst.checked 678.0 MiB 677.0 MiB -0.1% 25.45s 26.55s +4.3%
pulse/test/_cache/DivergentFn.fst.checked 244.0 MiB 243.0 MiB -0.4% 2.68s 2.63s -1.9%
pulse/test/_cache/PulseFnTerms.fst.checked 172.0 MiB 171.0 MiB -0.6% 0.86s 0.82s -4.8%
pulse/test/_cache/RenameLetLib.fst.checked 182.0 MiB 181.0 MiB -0.5% 0.72s 0.70s -3.2%
📈 Top 20 Memory Regressions
File Mem (base) Mem (patch) Mem Δ Time (base) Time (patch) Time Δ
tests/custard/pulse/_output/UglyAlias.custard.rust.krml 418.0 MiB 448.0 MiB +7.2% 2.12s 2.19s +3.4%
tests/custard/pulse/_output/UglyAlias.custard.krml 418.0 MiB 448.0 MiB +7.2% 2.22s 2.19s -1.3%
tests/calc/_cache/Long.fst.checked 1.1 GiB 1.1 GiB +2.0% 46.99s 47.88s +1.9%
tests/custard/pulse/_output/CborBoundarySlice.dc 445.0 MiB 460.0 MiB +3.4% 2.79s 2.91s +4.5%
…s/custard/pulse/_output/CborBoundarySlice.custard.rust.krml 445.0 MiB 457.0 MiB +2.7% 2.61s 2.68s +2.5%
tests/custard/_output/NormBudget.rejected 4.8 GiB 4.8 GiB +0.2% 222.62s 213.40s -4.1%
pulse/test/_output/InlineArrayLen.ml 224.0 MiB 231.0 MiB +3.1% 1.36s 1.37s +0.7%
pulse/test/_output/InlineArrayLen.c 224.0 MiB 229.0 MiB +2.2% 1.35s 1.34s -1.3%
pulse/test/_output/Example_Slice.c 450.0 MiB 455.0 MiB +1.1% 2.19s 2.18s -0.6%
tests/custard/pulse/_output/UnitSlice.custard.rust.krml 447.0 MiB 451.0 MiB +0.9% 2.23s 2.22s -0.3%
pulse/test/_output/ExtractUninit.c 224.0 MiB 228.0 MiB +1.8% 1.32s 1.36s +2.8%
pulse/test/_output/BangBang.c 225.0 MiB 229.0 MiB +1.8% 1.43s 1.38s -3.5%
examples/metatheory/_cache/MiniValeSemantics.fst.checked 991.7 MiB 995.1 MiB +0.3% 26.21s 28.01s +6.9%
tests/extraction/backends/_output/custard/ExtUInt128.c 147.9 MiB 151.3 MiB +2.3% 0.97s 0.96s -0.7%
tests/machine_integers/_output/TestPrint.ml 148.3 MiB 151.5 MiB +2.1% 0.98s 1.01s +2.7%
tests/custard/pulse/_output/TupBind.dc 215.0 MiB 218.0 MiB +1.4% 1.32s 1.39s +5.6%
tests/custard/pulse/_output/PulseSlice.custard.rust.krml 447.0 MiB 450.0 MiB +0.7% 2.24s 2.21s -1.3%
tests/custard/pulse/_output/PulseGlobalArray.dc 223.0 MiB 226.0 MiB +1.3% 1.41s 1.40s -0.8%
tests/custard/pulse/_output/PulseGlobalArray.custard.ml 222.0 MiB 225.0 MiB +1.4% 1.35s 1.40s +3.2%
tests/custard/pulse/_output/PulseFnPtrRet.dc 215.0 MiB 218.0 MiB +1.4% 1.34s 1.38s +2.8%
⬇️ Top 20 Time Improvements
File Mem (base) Mem (patch) Mem Δ Time (base) Time (patch) Time Δ
tests/custard/_output/NormBudget.rejected 4.8 GiB 4.8 GiB +0.2% 222.62s 213.40s -4.1%
examples/algorithms/_cache/StringMatching.fst.checked 135.5 MiB 137.4 MiB +1.4% 7.87s 6.07s -22.9%
…cro-benchmarks/_cache/SquashSubtypingDivergence.fst.checked 45.6 MiB 45.4 MiB -0.3% 6.20s 4.51s -27.3%
tests/vale/_cache/X64.Vale.Decls.fst.checked 273.8 MiB 275.1 MiB +0.5% 43.32s 41.67s -3.8%
tests/extraction/cmi/3/_output/B.ml 3.6 GiB 3.6 GiB +0.0% 50.17s 48.99s -2.3%
tests/micro-benchmarks/_cache/Test.IrrationalPow.fst.checked 42.4 MiB 42.2 MiB -0.3% 1.93s 1.06s -44.8%
tests/custard/_cache/CborBoundary.fst.checked 127.3 MiB 127.7 MiB +0.3% 7.79s 7.17s -7.9%
…lse/share/pulse/examples/dice/_cache/CBOR.Pulse.fst.checked 785.0 MiB 782.0 MiB -0.4% 37.57s 37.12s -1.2%
tests/extraction/backends/_cache/ExtUIntRotate.fst.checked 81.5 MiB 82.2 MiB +0.8% 30.18s 29.75s -1.4%
tests/extraction/backends/_cache/ExtUIntDivRem.fst.checked 75.9 MiB 76.1 MiB +0.3% 8.20s 7.83s -4.4%
examples/tactics/eci19/_cache/ConstructiveLogic.fst.checked 99.8 MiB 99.8 MiB +0.1% 8.02s 7.68s -4.2%
…se/examples/by-example/_cache/ParallelIncrement.fst.checked 525.0 MiB 526.0 MiB +0.2% 23.77s 23.46s -1.3%
tests/extraction/backends/_cache/ExtIntSigned.fst.checked 86.4 MiB 86.6 MiB +0.3% 15.93s 15.63s -1.9%
pulse/test/_cache/Example.BreakReturnContinue.fst.checked 363.0 MiB 363.0 MiB +0.0% 7.53s 7.27s -3.5%
…ples/by-example/_cache/PulseTutorial.Algorithms.fst.checked 345.0 MiB 345.0 MiB +0.0% 7.62s 7.40s -2.8%
…/share/pulse/examples/dice/_cache/CBOR.Spec.Map.fst.checked 506.0 MiB 506.0 MiB +0.0% 10.99s 10.81s -1.7%
…-example/_cache/PulseTutorial.ParallelIncrement.fst.checked 482.0 MiB 484.0 MiB +0.4% 19.92s 19.74s -0.9%
…ples/by-example/_cache/PulseTutorial.LinkedList.fst.checked 334.0 MiB 334.0 MiB +0.0% 5.76s 5.58s -3.1%
tests/bug-reports/closed/_cache/Bug3207.fst.checked 238.7 MiB 238.2 MiB -0.2% 7.49s 7.32s -2.4%
tests/bug-reports/closed/_cache/Bug2641.fst.checked 131.3 MiB 131.4 MiB +0.1% 1.63s 1.47s -9.8%
⬆️ Top 20 Time Regressions
File Mem (base) Mem (patch) Mem Δ Time (base) Time (patch) Time Δ
…ples/dsls/bool_refinement/_cache/BoolRefinement.fst.checked 482.7 MiB 483.7 MiB +0.2% 94.37s 106.41s +12.8%
tests/extraction/backends/_cache/ExtInt128.fst.checked 76.2 MiB 76.5 MiB +0.4% 10.16s 19.48s +91.7%
tests/extraction/backends/_cache/ExtUIntUnsigned.fst.checked 112.2 MiB 112.7 MiB +0.4% 132.95s 140.88s +6.0%
tests/extraction/backends/_cache/ExtUIntMask.fst.checked 96.7 MiB 96.8 MiB +0.1% 66.97s 74.66s +11.5%
…re/pulse/examples/dice/_cache/DPE.Messages.Spec.fst.checked 323.0 MiB 323.0 MiB +0.0% 110.66s 116.74s +5.5%
tests/custard/pulse/_cache/CborBoundarySlice.fst.checked 689.0 MiB 690.0 MiB +0.1% 43.84s 49.48s +12.8%
…_bool_refinement/_cache/DependentBoolRefinement.fst.checked 303.9 MiB 304.9 MiB +0.3% 20.01s 23.61s +18.0%
…sts/extraction/backends/_cache/ExtIntShiftArith.fst.checked 78.2 MiB 78.7 MiB +0.6% 46.69s 49.59s +6.2%
…e/pulse/examples/dice/_cache/DPE.Messages.Parse.fst.checked 406.0 MiB 407.0 MiB +0.2% 46.22s 48.83s +5.6%
examples/metatheory/_cache/MiniValeSemantics.fst.checked 991.7 MiB 995.1 MiB +0.3% 26.21s 28.01s +6.9%
tests/hacl/_cache/Lib.Vec.Lemmas.fst.checked 333.8 MiB 334.1 MiB +0.1% 22.05s 23.76s +7.8%
examples/data_structures/_cache/RBTreeIntrinsic.fst.checked 125.1 MiB 124.9 MiB -0.2% 29.87s 31.19s +4.4%
pulse/share/pulse/examples/dice/_cache/DPE.fst.checked 678.0 MiB 677.0 MiB -0.1% 25.45s 26.55s +4.3%
tests/extraction/backends/_cache/ExtIntDivRem.fst.checked 76.9 MiB 77.2 MiB +0.4% 17.52s 18.58s +6.1%
tests/tactics/_cache/Prettify.fst.checked 547.1 MiB 547.7 MiB +0.1% 20.08s 21.12s +5.2%
pulse/share/pulse/examples/dice/_cache/DPE_CBOR.fst.checked 392.0 MiB 392.0 MiB +0.0% 17.58s 18.61s +5.9%
tests/calc/_cache/Long.fst.checked 1.1 GiB 1.1 GiB +2.0% 46.99s 47.88s +1.9%
tests/extraction/backends/_cache/ExtUInt8Lognot.fst.checked 78.9 MiB 79.0 MiB +0.2% 25.86s 26.69s +3.2%
doc/book/code/_cache/Part3.DataTypesALaCarte.fst.checked 196.2 MiB 196.3 MiB +0.0% 21.73s 22.36s +2.9%
pulse/share/pulse/examples/_cache/MSort.Base.fst.checked 402.0 MiB 403.0 MiB +0.2% 8.95s 9.48s +6.0%

ℹ️ The full comparison table (2909 tests) was omitted to keep this report within GitHub's comment size limit. See the attached HTML report for all tests.

Note: Memory values report the peak OCaml heap of the F* process (excluding Z3 subprocesses). Time measurements are from single runs and may be noisy, especially for fast tests. Tests with baseline time < 0.1s are excluded from time statistics. The geometric mean (Geo Mean) of the patched/baseline ratio is the most robust summary statistic for performance comparisons (1.0× = no change, <1× = improvement).


📎 Download HTML report and raw .ramon files

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large SMT regression in nightly-2026-09-23: an unused Lemma in scope makes a later proof 20x more expensive

1 participant