Take the state at a join as the disjunction of the states behind it - #224
Draft
coord-e wants to merge 1 commit into
Draft
Take the state at a join as the disjunction of the states behind it#224coord-e wants to merge 1 commit into
coord-e wants to merge 1 commit into
Conversation
A block reached by more than one edge took a predicate variable for its precondition, inferred from a clause per incoming edge. The states its predecessors leave say exactly what it is entered in, so their disjunction is its precondition, and only a loop header still has one to infer: the state carried by its back edge is not yet known when it is analyzed. A disjunction is no conjunct of a Horn clause body, so this holds only as long as no predicate variable appears in the states. Where one does, the disjunction is named by a predicate variable of its own, bounded from below by each state, which is what the block had all along. `needs_own_precondition` becomes `is_loop_header` accordingly. The states are collected as the predecessors are analyzed and installed once they all have been, so a block that inherits its precondition now holds the states until then rather than a flag. Over the pass tests this drops the predicate variables from 734 to 676 and the constraints from 917 KiB to 832 KiB: naming a state costs an argument list at every use, which the disjunction rarely exceeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P7MyQbvfkfNy1h7yeN553N
coord-e
force-pushed
the
claude/reduce-predicate-variables-1aeoll
branch
from
August 16, 2026 05:48
2e4e840 to
8a03fd7
Compare
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.
Last of three, on top of #227 (which is on top of #226). Each removes one predicate variable that stood for a fact the constraints already state elsewhere.
A block reached by more than one edge took a template inferred from a clause per incoming edge. The states its predecessors leave say exactly what it is entered in, so their disjunction is its precondition, and only a loop header still has one to infer: the state carried by its back edge is not yet known when it is analyzed.
needs_own_preconditionbecomesis_loop_headeraccordingly.A disjunction is no conjunct of a Horn clause body, so this holds only as long as no predicate variable appears in the states. Where one does, the disjunction is named by a predicate variable of its own, bounded from below by each state — which is what the block had all along.
Body::into_formuladecides which of the two applies.The states are collected as the predecessors are analyzed and installed once they all have been, so a block that inherits its precondition holds the states until then rather than a flag.
Effect
Over
tests/ui/pass(160 files):mainThe path explosion this invites did not show up: the constraints came out 9% smaller, since naming a state costs an argument list at every use, which the disjunction rarely exceeds. Worst case
loop_invariant_multi.rsat 1.23x (2.4 KiB to 2.9 KiB), bestresult_mut.rsat 0.35x (59 KiB to 21 KiB).With all three, a predicate variable is generated only where something is genuinely unknown: the type of a function without an annotation, and a loop header without an
invariant!. A fully annotated program with no loop generates constraints with none at all — an annotatedfn abs(x: i64) -> i64 { if x >= 0 { x } else { -x } }comes out as a plain SMT problem and verifies.Notes for review
Refinement::disjunctiondemand a predicate-variable-free form even for a single state, which quietly put a predicate variable back on every block that inherits, and all 316 tests still passed. A unit test overRefinement::disjunctionwould cover the case that broke; happy to add one.is_loop_headerreads a back edge off the dominator tree, which answers the question asked (is some predecessor analyzed after this block?) only for a reducible CFG. An irreducible one does not go unnoticed: the state arrives after the precondition is installed, andpush_basic_block_preconditionpanics.316 tests pass.