Skip to content

Speed up the interpreted matcher: VarRef byte re-check and a fused child-mask walk - #127

Closed
MesTTo wants to merge 3 commits into
trueagi-io:mainfrom
MesTTo:varref-recheck
Closed

Speed up the interpreted matcher: VarRef byte re-check and a fused child-mask walk#127
MesTTo wants to merge 3 commits into
trueagi-io:mainfrom
MesTTo:varref-recheck

Conversation

@MesTTo

@MesTTo MesTTo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Two fast paths in coreferential_transition, the interpreted matcher, byte-identical by
construction and by oracle, together 32.5% on bench process_calculus.

Re-check repeated variables by byte comparison (WAM unify_value). The matcher re-checked a
VarRef, a repeated pattern variable, by pushing the data subterm bound at the first occurrence and
re-matching it structurally, which args-decomposes the data subterm on every re-check, the
dominant matcher cost. When the bound value is ground, the re-check is now an exact byte descent:
walk the bound bytes down the trie, and the moment the data branches on a variable child, fall
back to the recursive re-match for the whole subterm. The gate is proved in
kernel/resources/formal/verus/VarRefRecheck.rs (verus, 5 verified, 0 errors): on ground data the
byte comparison equals the structural match, byte equality is always sound, and a non-ground data
position can match without byte equality, which is why the fallback keys on the data, not just the
bound value. A differential oracle (varref_fast_recheck_matches_recursive_path) runs ten
coreference programs with the fast path on and off, the non-ground witness included, and asserts
identical result sets (the fast path drops duplicate re-matches, so match counts may differ; the
sets must not).

Fuse the three child-mask passes into one word-walk. The NewVar arm read each data node's
child mask three times (and(VARS), and(SIZES), and(ARITIES)) with an iterator drain and a
byte_item re-decode per child. The fused walk reads each mask word once, in the exact
VARS -> SIZES -> ARITIES visit order (VarRefs ascending, NewVar, symbol sizes ascending, arities
ascending), taking the size or arity from the bit index itself. A second oracle
(match_any_fused_equals_three_pass) runs the fused walk against the original three-pass, which
is retained behind a cfg(test) toggle, on data mixing every child tag class at one node, and
asserts an identical result list and order.

Measured, min of 3 interleaved against the pre-change binary on one machine:

  • commit 1 alone: bench process_calculus 31,526,693 -> 26,369,520 us, 16.4% faster
  • both: bench process_calculus 31,461,091 -> 21,248,837 us, 32.5% faster

Byte-identical along the way: every kernel/resources program at several step depths, Nil's bfc
chainer at proof size 19 (801 steps, a 2.9M-atom space), and a 4096-hub triangle produce identical
result spaces against the pre-change binary.

The two commits stack in one PR because both rewrite arms of coreferential_transition; separate
PRs would conflict. Independent of #124 and #126 (#126 touches only expr; if this lands with it,
the wins compose).

MesTTo added 2 commits July 2, 2026 22:04
The interpreted matcher re-checked a VarRef (a repeated pattern
variable) by pushing the data subterm bound at the first occurrence and
re-matching it structurally, which args-decomposes the data subterm,
the dominant matcher cost. When the bound value is ground, re-check it
by exact byte descent instead (WAM unify_value): descend the bound
bytes, and the moment the data branches on a variable child, fall back
to the recursive re-match.

Correctness:
- kernel/resources/formal/verus/VarRefRecheck.rs proves (verus, 5
  verified, 0 errors) the gate: for ground g and ground data the byte
  comparison equals the structural match (T1), byte equality is always
  sound (T2), and a non-ground data position can match without
  byte-equality (T3), so the data, not just the bound value, must be
  ground. The implementation meets T3 by detecting a data variable
  branch and falling back.
- A differential oracle test (varref_fast_recheck_matches_recursive_path)
  runs ten coreference programs both ways, including the Verus T3
  non-ground witness, and asserts identical result sets. The fast path
  drops duplicate re-matches, so counts may differ; the sets must not.
- Every kernel/resources program at several step depths, Nil's bfc
  chainer at proof size 19, and a 4096-hub triangle produce identical
  result spaces against the pre-change binary.

Measured (min-of-3, interleaved): bench process_calculus 31,526,693 ->
26,369,520 us, 16.4% faster. Gated behind a thread-local toggle the
differential test flips.
The NewVar arm of coreferential_transition read each data node's child
mask in three masked passes (and(VARS), and(SIZES), and(ARITIES)). Fuse
them into a single walk over the mask words, preserving the exact
VARS -> SIZES -> ARITIES visit order (VarRefs ascending from their
word, NewVar from the high word's bit 0, SymbolSizes from its bits
1..63, Arities from word 0), so the produced match stream stays
byte-identical, order included. The size or arity is the bit index
itself, dropping the three ByteMask::and ops, the three iterator
drains, and the per-child byte_item re-decode; each set child is still
descended once.

A differential oracle (match_any_fused_equals_three_pass) runs the
fused walk against the three-pass on data mixing every child tag class
at one node, variables, coreference, several symbol sizes and arities,
and deep nesting, and asserts an identical result list AND order; the
three-pass is retained behind a cfg(test) toggle to drive it. Every
kernel/resources program at several step depths, Nil's bfc chainer at
proof size 19, and a 4096-hub triangle stay byte-identical against the
pre-change binary.

Measured together with the previous commit (min-of-3, interleaved):
bench process_calculus 31,461,091 -> 21,248,837 us, 32.5% faster than
the pre-change binary.
@Adam-Vandervorst

Copy link
Copy Markdown
Collaborator

Hi! Appreciate your work here. The MORK kernel is closed to constant-time speed-ups until the asymptotic runtime has no known deficiencies. This prevents us from locking out much more impactful changes. Thanks for your understanding.

… copy buffer

The full bench suite caught what the targeted runs missed: the VarRef
fast path made tile_puzzle_states 40x slower. It ran the data-variable
branches before pushing the bound value, so every variable child
enumerated candidates with the wrong continuation; query_multi's
whole-tuple unify rejected them all, results stayed byte-identical,
and the walk cost exploded, since the puzzle re-checks boards against
move and empty facts whose columns are almost all variables. The fast
path now takes over only when the start node has no variable children
(the vs! it skips is then a no-op, so the original ordering is
preserved byte for byte wherever variable children exist), descends
subterm-aware, reading the child mask once per subterm rather than per
byte and bulk-checking symbol payloads, and a variable branch below
the start falls back to the structural re-match. The re-check's copy
of the bound value also reuses a thread-local buffer, the remaining
gap on ground-heavy workloads with tiny bound values.

Measured (min over interleaved reps, against the pre-series binary):
tile_puzzle_states 7.40s -> 7.10s (300s+ before this commit);
process_calculus 31.4 -> 21.1s; clique 15.5 -> 15.3s; transitive
31.9 -> 31.7s; odd_even_sort 7.0 -> 6.8s; counter_machine 1.3 ->
1.0s; bfc 14.5 -> 13.1s. Resources, bfc at proof size 19, the
triangle, and a distilled tile-puzzle program stay byte-identical,
and the oracle gains the schematic-boards shape.
@MesTTo

MesTTo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Closing per the same guidance: the kernel is closed to constant-time speed-ups until the asymptotic runtime has no known deficiencies. The VarRef byte re-check / fused child-mask walk is a constant-factor matcher optimization, so parking it.

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