Skip to content

Prune indeterminate-match rules using the matcher's partial substitution#4148

Merged
ehildenb merged 5 commits into
masterfrom
escalate-partial-match
Jun 2, 2026
Merged

Prune indeterminate-match rules using the matcher's partial substitution#4148
ehildenb merged 5 commits into
masterfrom
escalate-partial-match

Conversation

@ehildenb
Copy link
Copy Markdown
Member

@ehildenb ehildenb commented Jun 1, 2026

When applyRule hits a MatchIndeterminate result, it currently aborts immediately with RuleApplicationUnclear and hands off to Kore — even when the rule's requires clause is already unsatisfiable under the bindings the matcher did resolve. The matcher accumulates a partial substitution while processing pairs, so any successful extension of the indeterminate match must extend that substitution; if a requires clause already evaluates to FalseBool under it, no extension can rescue the rule and it can be pruned in favour of the next-priority alternative.

This PR exposes that partial substitution on the MatchIndeterminate result and uses it in applyRule to invoke checkRequires before the existing abort. On returnNotApplied from checkConstraint's onBottom, the rule is pruned; on smtUnclear or successful clauses, control falls through to the unchanged RuleApplicationUnclear abort (the match itself is still indeterminate, so we cannot apply the rule). The standard LLVM-discharge / equation-evaluator / SMT-discharge chain is reused via checkRequires directly.

The motivating case is the KEVM #addr [OP:OpCode], where the earlier rules in the priority chain have requires that are falsified by the partial sub but booster was bailing to Kore instead. See here for the rules, basically whenever the workstack was symbolic, the booster would fail on every opcode because it could not tell if there were sufficient items to fire the rule. But it does partially match the <k> cell, which frequently (usually) produces an opcode that fails the #isAddr1Op and #isAddr2Op side-conditions, so it can definitely throw out the rule and fall-through to the owise case.

Changes:

  • Pattern/Match: MatchIndeterminate carries the partial Substitution alongside the remainder pairs; checkIndeterminate threads it through.
  • Pattern/Rewrite: applyRule's MatchIndeterminate branch invokes checkRequires with the partial sub; on returnNotApplied, prunes the rule and continues with the next-priority alternative.
  • Pattern/ApplyEquations, Pattern/Implies: pattern-match the new field as _partialSubst — no behavioural change in the equation evaluator or implies handler.
  • Unit tests:
    • MatchRewrite: new "structured/var pattern with mixed-determinacy subject" case that mirrors the #addr [OP] shape and asserts the partial sub the matcher always produced but the API was previously discarding.
    • Pattern/Rewrite + Fixture.conBoolPair: rewrite tests covering prune-on-FalseBool (catch-all fires), prune-not-fired-on-TrueBool (still aborts — match is still indeterminate), and a deferred free-SortBool-variable SMT case noted for the rpc-integration suite.
    • Existing matcher test "Matching the same variable in a constructor (indeterminate)" updated to assert the partial sub it always produced.
  • RPC-integration test test-escalate-partial-match (booster-dev only): minimal K module pinning the end-to-end behaviour — pre-fix booster aborts at depth=0 with two Uncertain about unification of rule log entries; post-fix booster prunes the higher-priority rule and fires the catch-all in a single step.

Validation:

  • All 721 booster unit tests pass.
  • Confirmed that test-escalate-partial-match fails at the test-only commit (first in the chain) and passes at the tip.
  • Run this against KEVM and confirmed:
    • No meaningful change in performance.
    • No more tests enabled in booster-dev mode (mostly blocked by implication checking).
    • The cases of execute fallbacks that this was designed to address, it does address.

@ehildenb ehildenb force-pushed the escalate-partial-match branch from ba599b1 to 22409db Compare June 1, 2026 04:06
ehildenb and others added 4 commits June 1, 2026 04:42
…pile},test-escalate-partial-match}, scripts/booster-integration-tests.sh: add regression test for partial-substitution pruning of indeterminate match

Tiny self-contained K definition pinning the booster-dev behaviour fixed
by the partial-substitution pruning in 'applyRule's MatchIndeterminate
branch. The shape mirrors the motivating KEVM '#addr [OP] / wordStack'
case but uses a Bool-sorted rule variable so the partial substitution
binds directly to FalseBool without needing the LLVM backend to evaluate
a domain-specific function.

  * Module 'ESCALATE-PARTIAL-MATCH' (resources/escalate-partial-match.k):
    'Two' (priority 50) matches 'step(B, cons(_, cons(_, _)))' with
    'requires B'; 'Catch' (priority 60) is the unconditional catch-all.
  * Subject 'step(false, S:MyList)': matching the wordstack-shaped
    second slot against the symbolic 'S' goes MatchIndeterminate, but
    the first slot binds 'B -> false' cleanly into the partial sub.
  * Post-fix behaviour (state-000.execute / response-000.json): booster
    prunes 'Two' via 'returnNotApplied' (the substituted 'requires
    false' is FalseBool) and fires 'Catch' on its own, leaving the
    rewrite at depth=1 with a single success log entry for the catch-all
    (rule-id f297177533fd...). Pre-fix booster-dev aborts at depth=0
    with two 'Uncertain about unification of rule' log entries for
    'Two' (rule-id d0d3cc8f09ad...).
  * Wired into 'scripts/booster-integration-tests.sh' alongside the
    other booster-dev-only tests ('compute-ceil', 'simplify', etc.) —
    the test demonstrates booster-only progress and is not meaningful
    under 'kore-rpc-booster', whose Kore fallback would mask the
    difference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tests/Pattern/Match{Eval,Implies,Rewrite}: thread partial substitution through MatchIndeterminate

The matcher already accumulates a substitution as it processes pairs, even
when some pairs end up indeterminate. Expose that partial substitution on
the 'MatchIndeterminate' result so callers (next commit will exercise this
from 'applyRule') can attempt to falsify a rule's side conditions before
treating the match as unrecoverably indeterminate.

  * 'MatchResult.MatchIndeterminate' now carries the partial 'Substitution'
    in addition to the remainder pairs. 'checkIndeterminate' threads
    'mSubstitution' through.
  * All existing consumers (Rewrite.hs:407 applyRule, ApplyEquations.hs
    equation application, Implies.hs predicate matching) ignore the new
    field — no behavioural change yet.
  * Test helpers ('remainder' defaults to empty partial sub; new
    'remainderWith' helper for the non-empty case).
  * Existing matcher test "Matching the same variable in a constructor
    (indeterminate)" now asserts the bind-then-conflict partial sub the
    matcher always produced but the API was previously discarding.
  * New matcher test "Structured/var pattern with mixed-determinacy
    subject" pins the shape that mirrors the downstream '#addr [OP]'
    case: one slot resolves cleanly into the partial sub while another
    is indeterminate via a subject-side variable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…artial-substitution pruning of indeterminate match

Scaffolding for the next commit's prune behaviour. After this commit
857 of 858 unit tests still pass; the prune-on-false case fails on
purpose and flips to PASS once 'applyRule' starts using the partial
substitution to falsify the rule.

  * New 'conBoolPair' constructor in the test fixture so a rule LHS
    can bind a Bool-sorted variable through matching and drive a
    'requires' clause to syntactic 'FalseBool' under substitution.
  * 'ruleBoolGuarded' (priority 42) matches
    'conBoolPair("trigger", P:SortBool)' with 'requires = [P]', sitting
    above an unconditional 'ruleBoolCatchAll' (priority 50).
  * Subject 'conBoolPair(VSub:SomeSort, b)' with VSub a subject-side
    variable forces 'MatchIndeterminate' against the guarded rule but
    cleanly binds 'P ↦ b' in the partial substitution. Three
    scenarios:
      - b = FalseBool: rule should prune, catch-all fires.
        (today: aborts → FAIL; impl in the next commit flips it)
      - b = TrueBool: rule still aborts (the match is still
        indeterminate even though 'requires' is trivially true) —
        locks in that the impl does not accidentally treat a
        true-under-partial-sub rule as applicable.
  * A third "free Bool variable" case is noted as deferred to the
    rpc-integration test because it exercises the SMT-discharge
    branch of 'checkRequires' and the unit harness uses 'noSolver'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…uates to false under partial substitution

In 'applyRule' (MatchIndeterminate branch), invoke 'checkRequires' with the
matcher's partial substitution before the existing 'RuleApplicationUnclear'
abort. The matcher accumulates bindings for every pair it resolves cleanly,
independently of the indeterminate pairs; any successful extension of the
match must extend that substitution. So if any 'requires' clause evaluates
to bottom under the partial substitution alone (via 'returnNotApplied' from
'checkConstraint's onBottom), no extension can rescue the rule and we
prune it. The next-applicable rule in the priority group is then tried.

If 'checkRequires' returns successfully (clauses true or trivially dropped)
or aborts via 'smtUnclear' (RuleConditionUnclear), control falls through
to the unchanged 'RuleApplicationUnclear' abort — the match itself is still
indeterminate, so the rule is not safe to apply even if its conditions
look satisfied under the partial sub.

This reuses 'checkRequires' directly so the prune path picks up the
standard LLVM-discharge / equation-evaluator / SMT-discharge chain
automatically.

Flips the previously-failing
"Indeterminate match is pruned when requires under partial sub is FalseBool"
test to PASS; all 858 unit tests now green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ehildenb ehildenb force-pushed the escalate-partial-match branch from 22409db to 35a6746 Compare June 1, 2026 12:36
@ehildenb ehildenb marked this pull request as ready for review June 1, 2026 20:38
@ehildenb ehildenb requested a review from jberthold June 1, 2026 20:38
Copy link
Copy Markdown
Collaborator

@jberthold jberthold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one! 👍

@ehildenb ehildenb merged commit e92393a into master Jun 2, 2026
6 checks passed
@ehildenb ehildenb deleted the escalate-partial-match branch June 2, 2026 13:16
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.

2 participants