Implement solver-based branch pruning#8979
Open
tautschnig wants to merge 5 commits intodiffblue:developfrom
Open
Implement solver-based branch pruning#8979tautschnig wants to merge 5 commits intodiffblue:developfrom
tautschnig wants to merge 5 commits intodiffblue:developfrom
Conversation
Query the SAT solver at each branch point to eliminate infeasible branches. Works in both --paths mode (push/pop scoping) and monolithic mode (incremental conversion with converted_for_pruning flag). Uses a non-simplifying MiniSat solver to avoid backwardSubsumptionCheck overhead; falls back to solver_factoryt when MiniSat is not available (e.g., CaDiCaL builds). Adaptive pruning: cumulative 2-second time budget auto-disables pruning when overhead exceeds benefit. Assumes are explicitly asserted as guard => cond. Includes fix for symex::args name collisions across paths via a static type registry. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
The branch pruning solver performs a single SAT check without the iterative refinement loop. With --refine, --refine-arrays, or --refine-strings, this can give unsound results on the initial over-approximation (e.g., incorrectly determining a branch is infeasible). Disable branch pruning for all refinement modes. The --refine flag (used by --incremental-loop) also creates a bv_refinement solver. The branch pruning solver doesn't perform the refinement loop, so it must be disabled for all refinement modes, not just --refine-arrays and --refine-strings. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com> The branch pruning solver performs a single SAT check without the iterative refinement loop. With --refine, --refine-arrays, or --refine-strings, this can give unsound results on the initial over-approximation (e.g., incorrectly determining a branch is infeasible). Disable branch pruning for all refinement modes.
Add a command-line option to disable the solver-based branch pruning that is now active by default in both --paths and monolithic modes. This is useful for: - Comparative benchmarking (measuring pruning impact) - Working around potential edge cases in branch pruning - Debugging verification results Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Tests covering: prune_assume, no_prune_nondet, prune_chained, prune_loop, prune_malloc, refine_arrays_sound, refine_sound, guarded_assume, monolithic_prune, malloc_dynamic_bounds, stdlib_no_paths_explosion. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds solver-assisted feasibility checks at branch points to prune unreachable branches during symbolic execution, reducing path explosion.
Changes:
- Introduces a shared, push/pop-capable SAT solver for branch pruning (with MiniSat-no-simplifier fast path and solver-factory fallback).
- Implements pruning in both
--pathsand monolithic modes, with an adaptive time-budget auto-disable. - Adds CLI toggle
--no-branch-pruning, regression tests, and an architecture note.
Reviewed changes
Copilot reviewed 34 out of 34 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/goto-symex/symex_target_equation.cpp | Avoids argument symbol name/type collisions across shared solver conversions |
| src/goto-symex/symex_goto.cpp | Adds solver queries to prune infeasible branches in paths + monolithic execution |
| src/goto-symex/ssa_step.h | Adds converted_for_pruning flag for incremental pruning conversions |
| src/goto-symex/goto_symex.h | Stores pruning solver pointer and pruning budget state; exposes setter |
| src/goto-checker/single_path_symex_only_checker.h | Adds persistent pruning solver member and setup_symex hook |
| src/goto-checker/single_path_symex_only_checker.cpp | Builds/configures pruning solver and wires it into symex |
| src/goto-checker/multi_path_symex_only_checker.h | Adds persistent pruning solver member |
| src/goto-checker/multi_path_symex_only_checker.cpp | Builds/configures pruning solver and wires it into symex |
| src/cbmc/cbmc_parse_options.h | Adds no-branch-pruning command-line option |
| src/cbmc/cbmc_parse_options.cpp | Parses --no-branch-pruning and documents it in --help |
| regression/cbmc/unwind_counters4/test.desc | Updates expected output regex |
| regression/cbmc/paths-branch-pruning/*.c/.desc | Adds new regression tests covering pruning behavior and refine-soundness |
| doc/architectural/branch-pruning.md | Documents pruning architecture and limitations |
Comments suppressed due to low confidence (4)
src/goto-symex/symex_goto.cpp:1
- In monolithic pruning, assignments/constraints are asserted unconditionally via
set_to_true(step.cond_expr). SSA steps are typically guarded, and the normal conversion asserts them asguard => cond_expr; dropping the guard can over-constrain the pruning solver and incorrectly mark branches infeasible. Fix by asserting guarded steps asimplies_exprt{step.guard, step.cond_expr}(or the equivalent used by the normal equation conversion) for assignments and constraints as well.
src/goto-symex/symex_goto.cpp:1 SSA_stept::convertedis documented/used for incremental conversion. Resetting it here for pruning risks interfering with later conversions into other solvers (and can force repeated reconversion), which can change performance and potentially duplicate constraints depending on how conversion is orchestrated downstream. Prefer tracking pruning conversion exclusively withconverted_for_pruning(already added) and avoid mutatingconvertedin the pruning path.
src/goto-symex/symex_target_equation.cpp:1- This function-local
staticregistry is shared across all runs/instances and is not thread-safe. It also grows monotonically with every uniquesymex::args::<n>ever produced, which can create unbounded memory growth in long-running or multi-check workflows. A more robust approach is to make the registry solver- or symex-instance scoped (or incorporate a type-derived discriminator into the identifier) so state doesn’t leak globally and can be managed/reset deterministically.
src/goto-symex/symex_goto.cpp:1 - The pruning query is checking satisfiability under the converted equation, but it doesn’t appear to assert the current path condition explicitly (e.g.,
state.guard). That can significantly reduce pruning power (SAT models can satisfy implications by setting earlier guards false). Consider adding the current path guard as an assumption (or conjoining it withnew_guard/!new_guard) when callingpush(...)so the solver checks feasibility under the active path condition.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ^SIGNAL=0$ | ||
| ^VERIFICATION FAILED$ | ||
| ^\** 4 of 4 failed | ||
| ^\*\* [34] of [34] failed |
Comment on lines
+22
to
+25
| #ifdef HAVE_MINISAT2 | ||
| #include <solvers/sat/satcheck_minisat2.h> | ||
| #endif | ||
| #endif |
| @@ -0,0 +1,86 @@ | |||
| /// \file | |||
| /// Branch Pruning in --paths Mode | |||
Comment on lines
+69
to
+71
| /// ## Push/Pop Overhead | ||
| /// | ||
| /// Each branch check converts the entire equation inside a push/pop scope. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Query the SAT solver at each branch point to eliminate infeasible branches. Works in both --paths mode (push/pop scoping) and monolithic mode (incremental conversion with converted_for_pruning flag). Uses a non-simplifying MiniSat solver to avoid backwardSubsumptionCheck overhead; falls back to solver_factoryt when MiniSat is not available (e.g., CaDiCaL builds).
Adaptive pruning: cumulative 2-second time budget auto-disables pruning when overhead exceeds benefit.
Assumes are explicitly asserted as guard => cond. Includes fix for symex::args name collisions across paths via a static type registry.