Repository navigation
Try Z3's simple solver before the default one - #424
Conversation
The default solver's preprocessing tactics take most of the time of the small queries the verifier asks (about 5 ms each, against well under 1 ms for the simple solver). The simple solver now answers first, with a deterministic work budget, and only the queries it cannot decide go to the default solver as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@claude Does LiquidFixpoint have a default tactic for z3? |
|
No, it doesn't name a tactic. But the way it drives Z3 means it never runs the default preprocessing tactics on a normal query either, which is close to what this PR does. What liquid-fixpoint does (
Because of that So liquid-fixpoint effectively gets "SMT core first, preprocessing tactics only when it returns unknown", which is what this PR does by hand:
We could also try |
Verification in the IDE is slow on specs with many state refinements. Saving any file in a study workspace (6 exercises, 15 files) re-verifies everything and took 24 s. Nearly all of it was Z3 overhead: each of the ~1,100 queries per save paid ~5 ms for the default solver's preprocessing tactics, even though the queries themselves are tiny.
This PR answers each query with Z3's simple solver first, and only falls back to the default solver (exactly as before) when the simple solver returns unknown.
The simple solver can get stuck on queries it cannot decide (one nonlinear query in
liquidjava-exampletook 186 s before giving up, against 0.4 s for the default solver). It therefore runs with a deterministic work budget (rlimit= 100,000). Queries it decides use at most ~1,100 units (p99 inliquidjava-example; at most 837 in the study workspace). The ones it cannot decide use millions, so they go straight to the default solver. Being a resource count rather than a timeout, the budget does not depend on machine load.Soundness: either solver's SAT/UNSAT answer is sound. A query only reaches the default solver when the simple one returns unknown. So verdicts can only stay the same, or go from unknown to decided; only the counterexample shown for a failing check could come from a different model.
Timings (warm, in the same JVM, idle machine)
mainproposal-C-stars, 15 files), per saveliquidjava-example/src/main(283 files)* Measured with 3 runs in parallel, so the absolute numbers are inflated; the ratio is what matters.
Same results
I compared every verdict and every diagnostic (title and position) before and after, on:
testSuite(as inTestExamples)liquidjava-example/src/main(whole tree)correct/+incorrect/)exercises,exercises-oss)The number of
SMT Unknownerrors is unchanged everywhere.mvn testpasses.Where the rest of the time goes
The remaining ~8.5 s per save is mostly creating a Z3 context and declaring every context variable for each query. About 80% of it is spent on facts that depend only on the specs: the satisfiability check of each
@StateRefinementwhile specs are registered, and building the state machine used for the state hints. They are identical on every save, so follow-up PRs will reuse them instead of recomputing them.🤖 Generated with Claude Code