Skip to content

phl: reorganize the program logic into a recheckable four-layer structure#1041

Draft
strub wants to merge 4 commits into
mainfrom
refactor-pl
Draft

phl: reorganize the program logic into a recheckable four-layer structure#1041
strub wants to merge 4 commits into
mainfrom
refactor-pl

Conversation

@strub

@strub strub commented Jun 10, 2026

Copy link
Copy Markdown
Member

Tracking PR for the full src/phl refactoring. It lands incrementally on refactor-pl, one tactic at a time, and stays open until the reorganization is complete. Each tactic keeps behaviour identical and touches no proof scripts. Design, rationale and the per-tactic recipe live in src/phl/REFACTORING.md; the short structural convention is in src/phl/README.md.

Goal

Replace today's "one file per tactic, all logics inside, typing entangled with goal-inspecting dispatch, opaque proof-nodes" structure with a uniform four-layer spine per tactic:

  • dispatch process_<tactic> — logic-agnostic, inspects the goal kind, routes;
  • elaboration process_<logic>_<tac> — types the parse-tree args, builds params;
  • rule (TCB) t_<logic>_<tac> — computes subgoals via a pure shared builder and emits a node recording its params;
  • checker check_<logic>_<tac> — reruns the builder from the recorded params and is_conv-compares against the stored subgoals.

Today a proof-node (VExtern tc \Tag [...]`) records that a rule fired but not with what — the tag carries no parameters and is never inspected — so proofs cannot be re-validated. This refactor makes every TCB step carry its parameters and registers a checker for it.

Kernel (ecCoreGoal)

  • open type rule = .. + VRule of rule * handle list (recheckable sibling of VExtern);
  • FApi.xrule[1][_hyps] emitting VRule;
  • checker registry (register_rule_checker) over the open rule type, recheck_proofenv driver, RecheckFailure.

The driver runs at qed (ecScope.save_r), gated by the EC_RECHECK env var. Unmigrated nodes and rules with no registered checker are skipped, so migration is incremental and normal runs pay nothing.

Layout (by family, then logic)

(include_subdirs unqualified) flattens src/ into one library, so subdirs need no dune changes (module names stay globally unique):

  • rules/<logic>/ — genuine logic rules (hoare/ehoare/bdhoare/equiv/eager)
  • codetx/ — logic-uniform program transforms (wp, sp, inline, swap, rcond, …)
  • bridge/ — probabilistic / cross-logic (deno, pr, byequiv, fel, upto, eager)
  • multi/ — multi-logic tactics with shared machinery (conseq, trans, sym)

Progress

Phase 0 — scaffolding ✅ kernel rule/VRule/xrule, checker registry + recheck_proofenv, EC_RECHECK hook, docs.

Tactic migrations (each = a commit; seq/hoare is the reference pilot):

  • seq — hoare
  • seq — ehoare / bdhoare / equiv (+ one-sided)
  • skip
  • wp / sp (codetx pilot)
  • cond / match
  • rnd
  • while (+ async)
  • call
  • fun
  • exists
  • rcond / rmatch
  • inline, swap, kill/alias/cfold (codetx)
  • fission / fusion / unroll / splitwhile (looptx)
  • deno, pr, byequiv, fel, upto, eager (bridge)
  • conseq / trans / sym (multi-logic, last)

Validation (continuous)

Every increment keeps dune build clean, the test suite green (-no-eco), and a full EC_RECHECK=1 sweep over the stdlib at zero RecheckFailure. Current: 91 theories + 23 seq-using tests pass under EC_RECHECK=1.

strub added 4 commits June 10, 2026 17:07
Begin the src/phl reorganization into a four-layer spine
(dispatch / elaboration / TCB rule / checker) with re-checkable
proof-nodes, applied one tactic at a time.

Kernel (ecCoreGoal):
- open `type rule = ..` and a new `VRule of rule * handle list`
  validation node (the recheckable sibling of the opaque `VExtern`);
- `FApi.xrule[1][_hyps]` emitting `VRule`;
- a checker registry (`register_rule_checker`) over the open rule
  type, `recheck_proofenv` driver, and `RecheckFailure`.

The driver runs at qed (ecScope.save_r) gated by the EC_RECHECK env
var; unmigrated `VExtern` nodes and rules without a registered checker
are skipped, so migration is incremental.

Pilot: hoare `seq` migrated to rules/hoare/ecHoareSeq.ml — pure
subgoal-builder shared by the rule and its checker, `RHoareSeq` node
recording the split position + intermediate assertion. EcPhlSeq routes
its hoareS arm here and re-exports t_hoare_seq, so the public interface
and external callers are unchanged.

Docs: src/phl/README.md (spine + layout convention) and
src/phl/REFACTORING.md (plan, rationale, per-tactic recipe).

Validated: 91 stdlib theories + 23 seq-using tests compile under
EC_RECHECK=1 with zero RecheckFailure.
Extract the destruct/rebuild/compare boilerplate common to every
proof-node checker into its own module, src/phl/ecPhlRecheck.ml:
- recheck_forms: arity check + per-subgoal is_conv under each subgoal's
  own hypotheses;
- checker_of name destr build: assemble a rule_checker from a pf_as_*
  reader and a subgoal builder, wrapping any exception raised while
  reading/rebuilding into a RecheckFailure (so a checker never leaks a
  raw user-level exception).

The shared subgoal-builder now takes the goal's hypotheses (the
authoritative context; env is just LDecl.toenv of it, and the
conversion check needs hyps anyway) instead of a pre-projected env, so
the rule and its checker reason in the same context. hoare_seq_subgoals
updated accordingly; the rule passes FApi.tc1_hyps. check_hoare_seq
collapses to its checker_of registration.

Docs (README, REFACTORING) updated: builder signature, the EcPhlRecheck
factoring, the migration recipe, and a note that hyps-changing rules
(xrule*_hyps, e.g. while) will need a hyps-aware recheck variant.
Make the seq tactic self-documenting via records, and clean up its
dispatcher.

- EcParsetree.seq_info becomes a record { seqi_side; seqi_at; seqi_mid;
  seqi_bd } (built at the one grammar site) instead of a 4-tuple.
- The migrated rule in rules/hoare/ exposes a parameter record
  hoare_seq_rule { hsr_at; hsr_mid }, carried by the RHoareSeq node and
  consumed by the shared subgoal-builder. The canonical
  EcHoareSeq.t_hoare_seq takes that record; the legacy EcPhlSeq.t_hoare_seq
  keeps its positional .mli and is a thin adapter onto it, so external
  callers are untouched.
- process_hoare_seq takes the seq_info record and owns the hoare
  surface-syntax validation (no side, no bound, single position/assertion).
- process_seq dispatches by pattern-matching the goal's f_node (no is_*
  predicates); the five `when is_X` guards collapse to four F...S _ arms
  plus a shared bad_params fallback reproducing the original error
  messages exactly.

Docs (REFACTORING 7b + recipe, README) updated for the two-record
convention, the rules/*-record / phl/*-positional-adapter split, and
f_node dispatch.

Build clean; pilot + 23 seq-using files pass under EC_RECHECK=1 with
zero RecheckFailure.
The checker is trusted re-validation, so it must not redo elaboration
work the rule already did. The hoare-seq checker was re-resolving the
symbolic code gap (via s_split), pulling all of code-position
normalization into its trust boundary.

Split the parameters into two records: the high-level rule arguments
(hoare_seq_rule, with a symbolic codegap1) that the canonical rule
takes, and the low-level node payload (hoare_seq_node, with the resolved
nm_codegap1 integer index) that is recorded and rechecked. The rule
resolves the gap once (new EcLowPhlGoal.s_split_index = normalize_cgap1
with the s_split error), records the resolved node, and builds subgoals
through a pure low-level core that splits at the integer
(split_at_nmcgap1, no env). The checker reruns only that core, so
normalize_cgap1 stays out of its TCB; faithfulness is by construction
since the rule builds its subgoals through the same core.

Docs: new REFACTORING 7c ("the checker is post-resolution; the node
records resolved data") + rule of thumb to record the lowest-level value
that still determines the subgoals; 7b lists both records; recipe and
README spine updated.

Build clean; pilot + 23 seq files pass under EC_RECHECK=1.
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