Skip to content

Register allocation from liveness and interference - #210

Draft
hhugo wants to merge 3 commits into
ocaml-community:masterfrom
hhugo:pr4e-register-allocation
Draft

hhugo wants to merge 3 commits into
ocaml-community:masterfrom
hhugo:pr4e-register-allocation

Conversation

@hhugo

@hhugo hhugo commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Register allocation for the tagged DFA, from liveness and interference. Stacked on #209 (base: the pr4d-tdfa1 branch), three commits. An alternative to #207 that also covers what #175 set out to do; only one of #207 and this PR should land.

After #204 every tag write gets a fresh register and every accepting state copies the accepting path's registers into canonical cells: two cells per tag and a copy per tag on accept. This pass maps registers and canonical cells onto memory cells with one criterion: two cells may share a memory cell unless one is written while the other is live.

  • Liveness is a backward analysis over the automaton. Operation lists are parallel moves (read before write). A failure in a state reads the canonical cells of the rules that may be marked there, i.e. those with an accepting state from which the state is reachable through non-accepting states.
  • Dead writes, those to a cell that is not live afterwards (e.g. the tags of a rule that a higher-priority rule always beats), are dropped first, repeatedly, since dropping a copy may make its source dead.
  • Interference: a written cell and everything live after the write, except the source it is a copy of; and the cells written by one list.
  • Assignment: cells that do not interfere are merged where that removes an operation (a register with its tag's canonical cell, the two sides of a copy), then classes are colored greedily.

The three special cases #207 needs fall out of the criterion: two live paths disagreeing about a tag, a write between the mark of a rule and a failure, an accepting path setting a tag another path holds. So does #175's cross-rule sharing, so compiled gains cell_of_tag and compile_ir expresses the bindings in cells.

#209 #207 this PR
test_gen.ml (common tests): copies 21 0 0
test_gen.ml: cells 42 23 21
realistic lexer: copies / cells 2 / 4 0 / 2 0 / 1

Every captured value pinned in test/basic.ml is unchanged.

Commits

  1. cell_of_tag plumbing, identity for now; generated code unchanged.
  2. The pass (about 200 lines in src/compiler/sedlex.ml).
  3. Tests: a codegen test for the dead writes of a rule that never wins, codegen tests for three shapes where a tag needs a second cell, behaviour tests for one of them and for rules whose tags share cells (including after falling back from one rule to another), Current/Goal notes.

Verification

The #202 oracle, run locally with the TDFA(1) interpreter and no cell snapshot: no disagreement on the hand-written cases nor on 6M random cases (half of them two-rule, which is what exercises sharing), and again on 3M after adding dead-write elimination. With the "a failure reads the marked rule's cells" part of liveness removed, the same sweep finds wrong captures.

🤖 Generated with Claude Code

@hhugo
hhugo force-pushed the pr4e-register-allocation branch from 3a9714d to 6294953 Compare September 20, 2026 21:37
@hhugo
hhugo force-pushed the pr4e-register-allocation branch 2 times, most recently from 1387b90 to 746dc10 Compare September 28, 2026 22:21
@hhugo
hhugo changed the base branch from pr4d-tdfa1 to master September 28, 2026 22:21
hhugo and others added 3 commits October 5, 2026 17:03
[compiled] gains [cell_of_tag], the memory cell holding each logical tag
once a rule has matched, and [compile_ir] expresses the bindings in
cells. It is the identity for now: a tag's canonical cell has the
tag's number. The register allocation of the next commit shares cells
between tags.

[gen_definition] takes the automaton and the cell count instead of a
[compiled] record it had to be given a copy of.

Generated code is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The construction gives every tag write a fresh register and copies the
accepting path's registers into canonical cells: two cells per tag and
a copy per tag on every accept. A register allocation pass now maps
registers and canonical cells onto memory cells.

Two cells may share a memory cell unless one is written while the other
is live. Liveness is a backward analysis over the automaton; operation
lists are parallel moves, and a failure in a state reads the canonical
cells of the rules that may be marked there, i.e. those with an
accepting state from which it is reachable through non-accepting
states. Cells that do not interfere are merged where that removes an
operation, a register with the canonical cell of its tag and the two
sides of a copy, and the classes are colored greedily.

Liveness also shows which writes are dead, e.g. those of a rule that a
higher-priority rule always beats: they are dropped first, repeatedly,
since dropping a copy may make its source dead.

Most tags end up with one cell written directly by the transitions and
no final copy; a tag keeps a register apart from its canonical cell
exactly when sharing would corrupt a value read later: two live paths
disagreeing about it, a write between the mark of its rule and a
failure, or an accepting path setting it while another path holds it.
Tags of different rules share cells when their lifetimes allow, so
[cell_of_tag] is no longer the identity.

Over test/codegen/test_gen.ml: copies 21 -> 0, cells 42 -> 21. Realistic
lexer: copies 2 -> 0, cells 4 -> 1. Every captured value pinned in
test/basic.ml is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A codegen test for the dead writes of a rule that never wins, and
codegen tests for three shapes where a tag needs a second cell: two
live starts for one capture, a tag recorded again after an accepting
state of its rule on the way to a possible failure, and a tag that an
accepting path sets while another path holds it. Behaviour tests pin
the sub-matches of the last shape, and those of rules whose tags share
cells, including after falling back from one rule to another.

The Current/Goal notes of the optimization-tracking tests record one
cell per tag and the cells shared across rules.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@hhugo
hhugo force-pushed the pr4e-register-allocation branch from 746dc10 to a96fb86 Compare October 5, 2026 15:05

This branch has not been deployed

No deployments
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.

1 participant